Vordium LabsDocs

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

Bash
python3 -m venv .venv && . .venv/bin/activate
pip install vordium==1.4.6 requests

Make a key for the testnet

Use a key made only for the testnet — never a key that holds anything on the main network.

Bash
python3 -c "import secrets; print('0x' + secrets.token_hex(32))" > testnet.key && chmod 600 testnet.key
export PRIVATE_KEY=$(cat testnet.key)

Run it

Python
# 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

  1. Test USDC. The faucet credits test USDC straight to your account on the chain, once per address per day.
  2. A trading key. SessionCreate is signed by your account key under the VordexSession domain, 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).
  3. A limit order. vordium.mm puts the price on the market's tick and the size on its lot, signs a one-order batch with the session key under the VordCore domain and sends it to /order/batch-signed. 202 means received; the order is on the book once /orders/{address} lists it.
  4. A cancel. CancelOrder names the order id; the order leaves the open orders once the cancel is applied.

Next

Network facts last checked against the network 2026-10-22 13:00:37 UTC · mainnet spec v28 · testnet spec v8

On this page