API Reference

The StableSwap API is the public-facing REST service for cross-chain stablecoin swaps.

Base URL

https://api.stableswap.xyz

OpenAPI Spec

Machine-readable API definition: public.yaml

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.

ClientAPISource ChainDest ChainPOST /quotequoteId, minOutput, ...sign & submit gateway txtxIdSwapOrderCreated eventAPI creates orderpoll Circle attestationsolver: fulfill_order()GET /order/:txId{ status: complete }

1. Get a quote

Call 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

Build and sign the gateway transaction on the source chain using the quote fields. The API creates orders from on-chain events — no separate submit call is needed.

3. Poll for completion

Poll 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

GET/chains

Returns 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

POST/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

FieldTypeDescription
fromChainstringSource chain ID
toChainstringDestination chain ID
fromTokenstringSource token coin type (e.g. Ethereum USDT)
toTokenstringDestination token coin type (e.g. Solana USDC)
amountstringAmount in smallest unit (e.g. "1000000" for 1 USDC)
senderAddressstring?Sender’s address on the source chain (optional)
recipientAddressstring?Recipient’s address on the destination chain (optional, needed for mint routing)
slippageBpsnumber?Slippage tolerance in basis points (e.g. 30 = 0.3%). Defaults to 30 if omitted.
affiliateFeeBpsnumber?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.

GET/order/:txId

Get the current status of an order by its source chain transaction ID. This is the primary polling endpoint to track swap progress.

Path Parameters

ParamTypeDescription
txIdstringSource 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.

GET/history?addresses=...

Fetch order history for one or more wallet addresses. Pass a comma-separated list of addresses across any supported chain.

Query Parameters

ParamTypeDescription
addressesstringComma-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.

GET/in-flight-orders

List all currently tracked (in-flight) orders. Primarily for debugging and monitoring.

Response

{ "orders": [...], "total": 5 }
POST/submit-failure

Record 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

FieldTypeDescription
sourceTxIdstringSource chain transaction hash/digest
fromChainstringSource chain ID
toChainstringDestination chain ID
fromTokenstringSource token coin type (e.g. Ethereum USDT)
toTokenstringDestination token coin type (e.g. Solana USDC)
amountstringAmount in smallest unit (e.g. "1000000" for 1 USDC)
senderAddressstring?Sender's address on the source chain (optional)
recipientAddressstring?Recipient's address on the destination chain (optional)
slippageBpsnumber?Slippage tolerance in basis points (e.g. 30 = 0.3%). Defaults to 30 if omitted.

Response

{ "success": true }

Health Check

GET/health

Returns 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.

attestingready_to_fulfillfulfillingcompletefailedfallbackattestedpushed to solverconfirmed on-chaintimeout / errorreassign24h expiredAPI verifies fulfillment on-chain. Fallback: user withdraws after 24h via withdraw_fallback()
StateDescriptionTransition
attestingSource event detected, polling Circle API for CCTP V2 attestation (~30s)Attestation ready → ready_to_fulfill
ready_to_fulfillCCTP V2 attestation received, order assigned to solverPushed to solver → fulfilling
fulfillingSolver executing fulfill_order() on destination chainConfirmed on-chain → complete
completeAPI verified fulfillment on destination chainTerminal state
failedSolver timed out or transaction revertedReassign or user fallback withdrawal

Error Handling

All endpoints return JSON error responses with an error field:

{ "error": "Missing required fields: fromToken, toToken, amount" }
StatusMeaning
400Bad request — missing or invalid parameters
404Not found — order or resource does not exist
429Rate limited — too many requests, try again later
451Address restricted
500Internal error — server-side failure
503Service unavailable — solver or external API unreachable