Skip to main content

What It Does

One product, one account, two asset families:
  • Tokenized equities (Ondo) — fractional-share ERC-20s traded via 1inch Fusion intent orders (this page’s main flow).
  • RWA yield tokens (Midas) — NAV-accruing ERC-20s (mTBILL, mBASIS, mBTC) traded via swap-based transact/buy / transact/sell. See RWA Yield Assets below.
Both settle into the same Tokenized Assets Account per owner. Let your users buy and sell tokenized equities — real, on-chain ERC-20 tokens issued by Ondo Global Markets that represent fractional shares of stocks like Tesla, Apple, and Nvidia. Trades are routed through 1inch Fusion, an intent-based protocol where the user signs an off-chain order and professional market makers fill it on-chain. You call our API, we return either an unsigned transaction or an EIP-712 payload to sign. Users sign with their own wallet, tokens settle to the user’s Tokenized Assets Account — an isolated product account at a deterministic address per owner. Tokens move in and out of that account, never the owner’s wallet directly. This is distinct from Global Markets: that product offers perpetual futures on Hyperliquid (synthetic exposure, no token ownership). Tokenized Assets gives users actual ERC-20 tokens (Ondo equities, Midas RWA yield tokens), settled on-chain. Important: Users must first create a Tokenized Assets Account before placing orders.

Supported Markets

Ondo lists 250+ tokenized US equities on Ethereum. Use /v2/tokenized_assets/markets to fetch the live catalog with current prices, 24h price change, and sector tags. Examples:
SymbolUnderlyingSector
TSLAonTSLAConsumer Discretionary
AAPLonAAPLTechnology
NVDAonNVDATechnology
SPYonSPY (ETF)Broad Market
Ethereum mainnet only.

End-to-End Flow

  1. One-time: create a Tokenized Assets Account for the owner.
  2. Per order: build → sign the approval if returned + sign the order → relay the approval → submit the order → poll for status.
Cancellation uses the same approve-and-relay pattern as the allowance approval.

1. Create a Tokenized Assets Account (one-time per owner)

POST /v2/tokenized_assets/create_account returns the deterministic account address for owner and an unsigned creation transaction. If the account already exists, transaction is null and you can skip straight to building orders.
import httpx

create = httpx.post(
    "https://api.compasslabs.ai/v2/tokenized_assets/create_account",
    json={
        "sender": "0xYourOwnerAddress",
        "owner":  "0xYourOwnerAddress",
    },
    headers={"x-api-key": "YOUR_API_KEY"},
).json()

account_address = create["tokenized_assets_account_address"]
if create["transaction"] is not None:
    # Sign and broadcast `create["transaction"]` with the owner's wallet.
    ...
The account address is deterministic — calling create twice for the same owner returns the same address with transaction: null on the second call. You can cache it client-side after the first deploy.

2. Build an Order

POST /v2/tokenized_assets/order returns three things in one round-trip:
  • quote — preview of the input/output amounts and fees.
  • approval_safe_tx_eip712 — present only when the account’s allowance to the settlement contract is below amount. The owner signs it once per token; subsequent orders against the same from_token skip this step. null when the allowance is already sufficient.
  • order — the order metadata (order_hash, order_message, extension, quote_id) plus safe_message_eip712, an EIP-712 payload the owner signs off-chain to authorize the order.
build = httpx.post(
    "https://api.compasslabs.ai/v2/tokenized_assets/order",
    json={
        "from_token":   "USDC",       # symbol or 0x-prefixed Ethereum address
        "to_token":     "TSLAon",
        "amount":       "100",        # 100 USDC, decimals applied server-side
        "owner":        "0xYourOwnerAddress",
        "slippage_bps": 50,           # 0.5%, range 1–1000 (max 10%)
    },
    headers={"x-api-key": "YOUR_API_KEY"},
).json()
If the owner’s Tokenized Assets Account doesn’t exist yet, the API returns 400 ACCOUNT_NOT_DEPLOYED — go back to step 1. If from_token or to_token isn’t a supported tokenized equity (or the literal USDC), the API returns 404 MARKET_NOT_FOUND.

3. Sign the Approval (only when returned)

When build.approval_safe_tx_eip712 is non-null, the owner signs it with signTypedData. The payload is a one-time max-approve for the settlement contract; once it’s on-chain, future orders against the same from_token skip this step entirely.
import { createWalletClient, custom } from "viem";

const wallet = createWalletClient({ transport: custom(window.ethereum) });

let approvalSignature: `0x${string}` | undefined;
if (build.approval_safe_tx_eip712) {
  approvalSignature = await wallet.signTypedData({
    account:     ownerAddress,
    domain:      build.approval_safe_tx_eip712.domain,
    types:       build.approval_safe_tx_eip712.types,
    primaryType: build.approval_safe_tx_eip712.primaryType,   // "SafeTx"
    message:     build.approval_safe_tx_eip712.message,
  });
}
The signature is not yet on-chain — broadcasting happens in the next step.

Relay the approval

Submit the signed approval to POST /v2/gas_sponsorship/prepare with the sender who will pay gas (typically your relayer). The endpoint returns an unsigned transaction for sender to sign and broadcast. If you’d rather have the owner pay their own gas, the owner can broadcast the execTransaction against the product account directly instead of going through gas sponsorship.
if build["approval_safe_tx_eip712"] is not None:
    prep = httpx.post(
        "https://api.compasslabs.ai/v2/gas_sponsorship/prepare",
        json={
            "chain":     "ethereum",
            "product":   "tokenized_assets",
            "owner":     "0xYourOwnerAddress",
            "sender":    "0xYourSponsorAddress",
            "eip_712":   build["approval_safe_tx_eip712"],
            "signature": approval_signature,
        },
        headers={"x-api-key": "YOUR_API_KEY"},
    ).json()
    # `prep["transaction"]` is an unsigned execTransaction — sign with the sponsor key and broadcast.
Wait for the approval transaction to confirm before submitting the order — if you submit too early, the order will simply fail to fill (no resolver can pull the maker tokens) and surface as FUSION_NO_LIQUIDITY or a 502 from the relayer.

4. Sign the Order

The owner signs order.safe_message_eip712 — an EIP-712 payload that authorizes the order’s order_hash for the Tokenized Assets Account. Unlike the approval, this signature does not get broadcast on-chain: it’s relayed off-chain to the resolver network, which validates it against the product account at fill time.
const orderSignature = await wallet.signTypedData({
  account:     ownerAddress,
  domain:      build.order.safe_message_eip712.domain,
  types:       build.order.safe_message_eip712.types,
  primaryType: build.order.safe_message_eip712.primaryType,   // "SafeMessage"
  message:     build.order.safe_message_eip712.message,       // { message: "0x<orderHash>" }
});

5. Submit the Signed Order

POST /v2/tokenized_assets/order/submit relays the signature to the resolver network. The body echoes the order fields from the build step plus the owner’s signature.
submit = httpx.post(
    "https://api.compasslabs.ai/v2/tokenized_assets/order/submit",
    json={
        "signed_order": build["order"]["order_message"],
        "signature":    order_signature,
        "extension":    build["order"]["extension"],
        "quote_id":     build["order"]["quote_id"],
        "order_hash":   build["order"]["order_hash"],   # optional, recommended
    },
    headers={"x-api-key": "YOUR_API_KEY"},
).json()

order_hash   = submit["order_hash"]
submitted_at = submit["submitted_at"]   # ISO 8601 UTC timestamp
signed_order.maker is the Tokenized Assets Account, not the owner’s wallet — pass the dict back unchanged. Including order_hash is optional but recommended: the upstream relayer occasionally returns a 2xx response with an empty body, and supplying the hash lets the API still return a usable handle for status / cancel lookups.

6. Poll for Fill Status

GET /v2/tokenized_assets/order/{order_hash} returns the lifecycle state:
  • pending — waiting for a resolver to fill (covers partial fills too; filled_amount is populated as fills come in).
  • filled — fully settled. fill_tx_hash and filled_amount are populated. The output token lands in the Tokenized Assets Account.
  • expired — auction window closed without a fill.
  • cancelled — the order was cancelled on-chain before it filled.
status = httpx.get(
    f"https://api.compasslabs.ai/v2/tokenized_assets/order/{order_hash}",
    headers={"x-api-key": "YOUR_API_KEY"},
).json()
print(status["status"], status.get("fill_tx_hash"))

Cancel an Unfilled Order

POST /v2/tokenized_assets/order/{order_hash}/cancel returns cancel_safe_tx_eip712 — an EIP-712 payload that, when signed and broadcast, cancels the order on-chain. Cancellation follows the same pattern as the approval: owner signs, relayer (or owner) broadcasts. Cancellation works on pending and expired orders; calling it on a filled or already-cancelled order returns 409.
cancel = httpx.post(
    f"https://api.compasslabs.ai/v2/tokenized_assets/order/{order_hash}/cancel",
    json={"owner": "0xYourOwnerAddress"},
    headers={"x-api-key": "YOUR_API_KEY"},
).json()
# 1) Owner signs cancel["cancel_safe_tx_eip712"] with signTypedData.
# 2) POST {chain, product: "tokenized_assets", owner, sender, eip_712, signature} to /v2/gas_sponsorship/prepare.
# 3) Sponsor broadcasts the returned execTransaction.
Only the Tokenized Assets Account that placed the order can cancel it — the API rejects mismatches with 403 OWNER_NOT_MAKER.

Read Markets and Candles

Use GET /v2/tokenized_assets/markets/{symbol} to fetch a single market with extended detail (52-week range, volume, market cap, holder count, tradable sessions) and an optional OHLC candle series. Pass interval and range together to include candles. They must form one of the supported pairs:
intervalAllowed range values
1min1day
5min1day
15min1day
1hour1month
4hour1month
12hour3month
1day3month, 6month, 1year, all
Passing only one of the two, or an unsupported pair, returns 400. Omitting both returns the market detail without a candles field.
Python
detail = httpx.get(
    "https://api.compasslabs.ai/v2/tokenized_assets/markets/TSLAon",
    params={"interval": "1hour", "range": "1month"},
    headers={"x-api-key": "YOUR_API_KEY"},
).json()

Read Positions

GET /v2/tokenized_assets/positions?owner=0x... returns ERC-20 balances of every Ondo tokenized equity held in the owner’s Tokenized Assets Account, enriched with current USD prices and a total_usd aggregate. Pass the owner’s wallet address — the API derives the Tokenized Assets Account address from it and reads balances from the derived account (proceeds from filled orders land there, not in the owner’s wallet). If the account hasn’t been created yet, the call returns 400 ACCOUNT_NOT_DEPLOYED.
Python
positions = httpx.get(
    "https://api.compasslabs.ai/v2/tokenized_assets/positions",
    params={"owner": "0xYourOwnerAddress"},
    headers={"x-api-key": "YOUR_API_KEY"},
).json()

Errors

All 4xx/5xx responses use the standard Compass { "error": "<CODE>", "message": "<text>" } envelope, except standard input-validation 422s — those come from FastAPI in the form { "detail": [...] }. The codes below are the ones Compass raises with the envelope:
CodeStatusWhen
ACCOUNT_NOT_DEPLOYED400The owner doesn’t have a Tokenized Assets Account yet — call /create_account first.
MARKET_NOT_FOUND404Unknown symbol on a markets read, or from_token/to_token cannot be resolved on /order / /quote.
ORDER_NOT_FOUND404Unknown order hash on status or cancel.
OWNER_NOT_MAKER403The Tokenized Assets Account predicted from owner is not the order’s maker.
FUSION_NO_LIQUIDITY409No resolver could fill at this size — try a smaller size or retry shortly.
ORDER_ALREADY_FILLED409Cancel attempted on a filled order.
ORDER_ALREADY_CANCELLED409Cancel attempted on an already-cancelled order.
FUSION_QUOTE_EXPIRED410Quote stale at submit time — re-quote and retry.
FUSION_SLIPPAGE_EXCEEDED422On /order: Fusion’s auction may fill below your slippage_bps tolerance — raise slippage_bps or pick a more liquid market.
ONDO_API_UNAVAILABLE502Ondo read API (markets, prices, order status) is unavailable.
FUSION_API_UNAVAILABLE5021inch Fusion API (quote, order, submit, cancel) is unavailable.

Key Concepts

The maker is the Tokenized Assets Account, not the owner’s wallet. The product account holds the input tokens, signs orders, and receives the output tokens. The owner’s wallet is its sole controller and signs all EIP-712 payloads off-chain. Three EIP-712 payloads, two purposes.
  • approval_safe_tx_eip712 and cancel_safe_tx_eip712 authorize on-chain transactions (allowance approval and order cancellation). The owner signs them, then a relayer or the owner broadcasts the resulting transaction.
  • order.safe_message_eip712 authorizes the off-chain order sent to the resolver auction. The signature goes back to /order/submit and is never broadcast.
Approval is one-time per token. The API requests an unlimited approval, so subsequent orders against the same from_token skip step 3 entirely. Don’t inspect extension or quote_id. They are opaque values from the upstream quote — pass them back to /order/submit unchanged. Status mapping. The upstream protocol publishes finer-grained states (partially-filled, refunded, false-predicate, …) which Compass collapses onto the 4-value enum (pending, filled, expired, cancelled). Unknown upstream states return 502 rather than guessing.

Use Cases

Brokerage app for retail. Let users buy fractional Tesla / Apple shares with USDC, hold them as on-chain tokens, and trade out at any time without going through a centralized broker. On-chain portfolio diversification. Crypto-native users get equity exposure without leaving their wallet — same UX as a token swap, settlement is just-in-time on Ethereum. Treasury allocation. DAOs and on-chain orgs can rebalance treasuries into tokenized equities while keeping signing authority with the owner wallet — no third-party custody.

Next Steps

Product Accounts

Background on the per-product account model used here.

Gas Sponsorship

How /v2/gas_sponsorship/prepare broadcasts the signed approval and cancel transactions.

Global Markets

Synthetic stock perps via Hyperliquid — different product, leverage available.

RWA Yield Assets (Midas)

Alongside equities, the same product account holds and trades Midas RWA yield tokens — permissionless ERC-20s whose NAV accrues yield from real-world assets:
SymbolUnderlyingAsset classNetworks
mTBILLUS Treasury BillsT_BILLSEthereum, Base
mBASISCrypto basis tradingBASIS_TRADEEthereum, Base
mBTCBTC yieldBTC_YIELDEthereum
These trade on the secondary market via the 1inch Aggregation Router — mint/redeem at par with the issuer is not offered.

Discover markets

GET /v2/tokenized_assets/markets returns equities and RWA assets in one list. Each entry carries provider (ondo | midas), asset_class, and chain; RWA entries add apy_7d / apy_30d (trailing annualized NAV growth — null until enough history exists) and tvl_usd. Filter with the provider, asset_class, and chain query params.

Trade

RWA assets do not use the order endpoints. Instead:
  1. Fund the account — transfer the input token (e.g. USDC) to the Tokenized Assets Account address (plain ERC-20 transfer).
  2. BuyPOST /v2/tokenized_assets/transact/buy with token_in, token_out (the RWA symbol), amount_in, slippage, owner, chain. Returns an unsigned transaction executing the swap inside the product account (or an EIP-712 payload with gas_sponsorship: true), plus estimated_amount_out.
  3. SellPOST /v2/tokenized_assets/transact/sell with the RWA symbol as token_in.
Passing an equity symbol to transact/buy/transact/sell returns a 422 pointing back to the order flow, and vice versa: RWA symbols are not valid in /quote or /order.

Positions

GET /v2/tokenized_assets/positions includes RWA holdings next to equities, valued at the latest indexed NAV. Pass chain=base to read Base positions (equities are Ethereum-only). On Base, create the account first with POST /v2/tokenized_assets/create_account and "chain": "base" — the account address is identical on every network.