Skip to main content
Ondo Global Markets brings tokenized stocks & ETFs on-chain as DeFi strategies (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)
Crosschain
Position: always queried with the wallet on 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/bytecode returns a priced quote (for your UI) and bytecode[] (to execute on-chain).
Preview quotes without a connected wallet. wallet also accepts the zero address (0x000…0) or a well-known burn address (e.g. 0x000…dEaD, 0x111…, 0x222…). The response is priced exactly like a real request but includes previewOnly: true and omits id, singleUseAddress, and orderUid — no Action is created, and the address is never persisted or synced to Moralis.
A previewOnly response has no bytecode to sign. Use the user’s real, owned wallet address for any request you intend to execute.

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.
Don’t have a key yet? Get an API key.

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 token symbol to enrich the same response with per-asset tradability under asset.
The response shape is always the Ondo Global Markets market-wide payload plus an asset field:
  • without symbol: asset is null — returns the default market status:
  • with symbol: asset contains the per-symbol tradability snapshot
Key market fields: 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:
  1. The market is closed/paused (ONDO_MARKET_CLOSED / ONDO_MARKET_PAUSED), unless offhours.isOpen is true
  2. The specific asset has an active ASSET_PAUSED restriction (ONDO_ASSET_PAUSED)
An active 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

  • category accepts comma-separated values (stock, etf).
  • Each token: { symbol, name, address, decimals, chainId, category, priceInUSD, logoURI? }.
  • Ondo symbols end in ON (e.g. AAPLON). Strip ON for display if desired.

3. Resolve strategy ID

A market-share token address alone is not enough — you need the strategy id.
Returns { data: [...], pagination }. Each item’s id is the strategy id you use — a slug in the form {protocol}-{assetName}-{network}:
Pick the item where:
  • asset matches the market-share token address
  • networkId is "56" (a string)
Then pass its 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):
Crosschain: 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):
Response (relevant fields):
Client execution
Atomic execution is mandatory. Cross-chain buys may return multiple legs on the funding chain (approve + bridge). Execute every leg where Number(chainId) === Number(chainIdIn) in one transaction — never as separate sends. Partial execution parks funds in escrow with PENDING status and no pipeline progress. See Executing Bytecode and EIP-7702.
  1. Filter bytecode where Number(chainId) === Number(chainIdIn) (same-chain: 56; crosschain: the origin chain).
  2. Batch all filtered legs into one signed transaction (output=bytecode + EIP-7702, or output=userOperation / output=fireblocks per your wallet stack).
  3. 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 quote for the full pricing UI (funding amount, expected shares, fees). quote.tokenIn always reflects fromChainId — read its decimals/chainId directly, 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 (not amount) for Ondo sells. No minimum sell amount.
  • Response shape matches buy (id, chainIdIn, chainIdOut, crossChain, singleUseAddress, orderUid, quote, bytecode).
Sell legs are a direct transfer of the market-share token to singleUseAddress, not an approve + pull. If you run a calldata allowlist (e.g. a policy-signer-sponsored paymaster), the sell leg’s call target is the market-share token contract itself — whitelist each ticker token you support as a call target, not just a shared router/spender address.
Crosschain: 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.tokenIn is always shares on BSC (decimals: 18, chainId: 56) — what the client signs away.
  • quote.tokenOut reflects the payout leg: on same-chain sells this is BSC USDC (decimals: 18); on cross-chain sells it is already toTokenAddress on toChainId (e.g. Base USDC = decimals: 6, chainId: 8453). Read quote.tokenOut.decimals / quote.tokenOut.chainId directly — never assume BSC decimals for a cross-chain sell, and never cross-reference §8 to guess the payout scale.
  • quote.feeBreakdown.charges[] includes a bridgeAndSlippage line for the exit and, on cross-chain sells, a second one for the payout bridge — both self-describe their own decimals/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.tokenOut on cross-chain sells as the estimate at bytecode time, not the final amount.
  • bytecode[].chainId is a decimal string (e.g. "56"). Compare with Number(leg.chainId) === 56 (or Number(chainIdIn)), never strict-equal to a numeric route field.
Client execution
Batch all BSC legs (Number(chainId) === 56) into one atomic transaction. See Executing Bytecode.
  1. Filter bytecode where Number(chainId) === 56 (always BSC — shares are on BSC).
  2. Batch and sign on BSC in a single transaction.
  3. 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 10ofNVDAonBSCwitha10 of NVDA on BSC with a 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.
Auth is 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

The wallet is always normalized to BSC (same EVM address regardless of funding/payout chain).

Wallet snapshot (all positions)

Stocks & ETFs appear under earn.positions where strategy.protocol === "Ondo" — not at the top level of the response:
Each entry in 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

or
Both forms return the same response. It includes Actions-backed accounting for principal, profit, and in-flight operations. Example response — a settled 5-share AAPLON holding, nothing pending:

In-flight operations

The requestedTo* 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 / requestedToLendInShares populated.
  • Sell submitted, USDC not yet paid out → requestedToWithdraw / requestedToWithdrawInShares populated.
  • Settled → all pending arrays empty; currentPositionInShares / underlyingBalanceUSD reflect the real balance.
Suggested polling: every 5–15 s until the pending arrays clear (or use WebSocket/webhook below).

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 submitting bytecode[] 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 in actions[] 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:
Not posted yet (posted: false) — shows lastPostError, lastPostAttemptAt, canOpenOrder, code, message, retryable:
Posted / live (posted: true) — shows validTo, creationDate, executionDate, executedSellAmount, executedBuyAmount, txHash:
Non-Ondo strategies return the same root envelope without orderBook or suw.

Empty pending example

Suggested polling: every 5–15 s while hasPending is true, or until suw.phase reaches a terminal value. Example:
Listen for 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 in GET /v2/wallets/:address?include=all. For this strategy’s position and history, use:
Market-share buys appear as 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 WebSocket SUCCESS.

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

  1. Use the correct actions: buy or request-lend, and sell or request-withdraw. Do not use the generic lend/withdraw.
  2. Sign on the right chain: buy = funding chain (chainIdIn); sell = always BSC; position = BSC wallet.
  3. Atomic bytecode: never send bytecode[] legs as separate transactions — see Executing Bytecode.
  4. Async completion: after the origin tx, Pods completes bridge (if crosschain) + order fill; monitor via the action status, position, or history.
  5. Sells use shares: pass amountInShares (18 decimals), not amount. Gate sells on wallet position (currentPositionInShares) and suw.phase !== 'awaiting_forward'.
  6. Wallet output: EOA → output=bytecode + EIP-7702; smart account → output=userOperation; Fireblocks → output=fireblocks + accountId.
  7. Same EVM wallet works across funding/payout chains; the market-share position always lives on BSC.
  8. Limit vs market: omit both params for a market CoW (100 bps + SLA validTo). Pass priceInUsd for a client limit (10 bps). expireAt only changes validTo and does not make the order cancellable — cancel requires priceInUsd.

Minimal example

Buy $10 of AAPL from Base (crosschain):
Buy with a $320.00 client limit (cents + optional expiry):
Cancel that order after funding, if it is still unfilled:
Sign response.bytecode (legs where Number(chainId) === 8453) on Base, then poll:
Sell shares, receive USDC on Arbitrum (crosschain):
Sign 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.