API reference
Trading (authenticated)
All endpoints here require X-Thassa-Key; mutations require scope trade and honor Idempotency-Key. Order placement goes through the same relayer gate as the app — same validation, same batching, same states.
Place an order
/trade-api/v1/ordersX-Thassa-Key · scope tradeAccepts the same non-custodial payload as the app: the order fields plus an EIP-3009 authorization whose auth_nonce equals the order’s EIP-712 digest (the signature carriage convention). The API never signs for you — it validates and relays.
Maker binding
order.maker MUST equal the wallet registered to the API key’s user. Orders signed by any other address are rejected.| Body field | Type | Description | |
|---|---|---|---|
order.market_id | string | required | Target market. New-market opening orders are signed with marketId = 0 and bound to the assigned id on creation. |
order.side | int | required | 0 = YES, 1 = NO. |
order.price | int | required | Limit price in cents, 1..99. |
order.shares | string | required | Number of $1 shares. |
order.max_cost | string | required | Max token base units the signature authorizes (escrow + taker-fee headroom). |
order.affiliate_post_id | string | required | Post routing the trade; "0" = none. |
order.expiry | int | required | Unix seconds; order invalid after this. |
order.nonce | string | required | Per-maker sequential nonce. |
order.maker | address | required | Your registered wallet. |
auth.value | string | required | EIP-3009 value; must cover max_cost. |
auth.valid_after / valid_before | string | required | Authorization validity window (unix seconds). |
auth.auth_nonce | bytes32 | required | MUST equal orderDigest(order). Validated before batching. |
auth.v / r / s | sig | required | The single EIP-3009 signature. |
{
"order": {
"market_id": "42",
"side": 0, // 0 = YES, 1 = NO
"price": 62, // cents, 1..99
"shares": "100",
"max_cost": "63700000", // token base units: escrow + fee headroom
"affiliate_post_id": "0",
"expiry": 1752969600, // unix seconds
"nonce": "7", // per-maker sequential
"maker": "0x4A1f…9E02" // MUST be your registered wallet
},
"auth": { // EIP-3009 ReceiveWithAuthorization
"value": "63700000",
"valid_after": "0",
"valid_before": "1752969600",
"auth_nonce": "0x83fa…11cd", // = orderDigest(order) — signature carriage
"v": 27,
"r": "0x5c02…aa19",
"s": "0x2e8d…03b7"
}
}import { createWalletClient, hashTypedData, http, parseSignature } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { randomUUID } from "node:crypto";
const account = privateKeyToAccount(process.env.WALLET_KEY as `0x${string}`);
const wallet = createWalletClient({ account, transport: http(RPC_URL) });
// 1. Build the order. nonce is per-maker sequential — read it from
// GET /trade-api/v1/balance (order_nonce) or the contract's nonces(maker).
const order = {
marketId: 42n,
side: 0, // 0 = YES
price: 62, // cents
shares: 100n,
maxCost: 63_700_000n, // 100 × $0.62 escrow + fee headroom (6-dp token)
affiliatePostId: 0n,
expiry: BigInt(Math.floor(Date.now() / 1000) + 3600),
nonce: 7n,
maker: account.address,
};
// 2. Compute the order's EIP-712 digest — domain {ThassaMarkets, 1, chainId, contract}.
const orderDigest = hashTypedData({
domain: {
name: "ThassaMarkets",
version: "1",
chainId: CHAIN_ID,
verifyingContract: MARKETS_CONTRACT,
},
types: {
Order: [
{ name: "marketId", type: "uint256" },
{ name: "side", type: "uint8" },
{ name: "price", type: "uint8" },
{ name: "shares", type: "uint80" },
{ name: "maxCost", type: "uint256" },
{ name: "affiliatePostId", type: "uint256" },
{ name: "expiry", type: "uint64" },
{ name: "nonce", type: "uint256" },
{ name: "maker", type: "address" },
],
},
primaryType: "Order",
message: order,
});
// 3. Sign ONE thing: the payment token's ReceiveWithAuthorization,
// with nonce = orderDigest (the signature carriage convention).
const signature = await wallet.signTypedData({
domain: {
name: PAYMENT_TOKEN_NAME, // the token's own EIP-712 domain
version: PAYMENT_TOKEN_VERSION,
chainId: CHAIN_ID,
verifyingContract: PAYMENT_TOKEN,
},
types: {
ReceiveWithAuthorization: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "validAfter", type: "uint256" },
{ name: "validBefore", type: "uint256" },
{ name: "nonce", type: "bytes32" },
],
},
primaryType: "ReceiveWithAuthorization",
message: {
from: account.address,
to: MARKETS_CONTRACT, // always the markets contract
value: order.maxCost,
validAfter: 0n,
validBefore: BigInt(order.expiry),
nonce: orderDigest,
},
});
const { v, r, s } = parseSignature(signature);
// 4. Submit — idempotently.
const res = await fetch("http://localhost:8080/trade-api/v1/orders", {
method: "POST",
headers: {
"X-Thassa-Key": process.env.THASSA_KEY!,
"Idempotency-Key": randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
order: {
market_id: order.marketId.toString(),
side: order.side,
price: order.price,
shares: order.shares.toString(),
max_cost: order.maxCost.toString(),
affiliate_post_id: order.affiliatePostId.toString(),
expiry: Number(order.expiry),
nonce: order.nonce.toString(),
maker: order.maker,
},
auth: {
value: order.maxCost.toString(),
valid_after: "0",
valid_before: order.expiry.toString(),
auth_nonce: orderDigest,
v: Number(v),
r,
s,
},
}),
});
if (!res.ok) throw new Error((await res.json()).error);
const { order: placed } = await res.json(); // status: "QUEUED"{
"order": {
"id": "0f6b1c9a-88a1-4c5e-b7d2-6f6a5f3f2e11",
"market_id": "42",
"side": "yes",
"price_cents": 62,
"shares": "100",
"filled_shares": "0",
"status": "QUEUED",
"created_at": "2026-07-16T09:12:30Z"
}
}The response is immediate with status QUEUED; the relayer lands the batch onchain seconds later and the order moves to RESTING, PARTIAL, or FILLED. Track it via GET /trade-api/v1/orders or the WebSocket.
Cancel an order
/trade-api/v1/orders/{id}X-Thassa-Key · scope tradeCancels a resting order (signed cancel, relayed like placement). The unfilled remainder is refunded to your free balance; already-filled shares stay filled.
curl -X DELETE "http://localhost:8080/trade-api/v1/orders/0f6b1c9a-88a1-4c5e-b7d2-6f6a5f3f2e11" \
-H "X-Thassa-Key: $THASSA_KEY" \
-H "Idempotency-Key: $(uuidgen)"List your orders
/trade-api/v1/ordersX-Thassa-Key · scope read| Query parameter | Type | Description | |
|---|---|---|---|
market | string | optional | Filter to one market id. |
cursor | string | optional | Opaque pagination cursor. |
limit | int | optional | Page size (server-capped). |
curl "http://localhost:8080/trade-api/v1/orders?market=42" \
-H "X-Thassa-Key: $THASSA_KEY"{
"orders": [
{
"id": "0f6b1c9a-88a1-4c5e-b7d2-6f6a5f3f2e11",
"market_id": "42",
"side": "yes",
"price_cents": 62,
"shares": "100",
"filled_shares": "40",
"status": "PARTIAL",
"created_at": "2026-07-16T09:12:30Z"
}
],
"next_cursor": null
}List your positions
/trade-api/v1/positionsX-Thassa-Key · scope readNet position per market and side, with average entry price and realized PnL (in token base units).
curl "http://localhost:8080/trade-api/v1/positions" \
-H "X-Thassa-Key: $THASSA_KEY"{
"positions": [
{
"market_id": "42",
"question": "Will it rain in San Francisco on Saturday?",
"market_status": "OPEN",
"side": "yes",
"shares": "40",
"avg_price_cents": 62,
"realized_pnl": "0"
}
],
"next_cursor": null
}List your fills
/trade-api/v1/fillsX-Thassa-Key · scope readYour execution history across markets — every match your orders participated in, with price, shares, fee, and transaction hash.
curl "http://localhost:8080/trade-api/v1/fills" \
-H "X-Thassa-Key: $THASSA_KEY"{
"fills": [
{
"id": "f7a2…",
"market_id": "42",
"order_id": "0f6b1c9a-88a1-4c5e-b7d2-6f6a5f3f2e11",
"side": "yes",
"price_cents": 62,
"shares": "40",
"fee": "659680",
"tx_hash": "0x8c31…e9d0",
"created_at": "2026-07-16T09:12:34Z"
}
],
"next_cursor": null
}Get your balance
/trade-api/v1/balanceX-Thassa-Key · scope readPayment-token balance of your registered wallet, plus your next sequential order nonce.
curl "http://localhost:8080/trade-api/v1/balance" \
-H "X-Thassa-Key: $THASSA_KEY"{
"balance": "241503300",
"wallet_address": "0x4A1f…9E02",
"order_nonce": "8"
}