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.

Overview

Flow summary
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.

Integration flow


1. Market status

Gate buy/sell when the market is closed. Optionally pass a GM token symbol to enrich the same response with per-asset tradability under asset.
The response shape is always the Ondo GM market-wide payload plus an asset field:
  • without symbol: asset is null
  • 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 query

symbol is the Ondo GM 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 GM 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 }. Pick the strategy where:
  • asset matches the market-share token address
  • networkId === 56
Use actions request-lend (buy) and request-withdraw (sell) from availableActions.

4. Buy — request-lend

Same-chain (BSC): fromChainId=56, fromTokenAddress=0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d (18 decimals). The client signs on BSC. 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. 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 — request-withdraw

Same-chain (BSC): toChainId=56, toTokenAddress=0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d. The client signs 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.
  • 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 async.

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
Use fromActions=1 for correct accounting (principal, profit, in-flight operations).

Position shape

requestedTo* fields

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

What appears in actions[]

Root response (always)

Ondo Global Markets strategies only — root also includes orderBook:
Present even when actions is empty.

Each item in actions[]

Base fields (every pending action)
Cross-chain pending actions include only the base block (no suw). suw block (Ondo Global Markets request-lend / request-withdraw only)

suw.phase lifecycle

suw.order shapes

Not posted yet (posted: false):
Posted / live (posted: true): adds validTo, creationDate, executionDate, executedSellAmount, executedBuyAmount, txHash. Typical status values: open, fulfilled, unknown (with providerError when lookup fails).

Empty pending example

Suggested polling: every 5–15 s while hasPending is true, or until suw.phase reaches a terminal value. Example
Non-Ondo strategies return the same root envelope without orderBook or suw.
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

Embedded in GET /v2/wallets/:address?include=all&fromActions=1 or:
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: request-lend (buy) and request-withdraw (sell). 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.

Minimal example

Buy $10 of AAPL from Base (crosschain):
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.