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.
https://routing-api.stableyard.fiThree API calls to bridge tokens cross-chain:
Get a quote
POST /v1/quote with source/dest chain, token, amount
Create an order
POST /v1/order — returns deposit address or pre-built transaction calldata
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/0xOrderIdPublic 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.
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.
addressstringRequiredWallet 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)chainIdsstringCSV of EVM chain IDs to query (e.g. 1,8453,42161,137). Ignored for Solana/Movement. Defaults to all configured EVM chains.
minUsdnumberEligibility 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).
# 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"{
"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": "..." }
}aggregate3 + getEthBalance) — N stablecoins + native packed into a single eth_call.getBalance + per-mint getTokenAccountsByOwner). Uses mint filter, not programId, so it works on public RPCs that block programId queries./view calls (USDCx via xReserve module, MOVE via 0x1::coin::balance).errors[] — one bad RPC doesn't fail the whole request.null.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.
sourceChainIdnumberRequiredSource chain ID (e.g. 8453 for Base, 10001 for Bitcoin, 10002 for Movement, 10103 for Solana)
destChainIdnumberRequiredDestination chain network ID (e.g. 42161 for Arbitrum)
sourceTokenstringRequiredSource token contract address
destTokenstringRequiredDestination token contract address
sourceAmountstringAmount to send in raw units. Required for exact_input (default mode)
destAmountstringDesired output in raw units. Required for exact_output mode
modestringQuote mode: "exact_input" (default) or "exact_output"
recipientstringRequiredRecipient address on destination chain
slippageBpsnumberSlippage 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 */ ]
}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.
sourceChainIdnumberRequiredSource chain network ID
destChainIdnumberRequiredDestination chain network ID
sourceTokenstringRequiredSource token address
destTokenstringRequiredDestination token address
sourceAmountstringAmount to send in raw units. Required for exact_input (default)
destAmountstringDesired output in raw units. Required for exact_output mode
modestringQuote mode: "exact_input" (default) or "exact_output"
recipientstringRequiredRecipient address on destination chain
slippageBpsnumberSlippage 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
executionModestringExecution 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
preferredProviderstringForce a specific provider (e.g. "relay", "intent"). Omit for best rate
providersstring[]Filter providers. E.g. ["relay", "intent"]. Omit to query all
quoteIdstringLock to a previously-issued quote (pins price + provider). Single-use, 20s window
callDatastringAction intent calldata (DeFi actions). Forces native solver
privatebooleanHide order from public list and mask details. Default: false
sourceAddressstringRequired for Tron source quotes/orders. Use the sender's T-prefix Tron address.
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
};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;
}
}
}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.// ─── 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": "..."
}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:
action.kind === "transaction" with subtype: "atomic_swap_bridge" — single bundled tx. Sign action.approval (if any), then action.transactions[0]. The router swaps and forwards in one shot.status: "awaiting_source_swap" — two-step fallback. The legacy sourceSwap block holds the DEX swap (sourceSwap.approval + sourceSwap.transaction); the follow-up action is a normal deposit_address kind keyed in the intermediate stablecoin (where the swap output lands).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);
}Returns full order details including status, transactions, timing, and action intent info. Poll this endpoint to track order progress.
?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.pending — awaiting depositaccepted — source funds verified; settlement pendingin_progress — executingcompleted — deliveredfailed — execution failedrefunded — funds returnedamount.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.verified — true when observed on-chain (authoritative). false when provider-reported or inferred.amount.actual.source — "on-chain" | "provider" | "quoted" — where the number came from.fees.reliable — false when USD pricing is unavailable (unknown token); fall back to raw token amounts.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
}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.
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.
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.
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
}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).
# 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
}'| Chain | Network ID | VM Type | Supported Tokens |
|---|---|---|---|
| Ethereum | 1 | EVM | USDC, USDT, ETH |
| Base | 8453 | EVM | USDC, USDT, ETH |
| BNB Chain | 56 | EVM | USDC, USDT, BNB (wallet-only; deposit address disabled) |
| Arbitrum | 42161 | EVM | USDC, USDT, ETH |
| Bitcoin | 10001 | BTC | BTC source-only to Movement USDCx |
| Movement | 10002 | Move | USDCx, MOVE |
| Solana | 10103 | SVM | USDC, USDT, SOL |
| Tempo | 4217 | EVM | USDC, PathUSD (wallet-only) |
| Polygon | 137 | EVM | USDC, USDT, POL |
| Tron | 728126428 | TVM | USDT source-only to Movement USDCx |
| Avalanche | 43114 | EVM | USDC, AVAX (wallet-only/source disabled for deposit address) |
| HyperEVM | 999 | EVM | USDC, HYPE source-only to Movement USDCx |
| HTTP | Code | Meaning |
|---|---|---|
| 400 | INVALID_PARAMS | Missing or invalid request parameters |
| 400 | PROVIDER_NOT_AVAILABLE | preferredProvider isn't served for this pair — response lists availableProviders |
| 400 | BOOST_NOT_AVAILABLE | executionMode=deposit_address requested but no deposit-address provider is available for this route |
| 400 | MODE_MISMATCH_WITH_LOCKED_QUOTE | executionMode at /v1/order doesn't match the locked quote's selectedProvider. Re-quote with the correct mode |
| 400 | QUOTE_EXPIRED | Quote expired beyond the 30-minute grace window at /submit-tx — fetch a fresh quote and re-lock |
| 400 | TX_NOT_FOUND | /submit-tx: submitted txHash doesn't exist on the expected source chain |
| 400 | TX_REVERTED | /submit-tx: the submitted transaction reverted on-chain |
| 400 | NO_MATCHING_EVENT | /submit-tx: transaction exists but has no Gateway Deposit event / no ERC20 Transfer to depositAddress / no CCTP DepositForBurn |
| 400 | WRONG_QUOTE_ID | /submit-tx: Gateway Deposit event's quoteId doesn't match the order |
| 400 | WRONG_TOKEN | /submit-tx: token in the deposit event ≠ order.sourceToken |
| 400 | WRONG_AMOUNT | /submit-tx: observed on-chain amount below the 2% tolerance floor of sourceAmount |
| 400 | WRONG_RECIPIENT | /submit-tx: CCTP hookData recipient or xReserve depositToRemote mintRecipient ≠ order.recipient — submitting someone else's deposit is blocked here |
| 400 | WRONG_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 |
| 400 | WRONG_DOMAIN | /submit-tx cctp_xreserve: destinationDomain in DepositForBurn ≠ 0 (Ethereum) — burn targeted a different chain |
| 400 | MALFORMED_HOOK_DATA | /submit-tx cctp_xreserve: hookData shorter than 32 bytes — malformed burn, cannot verify recipient |
| 400 | MALFORMED_CALLDATA | /submit-tx xreserve: tx is not a depositToRemote call (wrong function or corrupted input) |
| 404 | NOT_FOUND | Order or quote not found |
| 404 | NO_QUOTES | No routes available for this pair |
| 404 | SWAP_NOT_AVAILABLE | No swap quote for this token pair |
| 404 | QUOTE_NOT_FOUND | quoteId was not found in DB (may have been TTL-swept after 1h) |
| 409 | QUOTE_ALREADY_USED | Quote already locked by a prior /v1/order call — each quote is single-use |
| 409 | DEPOSIT_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 |
| 409 | DEPOSIT_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 |
| 410 | QUOTE_EXPIRED | Quote's 20s lock window has elapsed at /v1/order — fetch a fresh quote |
| 429 | RATE_LIMITED | Too many requests |
| 500 | INTERNAL_ERROR | Server error — retry with backoff |
| 503 | INTERNAL_API_UNAVAILABLE | /internal/*: INTERNAL_API_KEY is not configured in this environment — contact ops |
| 500 | VERIFICATION_FAILED | On-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.