protocol: "Ondo", network: bsc). The market-share position always lives on BSC, but clients
can fund a buy from — or receive a sell payout on — another EVM chain. Pods handles the bridge and the
async order fill.
Introduction
Same-chain (BSC)
Settlement is asynchronous. The on-chain transaction submits the request in seconds; the order
reaches SUCCESS only after Ondo fills it (minutes later). For crosschain, the bridge leg settles
first, then the order. Track both the on-chain receipt and the action status via WebSocket or
history polling.
Quote and bytecode come from the same call.GET /strategies/:id/bytecodereturns a pricedquote(for your UI) andbytecode[](to execute on-chain).
Authentication
Every request requires:- The key identifies your customer. Fee configuration and strategy catalog are scoped to it.
- Success responses are the bare JSON body (HTTP 200). Errors use
{ "error": { "code", "message", "details?" } }. - Keep the API key on your backend — never expose it in a browser app.
Integration flow
Buy and sell share one path — only the bytecode call (step 4) differs by direction.1
Check market status
Check tradability — prefer passing a symbol.
2
List Global Markets tokens
3
Resolve the strategy id
4
Generate bytecode — the only step that differs
5
Show the quote
Buy only: enforce the
$10 minimum.6
Execute bytecode
Execute
bytecode[] on the correct chain.7
Track action status
Poll until
SUCCESS — via WebSocket, GET /strategies/:id/status, or position/history polling.8
Refresh positions
Re-read the wallet position to reflect the settled result.
1. Market status
Gate buy/sell when the market is closed. Optionally pass a Global Markets tokensymbol to enrich the same
response with per-asset tradability under asset.
asset field:
-
without
symbol:assetisnull— returns the default market status: -
with
symbol:assetcontains the per-symbol tradability snapshot
isOpen — block new submissions when false. The payload may also include
session details (marketStatus, nextOpen, nextClose, offhours).
symbol is the Ondo Global Markets token symbol and must end in lowercase on (for example NVDAon,
AAPLon). Invalid symbols return 422.
When present, asset includes:
asset.tradable is false when:
- The market is closed/paused (
ONDO_MARKET_CLOSED/ONDO_MARKET_PAUSED), unlessoffhours.isOpenistrue - The specific asset has an active
ASSET_PAUSEDrestriction (ONDO_ASSET_PAUSED)
ASSET_LIMITED restriction does not flip tradable to false; it only sets
limited: true.
Bytecode generation for Ondo request-lend / request-withdraw uses the same tradability
rules and returns 409 with those ONDO_* codes when a trade is blocked. Per-asset
eligibility during off-hours can still fail later at CoW quote time with errors such as
COW_ASSET_SESSION_CLOSED — see Ondo Global Markets docs.
2. List Stocks & ETFs
categoryaccepts comma-separated values (stock,etf).- Each token:
{ symbol, name, address, decimals, chainId, category, priceInUSD, logoURI? }. - Ondo symbols end in
ON(e.g.AAPLON). StripONfor display if desired.
3. Resolve strategy ID
A market-share token address alone is not enough — you need the strategy id.{ data: [...], pagination }. Each item’s id is the strategy id you use — a slug
in the form {protocol}-{assetName}-{network}:
assetmatches the market-share token addressnetworkIdis"56"(a string)
id (e.g. Ondo-NVDA-bsc) to buy / request-lend or
sell / request-withdraw from availableActions. buy and sell do the same
thing as request-lend and request-withdraw.
4. Buy — buy or request-lend
Same-chain (BSC):
fromChainId=56, fromTokenAddress=0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d (18 decimals). The client signs on BSC.
Buy $10 (USDC has 18 decimals on BSC → amount=10e18):
fromChainId= origin chain (e.g. 8453 Base). The client signs only on the origin
chain; Pods bridges funds to BSC and fills the order asynchronously.
Buy $10 from Base (USDC has 6 decimals on Base → amount=10e6):
- Filter
bytecodewhereNumber(chainId) === Number(chainIdIn)(same-chain:56; crosschain: the origin chain). - Batch all filtered legs into one signed transaction (
output=bytecode+ EIP-7702, oroutput=userOperation/output=fireblocksper your wallet stack). - Wait for async completion (crosschain: bridge → BSC → market-share fill).
- Minimum: $10 USDC (enforced server-side, error
REQUEST_LEND_AMOUNT_TOO_LOW). For crosschain the minimum is checked on the bridged USDC amount that lands on BSC. - Use
quotefor the full pricing UI (funding amount, expected shares, fees).quote.tokenInalways reflectsfromChainId— read itsdecimals/chainIddirectly, no need to cross-reference §8.
Crosschain: the client only signs on the origin chain. Bridge + market-share purchase are handled by Pods.
5. Sell — sell or request-withdraw
Same-chain (BSC):
toChainId=56, toTokenAddress=0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d. The client signs on BSC.
Sell 5 shares (shares are 18-decimal wei → amountInShares=5e18), receive USDC on BSC:
- Use
amountInShares(notamount) for Ondo sells. No minimum sell amount. - Response shape matches buy (
id,chainIdIn,chainIdOut,crossChain,singleUseAddress,orderUid,quote,bytecode).
toChainId= destination chain. The client signs only on BSC (shares live there);
Pods sells the market share for USDC on BSC, then bridges the proceeds to the destination chain.
Sell 5 shares (amountInShares is still 18-decimal shares → 5e18), receive USDC on Base:
quote.tokenInis always shares on BSC (decimals: 18,chainId: 56) — what the client signs away.quote.tokenOutreflects the payout leg: on same-chain sells this is BSC USDC (decimals: 18); on cross-chain sells it is alreadytoTokenAddressontoChainId(e.g. Base USDC =decimals: 6,chainId: 8453). Readquote.tokenOut.decimals/quote.tokenOut.chainIddirectly — never assume BSC decimals for a cross-chain sell, and never cross-reference §8 to guess the payout scale.quote.feeBreakdown.charges[]includes abridgeAndSlippageline for the exit and, on cross-chain sells, a second one for the payout bridge — both self-describe their owndecimals/symbol, so render each line as-is rather than assuming a shared scale.- The payout bridge is requoted at forward time with the realized settlement balance, so treat
quote.tokenOuton cross-chain sells as the estimate at bytecode time, not the final amount. bytecode[].chainIdis a decimal string (e.g."56"). Compare withNumber(leg.chainId) === 56(orNumber(chainIdIn)), never strict-equal to a numeric route field.
- Filter
bytecodewhereNumber(chainId) === 56(always BSC — shares are on BSC). - Batch and sign on BSC in a single transaction.
- Wait for async completion (market-share fill → crosschain: bridge USDC to
toChainId).
Crosschain: the client only signs on BSC. Market share → USDC redemption and the bridge payout are handled async by Pods.
Limit price, expiry, and cancel
priceInUsd and expireAt are optional on GET /strategies/:id/bytecode for Ondo BSC CoW
(Ondo-*-bsc / OndoStockBsc) request-lend and request-withdraw. Ethereum primary Ondo
strategies and every other strategy reject them with 400 PRICE_IN_USD_NOT_SUPPORTED /
EXPIRE_AT_NOT_SUPPORTED.
The two params are independent:
priceInUsd is integer USD cents, not dollars. 17050 = $170.50. When set, the bytecode
quote also includes:
expireAt is a UTC Unix timestamp in seconds (CoW validTo / uint32). Milliseconds are
rejected (INVALID_EXPIRE_AT). The timestamp must be in the future and at most 168 hours from
now.
Buy 320.00 limit that expires in one hour:
Cancel a client-limit order
Only funded, unfilled client-limit actions (quote / PRESIGN clientLimitPriceUsd > 0) can be
cancelled. An expireAt-only market order is not cancellable — it expires and refunds at
orderValidTo.
x-api-key or a Bearer token. wallet must match the action’s requesting wallet.
A 200 means the action is REFUNDED and the SUW refund outbox was queued (or already
existed). Poll GET /actions/:id until the refund step / outbox confirms on-chain. The reason
ONDO_CLIENT_LIMIT_CANCELLED is stored on the settlement step (steps[].failureReason), not
as a top-level Action field.
6. Positions & pending requests
Thewallet is always normalized to BSC (same EVM address regardless of funding/payout chain).
Wallet snapshot (all positions)
earn.positions where strategy.protocol === "Ondo" — not at the
top level of the response:
earn.positions has the same { spotPosition, strategy } shape as the single-strategy
endpoint below — read earn.positions[].strategy.protocol === "Ondo" to filter for stocks & ETFs.
Single strategy position
In-flight operations
TherequestedTo* arrays represent pending async orders not yet settled (same on same-chain and crosschain):
Empty arrays mean nothing is pending.
Expected states
- Buy submitted, shares not yet credited →
requestedToLend/requestedToLendInSharespopulated. - Sell submitted, USDC not yet paid out →
requestedToWithdraw/requestedToWithdrawInSharespopulated. - Settled → all pending arrays empty;
currentPositionInShares/underlyingBalanceUSDreflect the real balance.
Available balance for sell
Do not subtract pending buys from available shares. Use
availableShares as amountInShares
when selling the full position.
7. Track order status
After submittingbytecode[] on-chain, the action is not complete until status is SUCCESS.
HTTP — pending actions for one strategy
Poll when you want a strategy-scoped snapshot without loading the full wallet or strategy position. The handler refreshes in-flight operations before responding.Root response
Orderbook
orderBook (BSC Ondo Global Markets strategies only) is a pre-flight tradability probe — it tells you
whether a new request is likely to succeed right now, before you build any bytecode. requestLend
covers buys and requestWithdraw covers sells (present only when sell is supported):
Present even when
actions is empty.
GM market (Ethereum primary)
gmMarket is the equivalent pre-flight probe for Ethereum primary Ondo stock strategies. It comes from the memoized Ondo GM market/asset status, not CoW:
available: false and code: "ONDO_GM_UNAVAILABLE" mean the GM API could not be reached. Closed/paused markets keep available: true and set tradable: false with ONDO_MARKET_CLOSED, ONDO_MARKET_PAUSED, or ONDO_ASSET_PAUSED.
Actions
What appears inactions[]
Each item in
actions[] — base fields (every pending action)
Cross-chain pending actions include only the base block (no
suw).
Same-chain suw
suw block (same-chain only):
suw.phase lifecycle
suw.order shapes
All orders share a core, and posted decides the rest of the shape:
posted: false) — shows lastPostError, lastPostAttemptAt, canOpenOrder,
code, message, retryable:
posted: true) — shows validTo, creationDate, executionDate,
executedSellAmount, executedBuyAmount, txHash:
orderBook or suw.
Empty pending example
hasPending is true, or until suw.phase reaches a
terminal value. Example:
WebSocket (recommended)
action_update events where action.id matches the bytecode response id:
Re-subscribe on reconnect. Alternatively, use your customer webhook (
ACTION_UPDATE events).
History
Wallet-wide history is embedded inGET /v2/wallets/:address?include=all. For this strategy’s
position and history, use:
SAMECHAIN_INVESTMENT_DEPOSIT; sells as SAMECHAIN_INVESTMENT_WITHDRAW. Ignore
INITIAL status (quote artifact).
Refresh positions
Refetch wallet/strategy endpoints after on-chain confirmation and after WebSocketSUCCESS.
8. Supported chains & USDC addresses
Stocks & ETFs settle on BSC; funding (buy) and payout (sell) can use any supported USDC chain.
When
fromChainId / toChainId are set, fromTokenAddress / toTokenAddress are required
— the API does not default to USDC. Omission returns 422 VALIDATION_ERROR. A non-USDC
funding or payout token may add an extra same-chain swap leg.
9. Errors
Preserve
error.code in your UI for user-friendly messages.
10. Integration notes
- Use the correct actions:
buyorrequest-lend, andsellorrequest-withdraw. Do not use the genericlend/withdraw. - Sign on the right chain: buy = funding chain (
chainIdIn); sell = always BSC; position = BSC wallet. - Atomic bytecode: never send
bytecode[]legs as separate transactions — see Executing Bytecode. - Async completion: after the origin tx, Pods completes bridge (if crosschain) + order fill;
monitor via the action status, position, or
history. - Sells use shares: pass
amountInShares(18 decimals), notamount. Gate sells on wallet position (currentPositionInShares) andsuw.phase !== 'awaiting_forward'. - Wallet output: EOA →
output=bytecode+ EIP-7702; smart account →output=userOperation; Fireblocks →output=fireblocks+accountId. - Same EVM wallet works across funding/payout chains; the market-share position always lives on BSC.
- Limit vs market: omit both params for a market CoW (100 bps + SLA
validTo). PasspriceInUsdfor a client limit (10 bps).expireAtonly changesvalidToand does not make the order cancellable — cancel requirespriceInUsd.
Minimal example
Buy $10 of AAPL from Base (crosschain):response.bytecode (legs where Number(chainId) === 8453) on Base, then poll:
response.bytecode (legs where Number(chainId) === 56) on BSC, then poll the position until the
pending arrays clear.
Endpoint reference
All HTTP endpoints require
x-api-key.