API Reference
The StableSwap API is the public-facing REST service for cross-chain stablecoin swaps.
Base URL
https://api.stableswap.xyzOpenAPI Spec
Integration Flow
A typical swap involves three API calls. The diagram below shows the full sequence between the client, the API, and the source/destination chains.
1. Get a quote
POST /quote with the source token, destination token, amount, and wallet addresses. The response includes quoteId (to link the order later), minOutput, and chain-specific swap data (e.g. suiSourcePoolId, evmSourceSwapData).2. Submit the source transaction
3. Poll for completion
GET /order/:txId every few seconds using the source transaction ID. The order may take a few seconds to appear (event watcher delay). Status progresses through attesting → ready_to_fulfill → fulfilling → complete. Show the destination transaction hash from destTxId once complete.Programmatic Integration
Building a bot, CLI, or backend service that executes swaps programmatically? The Integration Guide walks through the full flow end-to-end: get a quote, map quote fields to chain-specific builder parameters, build and sign the deposit transaction, and poll for completion. Includes TypeScript code examples for all four chain families (EVM, Sui, Aptos, Solana) using the shared transaction builders from packages/transactions/.
Chains & Tokens
/chainsReturns all supported chains and their tokens. Use this to populate token selectors.
Response
{
"chains": [
{
"id": "ethereum",
"name": "Ethereum",
"tokens": [
{
"symbol": "USDC",
"name": "USD Coin",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"decimals": 6,
"isNativeUSDC": true
},
{
"symbol": "USDT",
"name": "Tether USD",
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"decimals": 6,
"isNativeUSDC": false
}
]
},
{
"id": "arbitrum",
"name": "Arbitrum",
"tokens": [...]
},
{
"id": "base",
"name": "Base",
"tokens": [...]
},
{
"id": "polygon",
"name": "Polygon",
"tokens": [...]
},
{
"id": "avalanche",
"name": "Avalanche",
"tokens": [...]
},
{
"id": "optimism",
"name": "Optimism",
"tokens": [...]
},
{
"id": "solana",
"name": "Solana",
"tokens": [...]
},
{
"id": "sui",
"name": "Sui",
"tokens": [...]
},
{
"id": "aptos",
"name": "Aptos",
"tokens": [...]
}
]
}Quoting
/quote(10 req/min)Request a swap quote. The API fans out to all registered solvers and returns the best quote (highest guaranteedMinOutput). Quotes expire after 90 seconds.
Request Body
| Field | Type | Description |
|---|---|---|
fromChain | string | Source chain ID |
toChain | string | Destination chain ID |
fromToken | string | Source token coin type (e.g. Ethereum USDT) |
toToken | string | Destination token coin type (e.g. Solana USDC) |
amount | string | Amount in smallest unit (e.g. "1000000" for 1 USDC) |
senderAddress | string? | Sender’s address on the source chain (optional) |
recipientAddress | string? | Recipient’s address on the destination chain (optional, needed for mint routing) |
slippageBps | number? | Slippage tolerance in basis points (e.g. 30 = 0.3%). Defaults to 30 if omitted. |
affiliateFeeBps | number? | Affiliate fee in basis points (e.g. 10 = 0.1%). Paid to the integrator. Defaults to 0 if omitted. |
Response
{
"quoteId": "a1b2c3d4-...",
"inputToken": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"outputToken": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"inputAmount": "1000000",
"usdcAmount": "1000000",
"estimatedOutput": "999500",
"guaranteedMinOutput": "997000",
"sourceSwapRequired": true,
"destSwapRequired": false,
"route": ["CCTP v2 Hooks", "Uniswap V3"],
"expiresAt": 1740422400000,
"fees": {
"explicit": {
"crossChainMessagingFee": "0",
"crossChainMessagingFeeSymbol": "",
"networkFee": "0.01",
"networkFeeSymbol": "ETH"
},
"implicit": {
"protocolFeeUsd": "0.00",
"cctpFeeUsd": "0.00",
"affiliateFeeUsd": "0.00"
}
}
}Key fields: minOutput, minUsdcOutput, and chain-specific swap data (e.g. suiSourcePoolId, evmSourceSwapData) are passed to the gateway contract. fees provides a customer-facing breakdown. explicit = fees paid directly (cross-chain messaging, network gas). implicit = fees reflected in output (protocol, CCTP, affiliate).
Order Tracking
Orders are created by the API when it detects SwapOrderCreated events on the source chain. After the user submits the source transaction, poll GET /order/:txId using the source transaction hash.
/order/:txIdGet the current status of an order by its source chain transaction ID. This is the primary polling endpoint to track swap progress.
Path Parameters
| Param | Type | Description |
|---|---|---|
txId | string | Source chain transaction hash/digest |
Response
{
"sourceDomain": 0,
"nonce": 352629,
"fromChain": "ethereum",
"toChain": "solana",
"status": "complete",
"sourceTxId": "0x1a2b...3c4d",
"destTxId": "4xPq...7r8s",
"recipientAddress": "7Bk9...Yz3a",
"outputToken": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"usdcAmount": "1000000",
"estimatedOutput": "999500",
"outputAmount": "999100",
"error": null,
"createdAt": 1740422300000,
"updatedAt": 1740422400000,
"numAttempts": 0
}Returns 404 if the order is not tracked.
/history?addresses=...Fetch order history for one or more wallet addresses. Pass a comma-separated list of addresses across any supported chain.
Query Parameters
| Param | Type | Description |
|---|---|---|
addresses | string | Comma-separated wallet addresses (min 10 chars each) |
Response
{
"orders": [
{
"sourceDomain": 0,
"nonce": 352629,
"fromChain": "ethereum",
"toChain": "solana",
"status": "complete",
"sourceTxId": "0x1a2b...3c4d",
"destTxId": "4xPq...7r8s",
"recipientAddress": "7Bk9...Yz3a",
"outputToken": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"usdcAmount": "1000000",
"outputAmount": "999100",
"error": null,
"createdAt": 1740422300000,
"updatedAt": 1740422400000
}
],
"failedSourceTxs": [
{
"sourceTxId": "0x5e6f...7a8b",
"fromChain": "ethereum",
"toChain": "solana",
"fromToken": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"toToken": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": "1000000",
"senderAddress": "0x9c0d...1e2f",
"recipientAddress": "7Bk9...Yz3a",
"createdAt": 1740422100000
}
]
}For Sui addresses, the API also queries recent on-chain transactions to find orders not yet matched by recipient address.
/in-flight-ordersList all currently tracked (in-flight) orders. Primarily for debugging and monitoring.
Response
{ "orders": [...], "total": 5 }/submit-failureRecord a failed source chain transaction for audit purposes. Call this when a deposit transaction fails so it shows up in the user's history.
Request Body
| Field | Type | Description |
|---|---|---|
sourceTxId | string | Source chain transaction hash/digest |
fromChain | string | Source chain ID |
toChain | string | Destination chain ID |
fromToken | string | Source token coin type (e.g. Ethereum USDT) |
toToken | string | Destination token coin type (e.g. Solana USDC) |
amount | string | Amount in smallest unit (e.g. "1000000" for 1 USDC) |
senderAddress | string? | Sender's address on the source chain (optional) |
recipientAddress | string? | Recipient's address on the destination chain (optional) |
slippageBps | number? | Slippage tolerance in basis points (e.g. 30 = 0.3%). Defaults to 30 if omitted. |
Response
{ "success": true }Health Check
/healthReturns API health status, uptime, order counts by state, quote cache size, and aggregated solver health (including gas balances).
Response
{
"status": "ok",
"uptime": { "ms": 3600000, "human": "1h 0m" },
"orders": {
"total": 12,
"attesting": 2,
"readyToFulfill": 1,
"fulfilling": 0
},
"solvers": {
"http://solver:3002": {
"status": "ok",
"uptime": { "ms": 3600000, "human": "1h 0m" },
"gas": {
"ethereum": { "balance": "1000000000000000000", "human": "1.0000 ETH" },
"arbitrum": { "balance": "500000000000000000", "human": "0.5000 ETH" },
"base": { "balance": "500000000000000000", "human": "0.5000 ETH" },
"polygon": { "balance": "10000000000000000000", "human": "10.0000 POL" },
"avalanche": { "balance": "5000000000000000000", "human": "5.0000 AVAX" },
"optimism": { "balance": "500000000000000000", "human": "0.5000 ETH" },
"solana": { "balance": "5000000000", "human": "5.0000 SOL" },
"sui": { "balance": "2000000000", "human": "2.0000 SUI" },
"aptos": { "balance": "500000000", "human": "5.0000 APT" }
}
}
}
}Status is "degraded" if any registered solver is unreachable.
Order State Machine
Every order progresses through a deterministic state machine. The API drives all transitions — clients only need to poll GET /order/:txId and display the current state.
| State | Description | Transition |
|---|---|---|
attesting | Source event detected, polling Circle API for CCTP V2 attestation (~30s) | Attestation ready → ready_to_fulfill |
ready_to_fulfill | CCTP V2 attestation received, order assigned to solver | Pushed to solver → fulfilling |
fulfilling | Solver executing fulfill_order() on destination chain | Confirmed on-chain → complete |
complete | API verified fulfillment on destination chain | Terminal state |
failed | Solver timed out or transaction reverted | Reassign or user fallback withdrawal |
Error Handling
All endpoints return JSON error responses with an error field:
{ "error": "Missing required fields: fromToken, toToken, amount" }| Status | Meaning |
|---|---|
400 | Bad request — missing or invalid parameters |
404 | Not found — order or resource does not exist |
429 | Rate limited — too many requests, try again later |
451 | Address restricted |
500 | Internal error — server-side failure |
503 | Service unavailable — solver or external API unreachable |