First trade in Python
In five minutes on the testnet — get test USDC, open a trading key, place a limit order, see it on the book, cancel it.
This walks through a whole trade on the testnet with the published Python SDK. Testnet tokens have no value; nothing here touches the main network.
Install
python3 -m venv .venv && . .venv/bin/activate
pip install vordium==1.4.6 requestsMake a key for the testnet
Use a key made only for the testnet — never a key that holds anything on the main network.
python3 -c "import secrets; print('0x' + secrets.token_hex(32))" > testnet.key && chmod 600 testnet.key
export PRIVATE_KEY=$(cat testnet.key)Run it
# Your first trade on the Vordium testnet: test USDC, a trading key, a limit order, a cancel.
# pip install vordium==1.4.6 requests
import os, time, secrets, requests
from eth_account import Account
from eth_account.messages import encode_typed_data
from vordium import Vordex, domains, mm, use_network
from vordium.dex import BUY, LIMIT
net = use_network("testnet") # every address below comes from the testnet's own spec
API = "https://testnet.vordex.xyz/chain" # the testnet's REST read service
owner = Account.from_key(os.environ["PRIVATE_KEY"]) # a key you made for the testnet — never a mainnet key
status = requests.get(f"{API}/status", timeout=10).json()
# 1. Test USDC: once per address per day; a second claim the same day is refused, which is fine
r = requests.post("https://testnet.vordex.xyz/faucet", json={"address": owner.address, "to": "chain"}, timeout=30)
print("faucet:", r.status_code, r.json().get("status") or r.json().get("error"))
for _ in range(30):
if int(requests.get(f"{API}/account/equity/{owner.address}", timeout=10).json().get("free_balance") or 0) > 0:
break
time.sleep(2)
# 2. A trading key (a session): the owner signs SessionCreate once; the session key then signs every order
session = Account.from_key("0x" + secrets.token_hex(32))
expires_at = int(time.time()) + 24 * 3600
nonce = int(time.time() * 1000) # must be higher than this account's last session nonce
typed = encode_typed_data(
domain_data=domains.domain("VordexSession", status["chain_id"], status["genesis_sha256"]),
message_types={"SessionCreate": [{"name": "sessionKey", "type": "address"},
{"name": "expiresAt", "type": "uint256"},
{"name": "nonce", "type": "uint256"}]},
message_data={"sessionKey": session.address, "expiresAt": expires_at, "nonce": nonce})
sig = owner.sign_message(typed).signature.hex()
r = requests.post(f"{API}/session/create", timeout=15, json={
"owner": owner.address, "session_key": session.address, "expires_at": expires_at,
"nonce": nonce, "signature": "0x" + sig.removeprefix("0x")})
print("session:", r.status_code)
for _ in range(30): # 202 means received; it counts once the chain confirms it
v = requests.get(f"{API}/session/valid", params={"owner": owner.address, "session_key": session.address}, timeout=10).json()
if v.get("valid") and v.get("confirmed"):
break
time.sleep(2)
print("session confirmed:", v.get("confirmed"))
# 3. A limit buy of ETH (pair 1) 5% under the mark, so it rests on the book instead of filling.
# vordium.mm works in exact integers and puts the price on the market's tick and the size on its lot:
# the chain does not apply an order whose price or size is off them.
dex = Vordex()
spec = mm.pair_specs(requests.get(f"{API}/markets/specs", timeout=10).json())[1]
mark8 = int(requests.get(f"{API}/oracle/1", timeout=10).json()["mark_price"]) * 100 # the read carries 6 decimals
price8 = mm.round_price(mark8 * 95 // 100, spec, "buy")
size18 = mm.round_size(20 * 10**26 // price8, spec) + spec.lot_18dec # about 20 USDC of ETH
maker = mm.MarketMaker(API, session.key.hex(), owner.address) # signs with the trading key, for the owner
client_nonce = maker.nonce() # your id for the order; it shares one nonce space with the batch, so one clock gives both
placed = maker.batch([mm.place(1, "buy", size18, price8, client_nonce)])
print("order:", placed.status_code, "submitted" if placed.ok else placed.refusal)
# 4. Find it among the open orders once the chain has applied it, then cancel it
order = None
for _ in range(30):
rows = requests.get(f"{API}/orders/{owner.address}", timeout=10).json()
order = next((o for o in rows if o["pair_id"] == 1 and o["client_nonce"] == client_nonce), None)
if order:
break
time.sleep(1)
print("resting:", order["id"], order["side"], order["order_type"], order["price_8dec"] / 1e8)
cancelled = dex.cancel_order(session.key.hex(), owner.address, order["id"])
print("cancel:", cancelled.get("status_code"))
for _ in range(30): # gone from the open orders once the cancel is applied
if not any(o["id"] == order["id"] for o in requests.get(f"{API}/orders/{owner.address}", timeout=10).json()):
print("cancelled: yes")
break
time.sleep(1)You should see the faucet answer, the session confirmed, the order resting at its price, and then cancelled.
What happened
- Test USDC. The faucet credits test USDC straight to your account on the chain, once per address per day.
- A trading key.
SessionCreateis signed by your account key under theVordexSessiondomain, bound to the testnet's genesis. From then on the session key signs orders, so your account key stays out of the trading loop. A session counts once the chain confirms it (/session/valid→confirmed: true). - A limit order.
vordium.mmputs the price on the market's tick and the size on its lot, signs a one-order batch with the session key under theVordCoredomain and sends it to/order/batch-signed.202means received; the order is on the book once/orders/{address}lists it. - A cancel.
CancelOrdernames the order id; the order leaves the open orders once the cancel is applied.
Next
- The same in TypeScript.
- Give a program its own limited key: your first agent.
- Everything an order can carry: Orders and the REST reference.
- On the main network:
use_network("mainnet"), a funded account, and the same code.
Network facts last checked against the network 2026-10-22 13:00:37 UTC · mainnet spec v28 · testnet spec v8