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"
}