Place Order
Place a limit order signed by the market maker's own wallet. Requires write. The body must carry a fresh wallet signature for this exact payload; the API never creates signatures and does not take payment by itself — collateral must already be funded/deposited on chain by the trading flow. EVM orders are EIP-712 typed data signed against the ClobExchange contract for that chain; Solana orders are base58 ed25519 signatures over a canonical string.
| Field | Meaning |
|---|---|
signature | Generated locally with wallet.signTypedData(domain, types, message) (EVM) or ed25519.sign (Solana). Do not copy a signature from an old GET /orders/{orderId} response. |
market | API book label, for example evm:97:314 (EVM) or 373 (Solana). |
message.market | Numeric on-chain market id inside the EIP-712 signed payload, for example 314. |
price | Ticks from 1 to 999. 660 means 66.0c. |
size | Share units with 6 decimals on every chain, whatever the collateral token's decimals: 18181818 means 18.181818 shares. The exchange converts to the token's own units; never scale by the token's decimals yourself. |
salt | Unique per order. A new salt means a new signature. |
expiry | Unix seconds, 0 for no expiry. Part of the signed message. |
type | GTC (default), GTD, FOK, FAK or POST_ONLY. Not signed. |
A successful post answers 201 { "accepted": true, "order": { "id": "o104", "status": "open", … }, "fills": [ … ] }; the id is what GET /orders/{orderId} and DELETE /orders/{orderId} take. 422 { "accepted": false, "reason": "…" } is a well-formed request the matching rules refused — invalid signature, a closed market, or a wallet that cannot cover the order.
Prerequisites for API-only trading
| Value | Meaning |
|---|---|
PRIVATE_KEY | The market maker's own wallet key. The API never creates signatures. |
RPC_URL | RPC for the chain where the CLOB market lives, for funding calls. |
ENGINE_MARKET | Engine label, for example evm:97:314. |
CLOB_MARKET_ID | Numeric on-chain market id, for example 314. |
CLOB_EXCHANGE | ClobExchange contract address. This is the EIP-712 verifying contract. |
PAYMENT_TOKEN | Collateral token used by this market, usually USDC for EVM markets. |
Three raw API requests
This is the exact API flow a market maker bot performs after it has found the engine market label and generated a fresh order signature locally. The base URL is this environment's engine URL — the Server URL at the top of this page. X-API-Key is an API key with the write scope, from the portal's keys page.
GET https://app-staging.daejin.xyz/clob/v1/marketsPOST https://app-staging.daejin.xyz/clob/v1/orderscontent-type: application/json{ "user": "0x87c088C6F5550516b8Ee80a9608149e140779850", "market": "evm:97:314", "action": "buy", "outcome": "YES", "price": 660, "size": "18181818", "type": "GTC", "salt": 1787155825206943, "expiry": 0, "signature": "0x<fresh_eip712_signature>"}GET https://app-staging.daejin.xyz/clob/v1/markets/evm%3A97%3A314/bookGET https://app-staging.daejin.xyz/clob/v1/markets/evm%3A97%3A314/ordersThe playground on this page cannot invent signature. Use the example client (examples/market-maker/mm-client.mjs in prediction-backend) or your own bot to sign first, then send the POST body.
Signature generation — EVM
Market makers generate a new signature locally for each unique order payload. The private key stays in their bot or vault; the platform only receives the resulting signature. EVM orders are EIP-712 typed data signed against the ClobExchange contract for that chain.
import { Wallet } from "ethers";const wallet = new Wallet(process.env.PRIVATE_KEY);const chainId = 97;const clobMarketId = 314n;const clobExchange = process.env.CLOB_EXCHANGE;const domain = { name: "tokenplay-clob-order-v1", version: "1", chainId, verifyingContract: clobExchange,};const types = { Order: [ { name: "user", type: "address" }, { name: "market", type: "uint256" }, { name: "action", type: "uint8" }, { name: "outcome", type: "uint8" }, { name: "price", type: "uint256" }, { name: "size", type: "uint256" }, { name: "salt", type: "uint256" }, { name: "expiry", type: "uint256" }, ],};const message = { user: wallet.address, market: clobMarketId, action: 0, // 0 = buy, 1 = sell outcome: 0, // 0 = YES, 1 = NO price: 660n, // 66.0c size: 18181818n, // 18.181818 shares salt: BigInt(Date.now()) * 1000n, expiry: 0n,};const signature = await wallet.signTypedData(domain, types, message);console.log(JSON.stringify({ user: wallet.address, market: "evm:97:314", action: "buy", outcome: "YES", price: Number(message.price), size: message.size.toString(), type: "GTC", salt: Number(message.salt), expiry: Number(message.expiry), signature,}, null, 2));Signature generation — Solana
Solana orders are ed25519 signatures over a canonical UTF-8 string, base58-encoded. Not EIP-712, and not a struct hash — the message is readable text because Phantom refuses to signMessage a binary payload. Four details that each surface as a bare invalid signature:
| Field | Rule |
|---|---|
market | The plain decimal on-chain market id, "373". Not evm:<chainId>:<id>, not sol:<pubkey> — a non-numeric label throws before the signature is read. |
outcome | Lowercase inside the signed message, UPPERCASE in the JSON body. The asymmetry is real. |
signature | base58. A base64 signature is rejected. |
salt, expiry | Both must be sent. The settler rebuilds the exact signed bytes for settle_fill; an order placed without its salt can never settle. |
import { ed25519 } from "@noble/curves/ed25519";import bs58 from "bs58";// 64-byte Solana secret key (Phantom export, or a solana-keygen JSON array).const secret = bs58.decode(process.env.SOLANA_PRIVATE_KEY);const seed = secret.slice(0, 32); // ed25519 signs with the seedconst user = bs58.encode(ed25519.getPublicKey(seed));const order = { user, market: 373, // numeric on-chain id — also the engine label action: "buy", // "buy" | "sell" outcome: "yes", // lowercase HERE, uppercase in the body price: 660, // ticks, 660 = 66.0c size: 18181818, // raw payment-mint units (USDC is 6dp) salt: Date.now() * 1000, expiry: 0, // unix SECONDS, 0 = no expiry};// Byte-identical to the on-chain OrderData::message().const message = [ "tokenplay-clob-order-v1", order.user, order.market, order.action, order.outcome, order.price, order.size, order.salt, order.expiry,].join("|");const signature = bs58.encode( ed25519.sign(new TextEncoder().encode(message), seed));console.log(JSON.stringify({ user, market: "373", action: order.action, outcome: order.outcome.toUpperCase(), price: order.price, size: String(order.size), type: "GTC", salt: order.salt, expiry: order.expiry, signature,}, null, 2));Or with the bundled clients: npm run mm -- place … (EVM) and npm run mm:solana -- place --action buy --outcome YES --price 400 --size 5000000 (Solana). Swap place for message to print the signed string and the signature without sending anything — the fastest way to see why a signature is being rejected. To prove the signature is really checked, post the same body with any other valid signature: the engine answers 422 {"accepted":false,"reason":"invalid signature"}.
Developer-portal API key (tp_live_… / tp_test_…), also accepted as Authorization: Bearer tp_…. Public market data needs no key; user-scoped reads need read, placing and cancelling need write.
In: header
Request Body
application/json
Response Body
curl -X POST "https://example.com/orders" \ -H "Content-Type: application/json" \ -d '{ "user": "string", "market": "evm:56:123", "action": "buy", "outcome": "YES", "price": 0, "size": "string", "signature": "string" }'