Backend API Quickstart
Make your first authenticated API call to Diffusal — REST, WebSocket, and meta-transactions for API integrators
This guide is for API integrators, bot operators, and backend developers building programmatic integrations with Diffusal. You will make a health check, discover markets, authenticate via SIWE, make your first authenticated request, connect to public and private WebSocket streams, and use the meta-transaction relay to submit a trade without paying gas directly.
This guide is not for trading app end-users — the web app handles authentication and API calls automatically.
Prerequisites
curlinstalledwebsocatinstalled —brew install websocat(macOS) or see websocat releases- A funded wallet on Monad Testnet (chain ID 10143) — see the Testnet Guide for faucet instructions
Your First API Call — Linear Walkthrough
Step 1: Health Check
Confirm the testnet API is reachable:
curl -s https://api.testnet.diffusal.xyz/api/health-signalExpected output:
{ "status": "ok", "timestamp": "2026-01-01T00:00:00.000Z" }If you see a connection error, check that https://api.testnet.diffusal.xyz is reachable from your network.
Step 2: Discover Markets
Fetch the current list of trading pairs. No authentication needed for public endpoints:
curl -s https://api.testnet.diffusal.xyz/api/markets/pairs | jq .Expected output (truncated):
[
{
"pairId": "0x7020b52841bb268cbc78137a54d4bf1f5305eed1039fb5d003ba95b8ededc46c",
"symbol": "ETH-USDT",
"underlying": "ETH",
"quote": "USDT"
},
{
"pairId": "0xa92bcb5bc51aa5535ed0cc3f522992dd9a6fb2e8dd6dcf484705d93eb3cd167a",
"symbol": "BTC-USDT",
"underlying": "BTC",
"quote": "USDT"
}
]Save a pair ID for use in later steps:
export PAIR_ID="0x7020b52841bb268cbc78137a54d4bf1f5305eed1039fb5d003ba95b8ededc46c"Step 3: Authenticate
Diffusal uses Better Auth SIWE for programmatic authentication. The three-step flow is: call POST /api/auth/siwe/nonce to request a nonce, sign a SIWE message, then call POST /api/auth/siwe/verify to verify and create a session.
a) Request a nonce
curl -s -X POST https://api.testnet.diffusal.xyz/api/auth/siwe/nonce \
-H 'Content-Type: application/json' \
-d '{"walletAddress":"0xYOUR_ADDRESS","chainId":10143}'Expected output:
{ "nonce": "a3f2c91d8b4e5072" }b) Construct the SIWE message
Build an EIP-4361 message using the nonce. Replace YOUR_ADDRESS and NONCE with actual values:
api.testnet.diffusal.xyz wants you to sign in with your Ethereum account:
YOUR_ADDRESS
Sign in to Diffusal
URI: https://api.testnet.diffusal.xyz
Version: 1
Chain ID: 10143
Nonce: NONCE
Issued At: 2026-01-01T00:00:00.000ZSign this message with your wallet private key.
c) Verify the SIWE signature
curl -s -X POST https://api.testnet.diffusal.xyz/api/auth/siwe/verify \
-H 'Content-Type: application/json' \
-d '{
"message": "api.testnet.diffusal.xyz wants you to sign in with your Ethereum account:\nYOUR_ADDRESS\n\nSign in to Diffusal\n\nURI: https://api.testnet.diffusal.xyz\nVersion: 1\nChain ID: 10143\nNonce: NONCE\nIssued At: 2026-01-01T00:00:00.000Z",
"signature": "0xYOUR_SIGNATURE",
"walletAddress": "0xYOUR_ADDRESS",
"chainId": 10143
}'Expected output:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user_abc123",
"address": "0xYOUR_ADDRESS"
}
}d) Save the bearer token
export TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Nonces expire after 5 minutes. For the full authentication reference including token format and session lifecycle, see Authentication.
Step 4: Make an Authenticated Request
With your bearer token, call an authenticated endpoint. Fetch your account's portfolio list:
curl -s https://api.testnet.diffusal.xyz/api/portfolio/list \
-H "Authorization: Bearer $TOKEN"Expected output:
{
"portfolios": [
{
"id": 0,
"owner": "0xYOUR_ADDRESS",
"cashBalance": "1000000000000000000000",
"marginUsed": "0",
"createdAt": "2026-01-01T00:00:00.000Z"
}
]
}If you receive a 401 Unauthorized, your token has expired — repeat Step 3 to get a new one.
Step 5: WebSocket Basics
Connect to the public WebSocket stream for real-time market data. No authentication required.
a) Subscribe to a channel
websocat "wss://api.testnet.diffusal.xyz/ws/public"Once connected, send a subscribe message:
{
"method": "SUBSCRIBE",
"params": ["global@rfq"]
}Expected acknowledgement:
{
"result": null,
"id": null
}You will now receive live RFQ auction events as they are created and resolved:
{
"channel": "global@rfq",
"data": {
"type": "rfq_created",
"requestId": "rfq_01j...",
"pairId": "0x7020b52841bb268cbc78137a54d4bf1f5305eed1039fb5d003ba95b8ededc46c",
"expiresAt": "2026-01-01T00:00:30.000Z"
}
}b) Index price feed
To receive the spot price and implied volatility for a pair, subscribe to the index channel:
{
"method": "SUBSCRIBE",
"params": ["eth-usdt@index"]
}Expected stream events:
{
"channel": "eth-usdt@index",
"data": {
"price": "2000.41",
"volatility": "0.65",
"timestamp": "2026-01-01T00:00:01.000Z"
}
}For the full channel list, control message format, and the complete AsyncAPI spec, see WebSocket Reference.
Step 6: Private WebSocket
The private WebSocket stream delivers authenticated account events — fills, positions, and portfolio changes.
websocat "wss://api.testnet.diffusal.xyz/ws/private"Send an AUTH message as the first frame after connecting:
{
"type": "AUTH",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Expected auth acknowledgement:
{ "type": "AUTH_SUCCESS", "userId": "user_abc123" }Subscribe to account events:
{
"method": "SUBSCRIBE",
"params": ["account@fills", "account@positions", "account@portfolios"]
}Example fill event:
{
"channel": "account@fills",
"data": {
"tradeId": "trd_01j...",
"symbol": "eth-2000-c-1780000000",
"side": "buy",
"size": "1000000000000000000",
"price": "50000000000000000",
"fee": "500000000000000",
"timestamp": "2026-01-01T00:00:05.000Z"
}
}For the full private channel reference, see Private WebSocket.
Meta-Transaction (EIP-712 Relay)
Diffusal supports a two-phase meta-transaction flow for order placement. The API relays your order to the chain — you sign the typed data, the backend pays gas. This means your wallet does not need MON for routine trading operations.
The flow works in two steps:
Step 1: Request the typed data to sign
Send POST /api/mm/orders/place without a signature to receive the EIP-712 typed data:
curl -s -X POST https://api.testnet.diffusal.xyz/api/mm/orders/place \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{
"seriesId": "0xACTIVE_SERIES_ID",
"isBuy": true,
"size": "0.1",
"price": "0.05"
}'Expected output (typed data to sign):
{
"mode": "typed_data",
"operation": "place_order",
"contractAddress": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa",
"typedData": {
"domain": {
"name": "DiffusalOptionsOrderBook",
"version": "1",
"chainId": 10143,
"verifyingContract": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa"
},
"types": {
"PlaceOrder": [
{ "name": "seriesId", "type": "bytes32" },
{ "name": "portfolioId", "type": "uint256" },
{ "name": "isBuy", "type": "bool" },
{ "name": "tick", "type": "uint32" },
{ "name": "size", "type": "uint128" },
{ "name": "expiry", "type": "uint32" },
{ "name": "nonce", "type": "uint256" },
{ "name": "deadline", "type": "uint256" }
]
},
"primaryType": "PlaceOrder",
"message": {
"seriesId": "0xACTIVE_SERIES_ID",
"portfolioId": "0",
"isBuy": true,
"tick": 500,
"size": "100000000000000000",
"expiry": 1780000000,
"nonce": "1776045156600",
"deadline": "1776045456"
}
}
}For MMM wallets, the unsigned request can omit portfolioId. The API normalizes to the MMM cash convention and returns the correct typed data while the internal trading portfolio stays auto-routed by series.
Step 2: Sign the typed data
Sign the typedData with your wallet using EIP-712 (eth_signTypedData_v4).
Step 3: Submit the signed order
Send the same request body with the signature field added:
curl -s -X POST https://api.testnet.diffusal.xyz/api/mm/orders/place \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{
"seriesId": "0xACTIVE_SERIES_ID",
"portfolioId": 0,
"isBuy": true,
"tick": 500,
"size": "100000000000000000",
"expiry": 1780000000,
"nonce": "1776045156600",
"deadline": "1776045456",
"signature": "0xYOUR_EIP712_SIGNATURE"
}'Expected output:
{
"mode": "relay",
"operation": "place_order",
"contractAddress": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa",
"relay": {
"to": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa",
"calldata": "0xa3b4c5d6...",
"value": "0",
"chainId": 10143,
"functionName": "placeOrderSigned"
}
}Meta-transactions mean your wallet does not need MON for gas on routine order placement. The Diffusal relayer submits the transaction to Monad on your behalf. You still need MON to interact directly with contracts (deposits, withdrawals, nonce management).
Replace an Order
Use POST /api/mm/orders/replace to atomically cancel a resting order and register its replacement in one transaction. The request body carries a cancel object (with the old orderId) and a place object shaped exactly like the single-place body above. As with single place, the two-phase flow applies: post unsigned to receive typed data, sign both halves, then post again with both signatures attached.
Step 1: Request typed data
curl -s -X POST https://api.testnet.diffusal.xyz/api/mm/orders/replace \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{
"cancel": {
"orderId": "0xOLD_ORDER_ID",
"nonce": "1776045156601",
"deadline": "1776045456"
},
"place": {
"seriesId": "0xACTIVE_SERIES_ID",
"isBuy": true,
"size": "0.1",
"price": "0.055"
}
}'Expected output (typed data bundle to sign):
{
"mode": "typed_data",
"operation": "replace_order",
"contractAddress": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa",
"replacement": {
"oldOrderId": "0xOLD_ORDER_ID",
"cancelTypedData": {
"primaryType": "CancelOrder",
"message": {
"orderId": "0xOLD_ORDER_ID",
"nonce": "1776045156601",
"deadline": "1776045456"
},
"...": "..."
},
"placeTypedData": {
"primaryType": "PlaceOrder",
"message": {
"seriesId": "0xACTIVE_SERIES_ID",
"portfolioId": "0",
"isBuy": true,
"tick": 550,
"size": "100000000000000000",
"expiry": 1780000000,
"nonce": "1776045156602",
"deadline": "1776045456"
},
"...": "..."
}
}
}Step 2: Sign both halves
Sign cancelTypedData and placeTypedData separately with the maker wallet (eth_signTypedData_v4). replace_order requires both signatures or neither — partial submissions are rejected with OrderValidationError.
Step 3: Submit the signed replace
Resend the same request with cancel.signature and place.signature filled in. The backend returns mode: "relay" with calldata for the replaceOrderSigned entry point:
{
"mode": "relay",
"operation": "replace_order",
"contractAddress": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa",
"relay": {
"to": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa",
"calldata": "0xa3b4c5d6...",
"value": "0",
"chainId": 10143,
"functionName": "replaceOrderSigned"
}
}Batch Place Orders
Use POST /api/mm/orders/place-batch to submit several PlaceOrder entries as a single all-or-nothing transaction. The request body is { "orders": [...] } where each entry matches the single-place schema. All entries must uniformly include or uniformly omit the signature field — mixed batches are rejected.
Step 1: Request typed-data bundles
curl -s -X POST https://api.testnet.diffusal.xyz/api/mm/orders/place-batch \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{
"orders": [
{ "seriesId": "0xACTIVE_SERIES_ID", "isBuy": true, "size": "0.1", "price": "0.049" },
{ "seriesId": "0xACTIVE_SERIES_ID", "isBuy": false, "size": "0.1", "price": "0.051" }
]
}'Expected output (one typed-data payload per order):
{
"mode": "typed_data",
"operation": "place_orders_batch",
"contractAddress": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa",
"placements": [
{
"typedData": {
"primaryType": "PlaceOrder",
"message": { "nonce": "1776045156610", "...": "..." },
"...": "..."
}
},
{
"typedData": {
"primaryType": "PlaceOrder",
"message": { "nonce": "1776045156611", "...": "..." },
"...": "..."
}
}
],
"count": 2
}Step 2: Sign each typed-data entry
Sign each placements[i].typedData separately with the maker wallet.
Step 3: Submit the signed batch
Resend the batch with a signature attached to every order in the orders array. The relayer emits a single batchPlaceOrders transaction:
{
"mode": "relay",
"operation": "place_orders_batch",
"contractAddress": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa",
"relay": {
"to": "0x7b0990d6ed456eacf04043063ede7c2ae6dd74aa",
"calldata": "0xa3b4c5d6...",
"value": "0",
"chainId": 10143,
"functionName": "batchPlaceOrders"
},
"count": 2
}Batch Cancel and Batch Replace
POST /api/mm/orders/cancel-batch and POST /api/mm/orders/replace-batch follow the same two-phase pattern. The cancel-batch body is { "cancellations": [{ "orderId": "0x...", "nonce": "...", "deadline": "..." }, ...] } and relays via batchCancelOrders. The replace-batch body is { "replacements": [{ "cancel": {...}, "place": {...} }, ...] } and relays via batchReplaceOrders. Same uniform-signature rule applies: every entry must include signatures or every entry must omit them — for replace-batch, each entry must also include both its cancel.signature and place.signature, or neither.
Common Patterns
Error handling
API errors use a consistent { "error": string } shape:
curl -s https://api.testnet.diffusal.xyz/api/portfolio/listExpected on missing auth:
{ "error": "Unauthorized" }Common status codes:
| Status | Meaning |
|---|---|
400 | Bad request — check request body schema |
401 | Unauthorized — token missing or expired |
403 | Forbidden — valid token but insufficient permissions |
404 | Not found — resource does not exist |
429 | Rate limit exceeded — back off and retry |
500 | Server error — contact support if persistent |
For the full error code reference, see Error Codes.
Rate limits
The API enforces per-IP and per-user rate limits. See Rate Limits for the exact limits and backoff guidance.
Pagination
Endpoints returning lists support cursor-based pagination via limit and cursor query parameters:
curl -s "https://api.testnet.diffusal.xyz/api/account/trades?limit=20" \
-H "Authorization: Bearer $TOKEN"The response includes a nextCursor field when more results are available.
See Also
- On-Chain Guide — direct contract interaction via Foundry Cast
- MM On-Chain Guide — market maker integration: auth, quoting, monitoring
- API Reference — full endpoint documentation
- WebSocket Reference — channel list and control message format
- Error Codes — complete error code reference