Stableyard Intent Engine

Cross-chain intent execution by Stableyard. Get quotes, create orders, and track status across 11 networks including Solana, Movement, Tron, HyperEVM, and Tempo. The engine automatically selects the best route — no provider selection needed. All endpoints are prefixed with /v1.

Base URLhttps://routing-api.stableyard.fi
Interactive API Reference: Browse every endpoint with typed request/response schemas and try calls directly at https://routing-api.stableyard.fi/reference. Machine-readable OpenAPI 3.1 spec at /openapi.json — drop it into Postman, Insomnia, or auto-generate a typed client.
Contents

Quick Start

Three API calls to bridge tokens cross-chain:

1

Get a quote

POST /v1/quote with source/dest chain, token, amount

2

Create an order

POST /v1/order — returns deposit address or pre-built transaction calldata

3

Execute deposit & track

Broadcast the transaction, then poll GET /v1/order/:id for status

# 1. Get quote
curl -X POST https://routing-api.stableyard.fi/v1/quote \
  -H "Content-Type: application/json" \
  -d '{
    "sourceChainId": 1,
    "destChainId": 8453,
    "sourceToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "sourceAmount": "1000000000",
    "recipient": "0xYourAddress"
  }'

# 2. Create order (use quoteId from step 1)
curl -X POST https://routing-api.stableyard.fi/v1/order \
  -H "Content-Type: application/json" \
  -d '{
    "sourceChainId": 1,
    "destChainId": 8453,
    "sourceToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "sourceAmount": "1000000000",
    "recipient": "0xYourAddress"
  }'

# 3. Track status
curl https://routing-api.stableyard.fi/v1/order/0xOrderId

Authentication

Public endpoints (quote, order, status) require no authentication. For enterprise access with higher rate limits and partner tracking, include your API key in the X-API-Key header.

curl -H "X-API-Key: your-api-key" https://routing-api.stableyard.fi/v1/quote ...

Rate limits: 10 req/min for order creation, 100 req/min globally. Enterprise keys get higher limits.

Configuration

Portfolio

Returns stablecoin + native balances for a wallet, with USD valuation and a per-balance eligible flag for a target spend amount. Address family is auto-detected by format — one uniform interface for all three ecosystems.

Use this to power a “pay $X” picker: send the user's wallet, get back ranked balances they can use to settle the payment. Results are sorted eligible-first, then USD desc, so balances[0] is the default option.

Query Parameters
addressstringRequired

Wallet address. Family auto-detected:

  • 0x + 40 hex → EVM (fans out across configured chains, 1 Multicall3 per chain)
  • 0x + 41–64 hex → Movement (Aptos /view: USDCx + APT)
  • base58 32–44 chars → Solana (JSON-RPC batch: getBalance + per-mint getTokenAccountsByOwner)

chainIdsstring

CSV of EVM chain IDs to query (e.g. 1,8453,42161,137). Ignored for Solana/Movement. Defaults to all configured EVM chains.

minUsdnumber

Eligibility threshold in USD decimal (e.g. 10 = $10). When set, the eligible flag is true only for stablecoin balances whose usdValue ≥ threshold. Native balances are always eligible: false when minUsd is set (no embedded price feed — frontend should price separately if needed).

Examples
# EVM — Vitalik's wallet, narrow to 4 chains, $10 minimum
curl "https://routing-api.stableyard.fi/v1/portfolio?address=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&chainIds=1,8453,42161,137&minUsd=10"

# Solana — USDC + USDT + SOL on a wallet
curl "https://routing-api.stableyard.fi/v1/portfolio?address=H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS&minUsd=10"

# Movement — USDCx + MOVE
curl "https://routing-api.stableyard.fi/v1/portfolio?address=0x3f3b8574389d713aeaadf1e603cb2363d3ba5e1cce4807d8f1280df1dfad4f76"
Response
{
  "success": true,
  "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
  "family": "evm",                       // "evm" | "solana" | "movement"
  "chains": [1, 8453, 42161, 137],       // chain IDs queried
  "minUsdRaw6": "10000000",              // USD-6 (null when no threshold)
  "totalUsdRaw6": "784392530",
  "totalUsd": "784.39",
  "eligibleCount": 5,
  "balances": [                          // sorted: eligible first, then USD desc
    {
      "chainId": 1,
      "chainName": "eth",
      "family": "evm",
      "token": {
        "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
        "symbol": "USDT",
        "name": "Tether USD",
        "decimals": 6,
        "logoURL": "https://…",
        "isNative": false,
        "isStablecoin": true
      },
      "rawBalance": "290246219",
      "formattedBalance": "290.246219",
      "usdValue": "290246219",           // USD-6 (1:1 from stablecoin amount)
      "eligible": true
    },
    {
      "chainId": 8453,
      "chainName": "base",
      "family": "evm",
      "token": {
        "address": "0x0000000000000000000000000000000000000000",
        "symbol": "ETH",
        "name": "Ethereum",
        "decimals": 18,
        "logoURL": "https://…",
        "isNative": true,
        "isStablecoin": false
      },
      "rawBalance": "3121940566764650005",
      "formattedBalance": "3.121940566764650005",
      "usdValue": null,                  // natives: no embedded price feed
      "eligible": false                  // ineligible when minUsd is set
    }
  ],
  "errors": [                            // per-chain failures (don't abort the whole call)
    // { "chainId": 56, "reason": "no rpc / viem chain mapping" }
  ],
  "meta": { "version": "v1", "timestamp": 1779010000000, "requestId": "..." }
}
Notes
  • 1 RPC per EVM chain via Multicall3 (aggregate3 + getEthBalance) — N stablecoins + native packed into a single eth_call.
  • Solana: JSON-RPC batch (getBalance + per-mint getTokenAccountsByOwner). Uses mint filter, not programId, so it works on public RPCs that block programId queries.
  • Movement: two parallel /view calls (USDCx via xReserve module, MOVE via 0x1::coin::balance).
  • Per-chain failures are isolated into errors[] — one bad RPC doesn't fail the whole request.
  • Stablecoin USD value is derived 1:1 from raw amount / decimals (handles BSC 18-decimal USDC, etc). Native USD value is intentionally null.

Get Quote

Returns the best available route for a cross-chain transfer. The engine automatically queries multiple liquidity sources and returns the optimal rate. Handles direct bridges, swaps, and composite routes (swap + bridge + swap) automatically.

Request Body
sourceChainIdnumberRequired

Source chain ID (e.g. 8453 for Base, 10001 for Bitcoin, 10002 for Movement, 10103 for Solana)

destChainIdnumberRequired

Destination chain network ID (e.g. 42161 for Arbitrum)

sourceTokenstringRequired

Source token contract address

destTokenstringRequired

Destination token contract address

sourceAmountstring

Amount to send in raw units. Required for exact_input (default mode)

destAmountstring

Desired output in raw units. Required for exact_output mode

modestring

Quote mode: "exact_input" (default) or "exact_output"

recipientstringRequired

Recipient address on destination chain

slippageBpsnumber

Slippage tolerance in basis points. Default: 50 (0.5%)

providersstring[]

Filter to specific providers. E.g. ["relay", "intent"]. Omit to query all

// Exact Input (default) — "I want to send 500 USDC"
{
  "sourceChainId": 42161,
  "destChainId": 8453,
  "sourceToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
  "destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "sourceAmount": "500000000",
  "recipient": "0xYourAddress"
}

// Exact Output — "I want to receive exactly 500 USDC"
{
  "sourceChainId": 42161,
  "destChainId": 8453,
  "sourceToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
  "destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "destAmount": "500000000",
  "mode": "exact_output",
  "recipient": "0xYourAddress"
}

// Response — same action envelope as /v1/order, with concrete-execution fields nulled
// (transactions / address / approval don't exist until you create the order).
{
  "success": true,
  "quoteId": "0x...",
  "expiresAt": 1778213481294,        // unix ms — 20s lock window
  "expiresIn": 19,                    // seconds remaining
  "mode": "exact_input",
  "routeType": "direct",
  "isComposite": false,
  "tokenIn": {
    "address": "0xaf88...5831", "symbol": "USDC", "decimals": 6,
    "amount": { "raw": "500000000", "formatted": "500.000000", "usd": "500.00" },
    "chain": { "chainId": 42161, "name": "arbitrum", "displayName": "Arbitrum One" }
  },
  "tokenOut": {
    "address": "0x8335...2913", "symbol": "USDC", "decimals": 6,
    "amount": { "raw": "499675270", "formatted": "499.675270", "usd": "499.68" },
    "minimumReceived": { "raw": "497176894", "formatted": "497.176894" },
    "chain": { "chainId": 8453, "name": "base", "displayName": "Base" }
  },
  "fees": {
    "platformFee": { "raw": "0", "formatted": "0.00", "usd": "0.00" },
    "solverIncentive": { "raw": "0", "formatted": "0.00", "usd": "0.00" },
    "networkFee": { "raw": "324730", "formatted": "0.32", "usd": "0.32" },
    "total":      { "raw": "324730", "formatted": "0.32", "usd": "0.32" }
  },
  "exchangeRate": { "rate": "0.99935054", "display": "1 USDC = 0.99935054 USDC", "inverse": "1.00064988" },
  "priceImpact": { "percent": "0.0600", "usd": "0.32", "severity": "low" },
  "estimatedTime": { "seconds": 15, "display": "~15 seconds" },
  "route": {
    "provider": "relay",
    "providerDisplayName": "Intent Engine",
    "isNative": false,
    "executionMode": "standard",
    "userSteps": 1,
    "tierLabel": "Standard"
  },
  "slippage": { "perSwapBps": 50, "totalBps": 50, "percent": "0.50" },
  "action": {                         // preview — same shape as POST /v1/order's action
    "kind": "deposit_address",        // | "gateway" | "transaction"
    "subtype": "relay",
    "provider": "relay",
    "providerDisplayName": "Intent Engine",
    "chainId": 42161,
    "chain": { /* … */ },
    "token": { /* … */ },
    "amount": { /* … */ },
    "instructions": [ /* … */ ],
    "requirements": { "needsApproval": false, "transactionCount": 0,
      "estimatedSeconds": 15, "gaslessAvailable": false, "userActionRequired": "send_to_address" },
    "address": null,                  // null on quote — allocated when you POST /v1/order
    "transactions": null,
    "expiresAt": null, "providerRequestId": null, "gasless": null
  },
  "alternatives": [ /* ranked sibling routes */ ]
}

Create Order

quoteId vs orderId: they hold the same 32-byte value — either can be passed to GET /v1/order/:id or POST /v1/order/:id/submit-tx. The field name just reflects lifecycle: quoteId is returned from POST /v1/quote (pre-deposit); orderId appears on POST /v1/order responses once an Order record exists. If your code checks one, use order.orderId || order.quoteId for safety.

Creates an order using the best available route. The response always carries a single action envelope describing exactly what the user must do next — discriminated on action.kind (gateway | transaction | deposit_address). Provider selection is automatic; pass preferredProvider only when you must pin one.

Request Body
sourceChainIdnumberRequired

Source chain network ID

destChainIdnumberRequired

Destination chain network ID

sourceTokenstringRequired

Source token address

destTokenstringRequired

Destination token address

sourceAmountstring

Amount to send in raw units. Required for exact_input (default)

destAmountstring

Desired output in raw units. Required for exact_output mode

modestring

Quote mode: "exact_input" (default) or "exact_output"

recipientstringRequired

Recipient address on destination chain

slippageBpsnumber

Slippage in bps. Route-aware defaults (your explicit value wins if higher): direct/swap_only=50, source_swap_bridge/bridge_dest_swap=100, full_composite=150, bridge_dest_bridge=50, privacy_transfer=0

executionModestring

Execution mode: "standard" (wallet/gateway/transaction flow) or "deposit_address" (single-transfer deposit-address UX). "boosted" is accepted as a legacy alias, but new integrations should send "deposit_address". Default: standard

preferredProviderstring

Force a specific provider (e.g. "relay", "intent"). Omit for best rate

providersstring[]

Filter providers. E.g. ["relay", "intent"]. Omit to query all

quoteIdstring

Lock to a previously-issued quote (pins price + provider). Single-use, 20s window

callDatastring

Action intent calldata (DeFi actions). Forces native solver

privateboolean

Hide order from public list and mask details. Default: false

sourceAddressstring

Required for Tron source quotes/orders. Use the sender's T-prefix Tron address.

TypeScript Interfaces
type ChainInfo = {
  chainId: number;
  networkChainId?: number; // legacy alias; use chainId as source of truth
  name: string;
  displayName?: string;
  baseToken?: string;
  logoURL?: string;
  explorerUrl?: string;
};

type ActionToken = { address: string; symbol: string; decimals: number; logoURL: string };
type ActionAmount = { raw: string; formatted: string; usd: string };

type ActionTransaction = {
  stepNumber: number;          // 1-based; first signed tx is 1
  to: string;
  data: string;                // hex calldata
  value: string;               // wei as string
  chainId: number;
  gasLimit?: string;
  description: string;
};

type ActionApproval = {
  needed: true;
  tokenAddress: string;
  spender: string;
  amount: string;
  transaction: ActionTransaction;
};

type ActionRequirements = {
  needsApproval: boolean;
  transactionCount: number;    // 0 for plain transfer flows
  estimatedSeconds: number;
  gaslessAvailable: boolean;
  userActionRequired: 'sign_tx' | 'send_to_address' | 'none';
};

type ActionGasless = { supported: true; endpoint: string; description: string };
type ActionPaymentRequest = {
  qrUri: string | null;
  standard: 'eip681' | 'solana_pay' | 'bitcoin_uri' | 'address_only' | null;
  displayAmount: string;
  rawAmount: string;
  amountIsExact: boolean;
  copyAddress: string | null;
  copyAmount: string;
};

type ActionBase = {
  provider: string;
  providerDisplayName: string;
  chainId: number;
  chain: ChainInfo;
  token: ActionToken;
  amount: ActionAmount;
  instructions: string[];
  requirements: ActionRequirements;
};

// 1. Gateway — user signs [approve, gateway.deposit()] on source chain
export type GatewayAction = ActionBase & {
  kind: 'gateway';
  subtype: 'evm' | 'movement';
  gatewayAddress: string;
  transactions: ActionTransaction[] | null;   // null on quote preview
  approval: ActionApproval | null;
  movePayload: { function: string; typeArguments: string[]; functionArguments: unknown[] } | null;
  permitSupported: boolean;
  gasless: ActionGasless | null;
};

// 2. Transaction — user signs N pre-built txs (xReserve, CCTP, atomic swap+bridge, …)
export type TransactionAction = ActionBase & {
  kind: 'transaction';
  subtype: 'cctp' | 'xreserve' | 'atomic_swap_bridge' | 'movement_v2' | 'composite_bridge' | 'same_chain_swap';
  transactions: ActionTransaction[] | null;
  approval: ActionApproval | null;
  movePayload: { function: string; typeArguments: string[]; functionArguments: unknown[] } | null;
  solanaParams: Record<string, unknown> | null;  // Solana CCTP only
};

// 3. Deposit-address — user sends a plain ERC-20 transfer to action.address
export type DepositAddressAction = ActionBase & {
  kind: 'deposit_address';
  subtype: 'intent_boost' | 'intent_movement_managed' | 'intent_solana_managed'
         | 'cctp_xreserve_boost' | 'cctp_xreserve_managed'
         | 'relay' | 'near' | 'tron_bridge';
  address: string | null;                        // allocated at order time, null on quote
  transactions: ActionTransaction[] | null;      // optional pre-built transfer calldata
  expiresAt: number | null;                      // unix ms
  providerRequestId: string | null;
  paymentRequest: ActionPaymentRequest | null;   // QR/copy metadata; prefer this over constructing URIs yourself
  gasless: ActionGasless | null;
};

export type OrderAction = GatewayAction | TransactionAction | DepositAddressAction;

export type OrderResponse = {
  success: true;
  orderId: string;
  quoteId: string;                               // same value as orderId
  status: 'awaiting_deposit' | 'ready_to_execute' | 'awaiting_source_swap';
  routeType: 'direct' | 'swap_only' | 'source_swap_bridge'
           | 'bridge_dest_swap' | 'full_composite'
           | 'privacy_transfer' | 'bridge_dest_bridge';
  isComposite: boolean;
  action: OrderAction;
  // alternatives, quote, route, recipient, slippage, timestamps, meta also present
};
Integration — switch on action.kind
async function execute(order: OrderResponse, wallet: Wallet) {
  const a = order.action;

  switch (a.kind) {
    case 'gateway': {
      // EVM: sign approve, then gateway.deposit(). Movement: send a.movePayload via wallet.
      if (a.subtype === 'movement' && a.movePayload) {
        return wallet.signAndSubmitMove(a.movePayload);
      }
      for (const tx of a.transactions ?? []) {
        const hash = await wallet.sendTransaction(tx);
        await wallet.waitForReceipt(hash);
      }
      return;
    }

    case 'transaction': {
      // xReserve / CCTP / atomic_swap_bridge — sign approval (if any) then transactions in order.
      if (a.approval) await wallet.sendTransaction(a.approval.transaction);
      for (const tx of a.transactions ?? []) {
        await wallet.sendTransaction(tx);
      }
      return;
    }

    case 'deposit_address': {
      // Plain transfer to a.address. Use a.paymentRequest.qrUri for QR if present.
      if (a.transactions?.[0]) {
        await wallet.sendTransaction(a.transactions[0]);
      } else {
        await wallet.transferERC20(a.token.address, a.address!, a.amount.raw);
      }
      return;
    }
  }
}
Single-transfer UX: pass executionMode: "deposit_address" to hard-require a deposit_address action. Routes that can't serve deposit-address UX are rejected with BOOST_NOT_AVAILABLE — you never see a multi-tx shape you didn't ask for.boosted remains a legacy alias.
Sample responses (real shapes — irrelevant fields trimmed)
// ─── action.kind = "gateway" ─────────────────────────────────────────────
// User signs [approve, gateway.deposit()]. action.transactions has both txs in order.
{
  "success": true,
  "orderId": "0x905d26d5...",
  "quoteId": "0x905d26d5...",
  "status": "awaiting_deposit",
  "routeType": "direct",
  "isComposite": false,
  "action": {
    "kind": "gateway",
    "subtype": "evm",
    "provider": "intent",
    "providerDisplayName": "Intent Engine",
    "chainId": 8453,
    "chain": { "chainId": 8453, "name": "base", "displayName": "Base" },
    "token": { "address": "0x833589fC...", "symbol": "USDC", "decimals": 6 },
    "amount": { "raw": "10000000", "formatted": "10.000000", "usd": "10.00" },
    "instructions": ["1. Approve USDC spending", "2. Execute the gateway.deposit() transaction"],
    "requirements": {
      "needsApproval": true, "transactionCount": 2, "estimatedSeconds": 15,
      "gaslessAvailable": true, "userActionRequired": "sign_tx"
    },
    "gatewayAddress": "0x8a422e10e7d67fb71aaf085e5a893f388dc781be",
    "transactions": [
      { "stepNumber": 1, "to": "0x833589fC...", "data": "0x095ea7b3...", "value": "0", "chainId": 8453,
        "description": "Approve USDC for Gateway" },
      { "stepNumber": 2, "to": "0x8a422e10...", "data": "0xebdff1cd...", "value": "0", "chainId": 8453,
        "description": "Deposit via IntentGateway" }
    ],
    "approval": {
      "needed": true, "tokenAddress": "0x833589fC...", "spender": "0x8a422e10...",
      "amount": "10000000",
      "transaction": { "stepNumber": 1, "to": "0x833589fC...", "data": "0x095ea7b3...",
        "value": "0", "chainId": 8453, "description": "Approve USDC for Gateway" }
    },
    "movePayload": null,
    "permitSupported": true,
    "gasless": {
      "supported": true,
      "endpoint": "/v1/order/0x905d26d5.../submit-gasless",
      "description": "Sign once — zero gas required"
    }
  }
}

// ─── action.kind = "deposit_address" ─────────────────────────────────────
// Plain ERC-20 transfer of action.amount to action.address. action.transactions
// MAY contain pre-built transfer calldata (boost), or be null (Relay/NEAR).
{
  "success": true,
  "orderId": "0xb4672b9d...",
  "quoteId": "0xb4672b9d...",
  "status": "awaiting_deposit",
  "routeType": "direct",
  "isComposite": false,
  "action": {
    "kind": "deposit_address",
    "subtype": "near",
    "provider": "near_intents",
    "providerDisplayName": "Intent Engine",
    "chainId": 8453,
    "chain": { "chainId": 8453, "name": "base", "displayName": "Base" },
    "token": { "address": "0x833589fC...", "symbol": "USDC", "decimals": 6 },
    "amount": { "raw": "10000000", "formatted": "10.000000", "usd": "10.00" },
    "instructions": ["Send exactly 10.000000 USDC to the deposit address"],
    "requirements": {
      "needsApproval": false, "transactionCount": 0, "estimatedSeconds": 34,
      "gaslessAvailable": false, "userActionRequired": "send_to_address"
    },
    "address": "0xC93B9CC9dF1AdDAD31839DC80AEd1105b3C87371",
    "transactions": null,
    "expiresAt": null,
    "providerRequestId": "ff22d1dd-430b-4ab4-bd60-274de7146513",
    "paymentRequest": {
      "qrUri": "ethereum:0xC93B9CC9dF1AdDAD31839DC80AEd1105b3C87371/transfer?address=0x833589fC...&uint256=10000000",
      "standard": "eip681",
      "displayAmount": "10",
      "rawAmount": "10000000",
      "amountIsExact": true,
      "copyAddress": "0xC93B9CC9dF1AdDAD31839DC80AEd1105b3C87371",
      "copyAmount": "10.000000"
    },
    "gasless": null
  }
}

// ─── action.kind = "transaction" ─────────────────────────────────────────
// User signs N pre-built txs (xReserve, CCTP, atomic swap+bridge, Movement V2).
// Sign action.approval.transaction first (if present), then action.transactions[] in order.
{
  "success": true,
  "orderId": "0x...",
  "quoteId": "0x...",
  "status": "ready_to_execute",
  "routeType": "direct",
  "isComposite": false,
  "action": {
    "kind": "transaction",
    "subtype": "xreserve",
    "provider": "xreserve",
    "providerDisplayName": "Intent Engine",
    "chainId": 1,
    "chain": { "chainId": 1, "name": "eth", "displayName": "Ethereum" },
    "token": { "address": "0xA0b86991...", "symbol": "USDC", "decimals": 6 },
    "amount": { "raw": "1000000", "formatted": "1.000000", "usd": "1.00" },
    "instructions": ["Approve USDC", "Submit depositToRemote()", "Circle mints USDCx within ~60s"],
    "requirements": {
      "needsApproval": true, "transactionCount": 2, "estimatedSeconds": 90,
      "gaslessAvailable": false, "userActionRequired": "sign_tx"
    },
    "approval": {
      "needed": true, "tokenAddress": "0xA0b86991...", "spender": "0x8888888199b2Df...",
      "amount": "1000000",
      "transaction": { "stepNumber": 1, "to": "0xA0b86991...", "data": "0x095ea7b3...",
        "value": "0", "chainId": 1, "description": "Approve USDC for xReserve" }
    },
    "transactions": [{
      "stepNumber": 2, "to": "0x8888888199b2Df...", "data": "0x...", "value": "0",
      "chainId": 1, "description": "xReserve.depositToRemote"
    }],
    "solanaParams": null
  }
}

// ─── 4xx error ───────────────────────────────────────────────────────────
{
  "success": false,
  "error": "Boost (deposit-address flow) is not available for this route.",
  "code": "BOOST_NOT_AVAILABLE",
  "availableProviders": [ { "provider": "xreserve", "depositType": "transaction" } ],
  "requestId": "..."
}

Composite Routes (non-stable source)

Source-side swaps only apply within the supported public token set; arbitrary long-tail tokens are not exposed by the current API surface. the bridge. Two response shapes apply, both still keyed on action.kind:

Pitfall (two-step shape): the DEX swap routes its output directly to action.address via the router's receiver field. Don't prompt the user for a second transfer — sign sourceSwap.transaction and the address gets funded automatically.
// Two-step composite — execute swap first, then status flips to awaiting_deposit
if (order.status === 'awaiting_source_swap') {
  if (order.sourceSwap.approval) {
    await wallet.sendTransaction({
      to:      order.sourceSwap.approval.tokenAddress,
      data:    encodeApprove(order.sourceSwap.approval.spender, order.sourceSwap.approval.amount),
      chainId: order.sourceSwap.transaction.chainId,
    });
  }
  await wallet.sendTransaction(order.sourceSwap.transaction);
  // Output lands at order.action.address automatically. Now poll status.
} else {
  // Atomic shape (action.kind === 'transaction', subtype 'atomic_swap_bridge')
  // — one bundled tx, handled by the same switch in the integration example above.
  await execute(order, wallet);
}

Order Status

Returns full order details including status, transactions, timing, and action intent info. Poll this endpoint to track order progress.

Soft confirmation: pass ?softConfirmation=true when your UI wants a non-terminalaccepted state. It means source funds were verified and Stableyard has accepted settlement responsibility, but final delivery may still be pending.
Status Codes
pending — awaiting deposit
accepted — source funds verified; settlement pending
in_progress — executing
completed — delivered
failed — execution failed
refunded — funds returned
Reading amounts for reconciliation:
  • amount.quoted — what was promised at order creation (immutable)
  • amount.minimum — slippage floor (null when unset). Solver contract enforces this on-chain for native routes.
  • amount.actual — what settled. Always non-null on terminal orders.
  • amount.actual.verifiedtrue when observed on-chain (authoritative). false when provider-reported or inferred.
  • amount.actual.source"on-chain" | "provider" | "quoted" — where the number came from.
  • fees.reliablefalse when USD pricing is unavailable (unknown token); fall back to raw token amounts.
TypeScript Interface
type StatusAmount = {
  raw: string; formatted: string; usd?: string | null;
  inferred?: boolean;                       // true when value was defaulted (no on-chain observation)
  verified?: boolean;                       // true when read on-chain (authoritative)
  source?: 'on-chain' | 'provider' | 'quoted';
};

type TxRef = { hash: string; explorerUrl?: string } | null;

export type OrderStatusResponse = {
  success: true;
  resourceType: 'order' | 'quote';          // 'quote' if order record not yet materialized
  orderId: string;
  quoteId: string;                          // same value as orderId
  status: {
    code: 'pending' | 'accepted' | 'in_progress' | 'completed' | 'failed' | 'refunded';
    display: string;
    description?: string;
    isTerminal: boolean;
    progress: number;                       // 0–100
  };
  softConfirmation?: {
    enabled: boolean;
    accepted: boolean;
    reason: 'source_funds_verified' | null;
    terminal: boolean;
  };
  tokenIn?: {
    address: string; symbol: string; decimals: number; logoURL?: string;
    amount: { raw: string; formatted: string; usd?: string | null;
              quoted?: StatusAmount; actual?: StatusAmount | null };
    chain?: ChainInfo;
  };
  tokenOut?: {
    address: string; symbol: string; decimals: number; logoURL?: string;
    amount: {
      expected?: StatusAmount;              // legacy alias of quoted
      quoted?: StatusAmount;
      minimum?: StatusAmount | null;        // slippage floor — null when unset
      actual?: StatusAmount | null;         // solver-reported fill
    };
    chain?: ChainInfo;
  };
  fees?: {
    reliable: boolean;                      // false → USD math suppressed (unknown token)
    quoted: { usd: string } | null;
    actual: { usd: string; deltaUsd: string; inferred: boolean } | null;
  };
  addresses?: {
    deposit?: string | null;
    depositor?: string | null;              // original sender (refund path)
    recipient?: string;
    solver?: string | null;
  };
  transactions?: {
    deposit: TxRef; execution: TxRef; destination: TxRef; refund: TxRef; sweep: TxRef;
  };
  route?: { provider: string; providerDisplayName?: string; isNative?: boolean };
  timing?: {
    estimatedSeconds: number;
    settlementSeconds: number | null;       // deposited → completed
    totalSeconds: number | null;            // created → completed
    actualSeconds: number | null;           // alias of settlementSeconds
    display: string;
    fasterThanEstimate: boolean | null;
  };
  partner?: { id: string } | null;
  trustedOrder?: {
    creditRecorded: boolean; creditAmount: string | null; creditTxHash: string | null;
    creditRecordedAt: string | null; repaid: boolean;
    repaymentTxHash: string | null; repaymentDeadline: string | null;
  } | null;
  masked?: boolean;                         // true on private orders without owning API key
  timestamps?: {
    created: string; deposited?: string | null; executed?: string | null;
    completed?: string | null; failed?: string | null; refunded?: string | null;
    lastUpdated?: string;
  };
  meta?: { version: string; requestId: string };
};
// Response (completed order, fields shown with real values)
{
  "success": true,
  "orderId": "0x...",
  "status": {
    "code": "completed",
    "display": "Completed",
    "isTerminal": true,
    "progress": 100
  },
  "tokenIn": {
    "symbol": "USDC",
    "amount": {
      "raw": "500000000", "formatted": "500.00", "usd": "500.00",
      "quoted": { "raw": "500000000", "formatted": "500.00", "usd": "500.00" },
      "actual": {
        "raw": "500000000", "formatted": "500.00", "usd": "500.00",
        "verified": true,                     // balance observed on-chain at CREATE2
        "source": "on-chain"
      }
    },
    "chain": { "chainId": 42161, "name": "arbitrum", "displayName": "Arbitrum One" }
  },
  "tokenOut": {
    "symbol": "USDC",
    "amount": {
      "expected": { "raw": "499600000", "formatted": "499.60" },   // legacy alias of quoted
      "quoted":   { "raw": "499600000", "formatted": "499.60", "usd": "499.60" },
      "minimum":  { "raw": "497100000", "formatted": "497.10" },   // null when not set
      "actual": {
        "raw": "499600000", "formatted": "499.60", "usd": "499.60",
        "inferred": false,
        "verified": true,                     // parsed from bridge receipt event
        "source": "on-chain"
      }
    },
    "chain": { "chainId": 8453, "name": "base", "displayName": "Base" }
  },
  "fees": {
    "reliable": true,                         // USD pricing was available for both tokens
    "quoted": { "usd": "0.4000" },
    "actual": { "usd": "0.4000", "deltaUsd": "0.0000", "inferred": false }
  },
  "transactions": {
    "deposit":     { "hash": "0x...", "explorerUrl": "https://arbiscan.io/tx/0x..." },
    "execution":   { "hash": "0x...", "explorerUrl": "https://arbiscan.io/tx/0x..." }, // solver's fill tx
    "destination": { "hash": "0x...", "explorerUrl": "https://basescan.org/tx/0x..." },
    "refund":      null,
    "sweep":       null                       // null for non-CREATE2 flows
  },
  "addresses": {
    "deposit":   "0x...",                    // CREATE2 sweeper (null when provider holds deposit)
    "depositor": "0x...",                    // original sender (for refund path)
    "recipient": "0x...",
    "solver":    "0x..."
  },
  "timing": {
    "estimatedSeconds": 30,
    "settlementSeconds": 12,                  // deposited → completed (swap speed)
    "totalSeconds": 45,                       // created → completed (includes user deposit wait)
    "actualSeconds": 12,                      // alias of settlementSeconds (backward-compat)
    "display": "12s",
    "fasterThanEstimate": true
  },
  "routeType": "direct",
  "route": { "provider": "intent", "providerDisplayName": "Intent Engine" },
  "partner": { "id": "stableyard" },          // null when order wasn't created with a partner API key
  "trustedOrder": null                        // or { creditRecorded, creditAmount, repaid, ... } for credit flow
}

List Orders

Submit Transaction

Gasless Deposit

Trusted Orders

Credit-based cross-chain orders for trusted partners. The solver fronts liquidity on the destination chain immediately — the partner repays the source token within 5 minutes. Only stablecoins (USDC, USDT) are accepted as source tokens. A per-partner credit limit (default $500) prevents unbounded exposure.

Privacy

Any order can be made private by setting private: true on POST /v1/order or POST /v1/trusted-order. Trusted orders additionally support delayed execution for timing decorrelation.

Private Order Flag

Set private: true on any order — regular or trusted. Private orders are excluded from GET /v1/orders list responses. Direct lookup by orderId still works but returns masked details unless the owning partner API key is provided.

Masked Order Details

When a private order is queried via GET /v1/order/:id without the owning partner's API key, the response only includes status, chain IDs, and timestamps. Token addresses, amounts, recipient, and transaction hashes are hidden. Include the partner API key header to see full details.

// Masked response (no API key or wrong partner)
{
  "success": true,
  "orderId": "0xabc...123",
  "status": { "code": "completed", "display": "Completed", "isTerminal": true, "progress": 100 },
  "sourceChain": { "chainId": 8453, "name": "base" },
  "destChain": { "chainId": 42161, "name": "arbitrum" },
  "timestamps": { "created": "...", "completed": "...", "lastUpdated": "..." },
  "masked": true
}

Delayed Execution

Set delayMinutes (1–1440) on trusted orders to delay solver execution. The order sits in queue and becomes eligible for the solver only after the delay elapses. This breaks timing correlation between the partner's request and the on-chain execution. Maximum delay: 24 hours. Trusted orders only (stablecoins — no volatility risk).

Usage Example

# Regular private order
curl -X POST https://routing-api.stableyard.fi/v1/order \
  -H "Content-Type: application/json" \
  -d '{
    "sourceChainId": 1,
    "destChainId": 8453,
    "sourceToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "sourceAmount": "1000000000",
    "recipient": "0xYourAddress",
    "private": true
  }'

# Trusted private order with delayed execution
curl -X POST https://routing-api.stableyard.fi/v1/trusted-order \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-partner-key" \
  -d '{
    "sourceChainId": 8453,
    "destChainId": 42161,
    "sourceToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "destToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
    "sourceAmount": "500000000",
    "recipient": "0xRecipient",
    "private": true,
    "delayMinutes": 30
  }'

Supported Chains

ChainNetwork IDVM TypeSupported Tokens
Ethereum1EVMUSDC, USDT, ETH
Base8453EVMUSDC, USDT, ETH
BNB Chain56EVMUSDC, USDT, BNB (wallet-only; deposit address disabled)
Arbitrum42161EVMUSDC, USDT, ETH
Bitcoin10001BTCBTC source-only to Movement USDCx
Movement10002MoveUSDCx, MOVE
Solana10103SVMUSDC, USDT, SOL
Tempo4217EVMUSDC, PathUSD (wallet-only)
Polygon137EVMUSDC, USDT, POL
Tron728126428TVMUSDT source-only to Movement USDCx
Avalanche43114EVMUSDC, AVAX (wallet-only/source disabled for deposit address)
HyperEVM999EVMUSDC, HYPE source-only to Movement USDCx

Error Codes

HTTPCodeMeaning
400INVALID_PARAMSMissing or invalid request parameters
400PROVIDER_NOT_AVAILABLEpreferredProvider isn't served for this pair — response lists availableProviders
400BOOST_NOT_AVAILABLEexecutionMode=deposit_address requested but no deposit-address provider is available for this route
400MODE_MISMATCH_WITH_LOCKED_QUOTEexecutionMode at /v1/order doesn't match the locked quote's selectedProvider. Re-quote with the correct mode
400QUOTE_EXPIREDQuote expired beyond the 30-minute grace window at /submit-tx — fetch a fresh quote and re-lock
400TX_NOT_FOUND/submit-tx: submitted txHash doesn't exist on the expected source chain
400TX_REVERTED/submit-tx: the submitted transaction reverted on-chain
400NO_MATCHING_EVENT/submit-tx: transaction exists but has no Gateway Deposit event / no ERC20 Transfer to depositAddress / no CCTP DepositForBurn
400WRONG_QUOTE_ID/submit-tx: Gateway Deposit event's quoteId doesn't match the order
400WRONG_TOKEN/submit-tx: token in the deposit event ≠ order.sourceToken
400WRONG_AMOUNT/submit-tx: observed on-chain amount below the 2% tolerance floor of sourceAmount
400WRONG_RECIPIENT/submit-tx: CCTP hookData recipient or xReserve depositToRemote mintRecipient ≠ order.recipient — submitting someone else's deposit is blocked here
400WRONG_MINT_RECIPIENT/submit-tx cctp_xreserve: mintRecipient in DepositForBurn ≠ CCTPxReserveWrapper — you minted USDC to yourself instead of to our wrapper, so we cannot relay it
400WRONG_DOMAIN/submit-tx cctp_xreserve: destinationDomain in DepositForBurn ≠ 0 (Ethereum) — burn targeted a different chain
400MALFORMED_HOOK_DATA/submit-tx cctp_xreserve: hookData shorter than 32 bytes — malformed burn, cannot verify recipient
400MALFORMED_CALLDATA/submit-tx xreserve: tx is not a depositToRemote call (wrong function or corrupted input)
404NOT_FOUNDOrder or quote not found
404NO_QUOTESNo routes available for this pair
404SWAP_NOT_AVAILABLENo swap quote for this token pair
404QUOTE_NOT_FOUNDquoteId was not found in DB (may have been TTL-swept after 1h)
409QUOTE_ALREADY_USEDQuote already locked by a prior /v1/order call — each quote is single-use
409DEPOSIT_TX_ALREADY_SET/submit-tx: this order already has a different depositTxHash recorded. Submitting the same hash again is a no-op 200; submitting a different hash is blocked. Use /v1/order/recover-deposit if the original submission was wrong
409DEPOSIT_TX_DUPLICATE/submit-tx: this txHash is already bound to a different order — enforces the 1-deposit-to-1-order invariant at the DB layer
410QUOTE_EXPIREDQuote's 20s lock window has elapsed at /v1/order — fetch a fresh quote
429RATE_LIMITEDToo many requests
500INTERNAL_ERRORServer error — retry with backoff
503INTERNAL_API_UNAVAILABLE/internal/*: INTERNAL_API_KEY is not configured in this environment — contact ops
500VERIFICATION_FAILEDOn-chain deposit verification failed inside /submit-tx (tx receipt missing, balance too low, or DB write failed)

All responses include meta.requestId for debugging. Include X-Request-Id header to set your own.