Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- the eth private key will only be used in the Py SDK to sign a message
- the eth private key is not required in order to trade on the platform
- the eth private key is not passed to the binary
- the API key config is saved in a local file `./api_key_config.json`
- the API key config is saved as `examples/api_key_config.json`; examples resolve this path from their source location, so they can be run from any working directory

## Start trading on testnet
- `create_modify_cancel_order_http.py`
Expand Down
5 changes: 2 additions & 3 deletions examples/integrator/approve.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@


ETH_PRIVATE_KEY = ""
CONFIG_FILE = "../api_key_config.json"
APPROVAL_EXPIRY = int(time.time() * 1000) + 90 * 24 * 60 * 60 * 1000 # now + 90 days, in ms


async def main():
client, api_client, _ = default_example_setup(CONFIG_FILE)
client, api_client, _ = default_example_setup()

err = client.check_client()
if err is not None:
Expand All @@ -31,4 +30,4 @@ async def main():
await api_client.close()

if __name__ == "__main__":
asyncio.run(main())
asyncio.run(main())
5 changes: 2 additions & 3 deletions examples/integrator/approve_same_master_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
from examples.utils import default_example_setup


CONFIG_FILE = "../api_key_config.json"
APPROVAL_EXPIRY = int(time.time() * 1000) + 90 * 24 * 60 * 60 * 1000 # now + 90 days, in ms


async def main():
client, api_client, _ = default_example_setup(CONFIG_FILE)
client, api_client, _ = default_example_setup()

err = client.check_client()
if err is not None:
Expand All @@ -30,4 +29,4 @@ async def main():
await api_client.close()

if __name__ == "__main__":
asyncio.run(main())
asyncio.run(main())
5 changes: 2 additions & 3 deletions examples/integrator/approve_zero_fees.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
from examples.utils import default_example_setup


CONFIG_FILE = "../api_key_config.json"
APPROVAL_EXPIRY = int(time.time() * 1000) + 90 * 24 * 60 * 60 * 1000 # now + 90 days, in ms


async def main():
client, api_client, _ = default_example_setup(CONFIG_FILE)
client, api_client, _ = default_example_setup()

err = client.check_client()
if err is not None:
Expand All @@ -32,4 +31,4 @@ async def main():


if __name__ == "__main__":
asyncio.run(main())
asyncio.run(main())
5 changes: 1 addition & 4 deletions examples/integrator/create_market_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
from examples.utils import default_example_setup


CONFIG_FILE = "../api_key_config.json"


async def main():
client, api_client, _ = default_example_setup(CONFIG_FILE)
client, api_client, _ = default_example_setup()
client.check_client()

# Note: change this to 2048 to trade spot ETH. Make sure you have at least 0.1 ETH to trade spot.
Expand Down
5 changes: 1 addition & 4 deletions examples/integrator/create_modify_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
from examples.utils import default_example_setup


CONFIG_FILE = "../api_key_config.json"


async def main():
client, api_client, _ = default_example_setup(CONFIG_FILE)
client, api_client, _ = default_example_setup()
client.check_client()

# Note: change this to 2048 to trade spot ETH. Make sure you have at least 0.1 ETH to trade spot.
Expand Down
7 changes: 2 additions & 5 deletions examples/integrator/revoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
from examples.utils import default_example_setup


CONFIG_FILE = "../api_key_config.json"


async def main():
client, api_client, _ = default_example_setup(CONFIG_FILE)
client, api_client, _ = default_example_setup()

err = client.check_client()
if err is not None:
Expand All @@ -28,4 +25,4 @@ async def main():
await api_client.close()

if __name__ == "__main__":
asyncio.run(main())
asyncio.run(main())
13 changes: 7 additions & 6 deletions examples/orders/create_market_order_async_client.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import asyncio

import lighter
from examples.utils import get_api_key_config, trim_exception
from examples.utils import DEFAULT_API_KEY_CONFIG, get_api_key_config, trim_exception


async def create_signer_for_user(config_file="./api_key_config.json"):
async def create_signer_for_user(config_file=DEFAULT_API_KEY_CONFIG):
# Constructing the SignerClient performs no network I/O (nonces are
# fetched lazily on first use), so it is safe inside a coroutine.
base_url, account_index, private_keys = get_api_key_config(config_file)
profile, account_index, private_keys = get_api_key_config(config_file)
client = lighter.SignerClient(
url=base_url,
url=profile.api_url,
account_index=account_index,
api_private_keys=private_keys,
chain_id=profile.chain_id,
)

err = client.check_client()
Expand All @@ -22,7 +23,7 @@ async def create_signer_for_user(config_file="./api_key_config.json"):
return client


async def place_market_order_for_user(config_file="./api_key_config.json"):
async def place_market_order_for_user(config_file=DEFAULT_API_KEY_CONFIG):
client = await create_signer_for_user(config_file)

try:
Expand All @@ -32,7 +33,7 @@ async def place_market_order_for_user(config_file="./api_key_config.json"):
tx, tx_hash, err = await client.create_market_order(
market_index=market_index,
client_order_index=0,
base_amount=100, # 0.1 ETH
base_amount=10, # 0.1 ETH
avg_execution_price=4000_00, # $4000 -- worst acceptable price for the order
is_ask=False,
)
Expand Down
108 changes: 108 additions & 0 deletions examples/orders/modify_order_version_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import asyncio
import time
from examples.utils import default_example_setup


# Versioned order modification.
#
# order_version: a client-supplied, strictly increasing version guard for modify order.
# * A modify with order_version=V is applied only if V is greater than the order's
# current version. On success, the order's version becomes V.
# * The default (NIL_ORDER_VERSION = 0) skips the check and always applies.
# * A stale modify (version <= current) is rejected, which protects against
# out-of-order or retried modifies overwriting a newer one.
#
# A common pattern is to use a millisecond timestamp as the version.
async def main():
client, api_client, _ = default_example_setup()
client.check_client()

# Note: change this to 2048 to trade spot ETH. Make sure you have at least 0.1 ETH to trade spot.
market_index = 0
client_order_index = int(time.time() * 1000)

# create order
api_key_index, nonce = client.nonce_manager.next_nonce()
tx, tx_hash, err = await client.create_order(
market_index=market_index,
client_order_index=client_order_index,
base_amount=1000, # 0.1 ETH
price=4050_00, # $4050
is_ask=True,
order_type=client.ORDER_TYPE_LIMIT,
time_in_force=client.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
reduce_only=False,
trigger_price=0,
nonce=nonce,
api_key_index=api_key_index,
)
print(f"Create Order {tx=} {tx_hash=} {err=}")
if err is not None:
raise Exception(err)

## modify order with version 100
# use the same API key so the TX goes after the create order TX
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
tx, tx_hash, err = await client.modify_order(
market_index=market_index,
order_index=client_order_index,
base_amount=1100, # 0.11 ETH
price=4100_00, # $4100
trigger_price=0,
order_version=100,
nonce=nonce,
api_key_index=api_key_index,
)
print(f"Modify Order (version=100) {tx=} {tx_hash=} {err=}")
if err is not None:
raise Exception(err)

## stale modify: version 100 is not greater than the order's current version (100),
## so this modify is rejected by the exchange and the order keeps its previous state
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
tx, tx_hash, err = await client.modify_order(
market_index=market_index,
order_index=client_order_index,
base_amount=1200, # 0.12 ETH
price=4150_00, # $4150
trigger_price=0,
order_version=100,
nonce=nonce,
api_key_index=api_key_index,
)
print(f"Stale Modify Order (version=100, expected to be rejected) {tx=} {tx_hash=} {err=}")

## modify order with a higher version (200) succeeds
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
tx, tx_hash, err = await client.modify_order(
market_index=market_index,
order_index=client_order_index,
base_amount=1200, # 0.12 ETH
price=4150_00, # $4150
trigger_price=0,
order_version=200,
nonce=nonce,
api_key_index=api_key_index,
)
print(f"Modify Order (version=200) {tx=} {tx_hash=} {err=}")
if err is not None:
raise Exception(err)

## cancel order
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
tx, tx_hash, err = await client.cancel_order(
market_index=market_index,
order_index=client_order_index,
nonce=nonce,
api_key_index=api_key_index,
)
print(f"Cancel Order {tx=} {tx_hash=} {err=}")
if err is not None:
raise Exception(err)

await client.close()
await api_client.close()


if __name__ == "__main__":
asyncio.run(main())
76 changes: 76 additions & 0 deletions examples/orders/modify_order_version_ws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import time
import websockets
import asyncio
from examples.utils import default_example_setup, ws_send_tx


# this example does the same thing as the modify_order_version_http.py example, but sends the TX over WS instead of HTTP
async def main():
client, api_client, ws_client_promise = default_example_setup()
client.check_client()

# set up WS client and print a connected message
ws_client: websockets.ClientConnection = await ws_client_promise
print("Received:", await ws_client.recv())

# Note: change this to 2048 to trade spot ETH. Make sure you have at least 0.1 ETH to trade spot.
market_index = 0
client_order_index = int(time.time() * 1000)

# create order
api_key_index, nonce = client.nonce_manager.next_nonce()
tx_type, tx_info, tx_hash, err = client.sign_create_order(
market_index=market_index,
client_order_index=client_order_index,
base_amount=1000, # 0.1 ETH
price=4050_00, # $4050
is_ask=True,
order_type=client.ORDER_TYPE_LIMIT,
time_in_force=client.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
reduce_only=False,
trigger_price=0,
nonce=nonce,
api_key_index=api_key_index,
)
if err is not None:
raise Exception(err)
await ws_send_tx(ws_client, tx_type, tx_info, tx_hash)

## modify order using a millisecond timestamp as the order version
# a modify is applied only if its order_version is greater than the order's current version
# use the same API key so the TX goes after the create order TX
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
tx_type, tx_info, tx_hash, err = client.sign_modify_order(
market_index=market_index,
order_index=client_order_index,
base_amount=1100, # 0.11 ETH
price=4100_00, # $4100
trigger_price=0,
order_version=int(time.time() * 1000),
nonce=nonce,
api_key_index=api_key_index,
)
if err is not None:
raise Exception(err)
await ws_send_tx(ws_client, tx_type, tx_info, tx_hash)

## cancel order
# use the same API key so the TX goes after the modify order TX
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
tx_type, tx_info, tx_hash, err = client.sign_cancel_order(
market_index=market_index,
order_index=client_order_index,
nonce=nonce,
api_key_index=api_key_index,
)
if err is not None:
raise Exception(err)
await ws_send_tx(ws_client, tx_type, tx_info, tx_hash)

await client.close()
await api_client.close()
await ws_client.close()


if __name__ == "__main__":
asyncio.run(main())
10 changes: 7 additions & 3 deletions examples/utils.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
from typing import Tuple, Optional
import logging
import json
from pathlib import Path
import websockets
import lighter


DEFAULT_API_KEY_CONFIG = Path(__file__).resolve().with_name("api_key_config.json")


def trim_exception(e: Exception) -> str:
return str(e).strip().split("\n")[-1]


def save_api_key_config(endpoint_profile, account_index, private_keys, config_file="./api_key_config.json"):
def save_api_key_config(endpoint_profile, account_index, private_keys, config_file=DEFAULT_API_KEY_CONFIG):
with open(config_file, "w", encoding="utf-8") as f:
json.dump({
"endpointProfile": endpoint_profile.name,
Expand All @@ -18,7 +22,7 @@ def save_api_key_config(endpoint_profile, account_index, private_keys, config_fi
}, f, ensure_ascii=False, indent=2)


def get_api_key_config(config_file="./api_key_config.json"):
def get_api_key_config(config_file=DEFAULT_API_KEY_CONFIG):
with open(config_file) as f:
cfg = json.load(f)

Expand All @@ -43,7 +47,7 @@ def get_api_key_config(config_file="./api_key_config.json"):
return profile, cfg["accountIndex"], private_key


def default_example_setup(config_file="./api_key_config.json",nonce_management_type=lighter.nonce_manager.NonceManagerType.OPTIMISTIC,endpoint_profile=None,) -> Optional[Tuple[lighter.SignerClient, lighter.ApiClient, websockets.connect]]:
def default_example_setup(config_file=DEFAULT_API_KEY_CONFIG,nonce_management_type=lighter.nonce_manager.NonceManagerType.OPTIMISTIC,endpoint_profile=None,) -> Optional[Tuple[lighter.SignerClient, lighter.ApiClient, websockets.connect]]:
logging.basicConfig(level=logging.DEBUG)

config_endpoint, account_index, private_keys = get_api_key_config(config_file)
Expand Down
Loading
Loading