Wallet-authenticated API · v1

Ignite Authenticated Trading API

Connect an EVM wallet, create a short-lived Ignite session, read the wallet's account state, and place or cancel orders. Standard wallets use EIP-712; Thanos Wallet uses its published SIWE personal_sign flow. Authentication proves wallet control; Ignite never requests a private key or seed phrase.

Authentication flow

1. Request a challenge

Send the wallet address. The challenge is valid for five minutes and replaces any earlier challenge for that address.

2. Sign typed data

Sign the returned typedData object as EIP-712 data. Do not sign the human-readable challenge hint.

3. Verify the signature

Send the address and signature. A valid, unused challenge returns an eight-hour bearer session.

4. Call protected routes

Send Authorization: Bearer <token>. Responses are always scoped to the authenticated wallet.

Request a wallet challenge

POST /v1/auth/challenge

The EIP-712 domain intentionally has no chain ID, so the login is not tied to the wallet's currently selected network.

curl -X POST https://api.ignite.trade/v1/auth/challenge \
  -H "Content-Type: application/json" \
  -d '{"address":"0xYOUR_WALLET"}'

{
  "typedData": {
    "domain": { "name": "Ignite DEX", "version": "1" },
    "types": { "Login": [
      { "name": "address", "type": "address" },
      { "name": "nonce", "type": "string" },
      { "name": "issuedAt", "type": "string" }
    ]},
    "primaryType": "Login",
    "message": {
      "address": "0xYOUR_WALLET",
      "nonce": "single-use-random-value",
      "issuedAt": "2026-07-27T12:00:00.000Z"
    }
  },
  "expiresIn": "5m"
}

Verify and create a session

POST /v1/auth/verify
// viem example
const challenge = await fetch('https://api.ignite.trade/v1/auth/challenge', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ address: account.address })
}).then((response) => response.json());

const signature = await walletClient.signTypedData({
  account,
  ...challenge.typedData
});

const session = await fetch('https://api.ignite.trade/v1/auth/verify', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ address: account.address, signature })
}).then((response) => response.json());

// { "verified": true, "token": "<session JWT>" }
A nonce is single-use. A replay, expired nonce, changed address, or altered typed message is rejected.
Treat the returned token as a temporary credential. Keep it out of URLs and logs and discard it after eight hours.

Thanos Wallet SIWE compatibility

GET + POST /api/auth/nonce · /api/auth/verify

Thanos Wallet signs an EIP-4361 SIWE message with personal_sign. Request a nonce for the wallet, build the SIWE message with the current Ignite domain and URI, sign that exact message, then verify it. The returned sessionToken is the same eight-hour bearer credential used by all protected routes.

const nonce = await fetch(
  'https://api.ignite.trade/api/auth/nonce?address=' + account.address
).then((response) => response.text());

// Use thanos-connect's buildSiweMessage() so the signed bytes match.
const message = buildSiweMessage({
  domain: window.location.host,
  address: account.address,
  uri: window.location.origin,
  statement: 'Sign in to Ignite DEX with your Thanos Wallet.',
  chainId,
  nonce,
  expirationTime: new Date(Date.now() + 5 * 60_000).toISOString()
});

const signature = await walletClient.signMessage({ account, message });
const { sessionToken } = await fetch('https://api.ignite.trade/api/auth/verify', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ address: account.address, message, signature })
}).then((response) => response.json());

The nonce endpoint answers text/plain (32 hex characters). It is valid for five minutes and one login; requesting another nonce for the same address replaces it. The server checks the following and nothing else: the personal_sign signature recovers to address; line 1 names an Ignite web host as the domain (ignite.trade or www.ignite.trade); line 2 is the same address; the Nonce: line carries the current nonce; and Expiration Time:, if present, is in the future. URI, Version, Chain ID, Statement and Issued At are not checked. Lines are separated by a single LF, with no trailing newline.

ignite.trade wants you to sign in with your Ethereum account:
0x1111111111111111111111111111111111111111

Sign in to Ignite DEX with your Thanos Wallet.

URI: https://ignite.trade
Version: 1
Chain ID: 1
Nonce: 3f6c1d0e9b8a7c6d5e4f30211a2b3c4d
Issued At: 2026-10-10T12:00:00.000Z
Expiration Time: 2026-10-10T12:05:00.000Z
401 codeMeaning
signature_mismatchThe signature does not recover to address over the exact message bytes.
malformed_messageNot an EIP-4361 message, or an unreadable Expiration Time.
domain_mismatchLine 1 names a domain other than an Ignite web host.
address_mismatchLine 2 is not the address in the request body.
message_expiredExpiration Time has passed.
nonce_missingThe message has no Nonce line.
nonce_invalidNot the current nonce for this address: never issued, replaced by a newer one, or already used.
nonce_expiredThe nonce is more than five minutes old.
This compatibility path is for Thanos Wallet only. Do not send an EIP-712 signature to the SIWE verifier, and never reuse a nonce or signed message. Both login flows issue the same eight-hour bearer token; /v1/auth/verify returns it as token and /api/auth/verify as sessionToken, because the latter follows the Thanos Connect SDK contract.

Authoritative market rules

Read GET /v1/markets before submitting an order. Each market record is authoritative for symbol, type, status, tradingEnabled, feeBps, tickSize, minOrderSize, maxOrderSize, token symbols, and network ID.

Order limits

Limit prices must be a multiple of tickSize; sizes (base units) a multiple of sizeStep and within minOrderSize–maxOrderSize. A null field is not enforced. A quoteAmount market buy is rounded down to whole size steps.

Fees

Takers pay takerFeeBps (equal to feeBps) of notional in the quote asset; makers pay nothing (makerFeeBps is 0). Each fill reports the fee you paid.

Balances and positions

GET /v1/portfolio/{address}

Returns equity, free collateral, balances and positions for the authenticated wallet only. The path address must match the bearer-token subject; another wallet is rejected. reserved is held by your resting limit orders (bids hold quote, asks hold base); available is what new orders, liquidity deposits and withdrawals can use. Withdrawals are deducted from total when requested. amount equals total and is kept for compatibility.

curl https://api.ignite.trade/v1/portfolio/0xYOUR_WALLET \
  -H "Authorization: Bearer $IGNITE_TOKEN"

{
  "address": "0xyour_wallet",
  "equity": 1520.4,
  "freeCollateral": 1520.4,
  "balances": [
    { "asset": "USDC", "amount": 1200, "total": 1200, "reserved": 220.86, "available": 979.14 },
    { "asset": "SOL", "amount": 2.9, "total": 2.9, "reserved": 0, "available": 2.9 }
  ],
  "positions": []
}

Place an order

POST /v1/orders
curl -X POST https://api.ignite.trade/v1/orders \
  -H "Authorization: Bearer $IGNITE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "market": "LITHO/USDT",
    "side": "buy",
    "type": "limit",
    "price": 8.70,
    "size": 1,
    "clientOrderId": "strategy-a-000001",
    "timeInForce": "GTC",
    "postOnly": false,
    "reduceOnly": false
  }'
FieldAccepted valuesRule
marketPublished market symbolRequired; must be active and trading-enabled.
sidebuy | sellRequired. Market-specific restrictions still apply.
typelimit | marketRequired. Limit orders should include price.
pricePositive numberRequired for limit orders. Must be a multiple of tickSize when the market publishes one.
sizePositive numberBase units. Required except quote-denominated market buys; sizeStep and min/max rules apply.
quoteAmountPositive numberOnly for market buys; cannot be combined with size.
clientOrderId1–128 charactersIdempotency key, unique per wallet and remembered permanently. Reusing one returns the original order and its fills; the rest of the new request is ignored. Without it, every request is a new order.
timeInForceGTC | IOC | FOKDefaults to GTC. Market orders terminate any unfilled remainder.
postOnlybooleanRejects an order that would immediately take liquidity.
reduceOnlybooleanRelevant only to supported position markets; cannot increase or flip a position.
leverage1–50Accepted and stored but ignored on spot markets: spot orders are always fully funded from your balance.
accountWallet addressOptional; if supplied it must equal the authenticated wallet.

An order the matching engine refuses comes back as HTTP 200 with accepted: false, status: "rejected" and a rejectCode with rejectReason: POST_ONLY_WOULD_CROSS or FOK_NOT_FILLED. They are sent on the placing response only; a clientOrderId replay returns the order without them.

List wallet orders

GET /v1/orders

Returns the wallet's latest 100 orders, newest first, in open and terminal states. Each order carries createdAt, updatedAt (last status or fill change), remainingSize, and every fill it took part in as taker or maker. The place-order response returns the same object as order, alongside createdAt and updatedAt.

curl https://api.ignite.trade/v1/orders \
  -H "Authorization: Bearer $IGNITE_TOKEN"

{ "orders": [{
  "id": "cmg…", "market": "SOL/USDC", "side": "sell", "type": "limit", "timeInForce": "GTC",
  "postOnly": true, "price": 110.5, "size": 2, "filledSize": 0.5, "remainingSize": 1.5,
  "status": "partially_filled", "clientOrderId": "strategy-a-000002",
  "createdAt": "2026-10-10T12:00:00.000Z", "updatedAt": "2026-10-10T12:03:10.000Z",
  "fills": [{ "id": "…", "orderId": "cmg…", "role": "maker", "price": 110.5, "size": 0.5,
              "notional": 55.25, "feeAmount": 0, "feeAsset": "USDC", "createdAt": "2026-10-10T12:03:10.000Z" }]
}] }

Cancel an order

DELETE /v1/orders/{orderId}

Only the authenticated owner can cancel a live order. Filled, rejected, expired, failed, or already-cancelled orders cannot be cancelled (ORDER_NOT_FOUND).

curl -X DELETE https://api.ignite.trade/v1/orders/ORDER_ID \
  -H "Authorization: Bearer $IGNITE_TOKEN"

Cancel all open orders

DELETE /v1/orders?market={symbol}

Cancels every open, resting and partially filled order the wallet has when the request runs, each exactly as the single cancel would; a partially filled order ends partially_filled_cancelled. The optional market filter takes the published symbol, URL-encoded. It works while trading is paused, so it can be used as an emergency stop. Orders placed after the call starts are not included; an unknown market is MARKET_NOT_FOUND.

curl -X DELETE "https://api.ignite.trade/v1/orders?market=SOL%2FUSDC" \
  -H "Authorization: Bearer $IGNITE_TOKEN"

{
  "cancelled": [
    { "id": "cmh…", "clientOrderId": "strategy-a-000002", "market": "SOL/USDC", "status": "cancelled" },
    { "id": "cmh…", "clientOrderId": null, "market": "SOL/USDC", "status": "partially_filled_cancelled" }
  ],
  "count": 2
}

Wallet fills

GET /v1/trades?market={symbol}&limit=100

Returns fills where the authenticated wallet was maker or taker, newest first. The optional limit is clamped to 1–500 and the optional market filter uses the published market symbol. Each fill has orderId (your order), side, role, notional, and the fee you paid as feeAmount in feeAsset (0 as maker).

{ "trades": [{
  "id": "…", "orderId": "cmh…", "market": "SOL/USDC", "side": "buy", "role": "taker",
  "price": 110.5, "size": 0.5, "notional": 55.25, "feeAmount": 0.05525, "feeAsset": "USDC",
  "counterparty": "0x…", "createdAt": "2026-10-10T12:03:10.000Z"
}] }

Responses and integration safety

400 Bad Request

Malformed input, invalid signature, market rule failure, insufficient balance, or an order that cannot be accepted.

401 Unauthorized

Missing, invalid, or expired bearer token. Request a new challenge and session; do not retry with the same expired token.

403 Forbidden

The requested account or object does not belong to the authenticated wallet.

404 Not Found

Unknown market or resource. Refresh the market list before retrying.

409 Conflict

Account state requires a current action, such as accepting a new terms version.

429 Too Many Requests

Honor RateLimit headers and retry only after the published reset time with exponential backoff.

503 Service Unavailable

Trading is paused (code OPERATIONAL_PAUSED). Retry later; nothing was placed.

Every error carries a stable code next to the message: { "error": string, "code": string }. Validation failures put the field details in error; unknown routes and unexpected errors use { "statusCode", "code", "error", "message" }. Branch on code, not on the message text, which may be reworded. Codes are never renamed or reused; new ones may be added. Route-specific codes keep their values: STALE_TERMS_VERSION (409), the lowercase Thanos login codes above, and Fastify FST_ERR_* codes for malformed HTTP requests such as invalid JSON.

codeHTTPMeaning
VALIDATION_ERROR400A required field is missing or a value is invalid; error holds the field details.
INVALID_ORDER400Non-positive size, a limit order without a price, post-only on a market or non-GTC order, or leverage above 100.
MARKET_NOT_FOUND400No market has this symbol (also cancel-all with an unknown market).
MARKET_NOT_TRADING400The market exists but is not accepting orders.
MARKET_PRE_OPEN400The pair has not opened: only resting limit orders that do not cross are accepted.
NO_REFERENCE_PRICE400The market has no valid mark price to value the order.
ORDER_BELOW_MIN_SIZE400Size is below minOrderSize.
ORDER_ABOVE_MAX_SIZE400Size is above maxOrderSize.
SIZE_STEP400Size is not a multiple of sizeStep.
PRICE_TICK400Price is not a multiple of tickSize.
SELL_DISABLED400Selling is disabled on this pair (buy-only launch pairs).
INSUFFICIENT_BALANCE400The available balance, after what your open orders reserve, does not cover the order and its fee.
QUOTE_AMOUNT_TOO_SMALL400A market buy by quoteAmount buys less than one size step.
NO_LIQUIDITY400A market buy by quoteAmount found no ask liquidity.
REDUCE_ONLY400A reduce-only order would open, increase or flip a position.
RISK_LIMIT400The order would breach the account margin requirements (position markets).
SETTLEMENT_FAILED400The order matched but could not be settled; nothing moved and the order is recorded as rejected.
CUSTODY_HOLD400The account is on a custody hold pending reconciliation.
ORDER_NOT_FOUND400Cancel: the order does not exist, belongs to another wallet, or is already closed.
BAD_REQUEST400Refused for a reason without a more specific code.
UNAUTHORIZED401Missing, invalid or expired bearer token.
FORBIDDEN403The token cannot do this, for example another wallet's account or an unlinked Google session.
NOT_FOUND404No such route or resource.
CONFLICT409The request conflicts with the current state.
RATE_LIMITED429Too many requests; wait for Retry-After.
RESTRICTED_JURISDICTION451Ignite is not available in your jurisdiction.
INTERNAL_ERROR500Unexpected server error.
OPERATIONAL_PAUSED503Trading is paused by the emergency kill switch; nothing was placed.
SERVICE_UNAVAILABLE503A dependency is temporarily unavailable.

Rate limits

RoutesLimit
/v1/auth/challenge and /api/auth/nonce30 per minute
/v1/auth/verify and /api/auth/verify30 per minute
/v1/market-data/*300 per minute
Orders, portfolio, trades, marketsNo per-client limit at present; keep to about 10 requests per second per session. Limits added later will be announced and sent in the same headers.

Limits count requests per bearer token (per IP without one) over a sliding 60-second window. Limited routes send RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. RateLimit-Reset is always an absolute Unix time in seconds: when the oldest request in the window expires. A 429 also sends Retry-After in seconds.

Public WebSocket streams

No authentication. Both streams push full snapshots every two seconds (not diffs); REST remains the reference.

wss://api.ignite.trade/ws

First a {"type":"snapshot","markets":[…]} message with the GET /v1/markets records, then every two seconds one {"type":"ticker","symbol","mark","index","volume24h","openInterest","change24h"} message per market.

wss://api.ignite.trade/ws/market?symbol=SOL%2FUSDC&tf=1m

Every two seconds: {"type":"market","symbol","tf","ticker","book":{"bids","asks"},"trades","candle"}. book has up to 250 {price,size} levels per side; trades holds the latest 60 {price,size,side,time}; tf is 1m, 5m, 15m, 1h, 4h, 1D, 1W or 1M.

Private order and fill streams are not available; poll GET /v1/orders and GET /v1/trades.

Admin, custody-operator, RPC credentials, internal service routes, and automation-signer details are intentionally outside this public integration contract.