Docs
API https://api.orblivion.com Home

Trading

How a trade is planned, what comes back, how to sign and send it, and what each refusal means. The SDKs do all of this for you; read this to know what they check, or to call the API from another language.

The calls#

CallUse it toSignable?
POST /api/v1/live/prepareplan a trade: quote, policy, the unsigned transactions and a simulation of exactly those, in one call. Use this.yes
POST /api/v1/live/quotecompare routes. Read-only; never refuses for risk.no
POST /api/v1/live/buildthe same plan as prepare, without the simulationyes
POST /api/v1/live/simulatea dry run against live state, for your wallet or a synthetic onefor your wallet only
POST /api/v1/live/submitrelay transactions Orblivion built for you, once signed
POST /api/v1/live/fastfast mode: a major-pair trade your SDK built and signed, checked and relayed in one callyou sign it
POST /api/v1/live/revoketake your token approvals backyes

prepare makes two round trips to the chain, about 0.1 s in all. Every field is in the API reference.

Plan a trade#

HTTP
POST /api/v1/live/prepare
Authorization: Bearer pit_w_...
Content-Type: application/json

{
  "wallet": "0xYourAgentWallet",
  "token_in": "ETH",
  "token_out": "USDG",
  "notional_usd": 5
}
  • wallet is required and must be your key's wallet.
  • token_in, token_out: a core symbol (ETH, WETH, USDG, cbBTC, ORBIO, NVDA, SPY) or any token's contract address.
  • The size: notional_usd (dollars, at Orblivion's marks) or amount_in (atoms of the input, as a string). From $1 to $10,000 a trade (MIN_NOTIONAL, MAX_NOTIONAL).
  • Optional: slippage_bps, min_amount_out, accept_risks, shield and policies, max_fee_bps (the most the trade may pay; see Fees and gas), the approval and permit settings below, route_id (a route from an earlier quote), owner_attests_non_us (stock tokens).

The answer (abridged):

JSON
{
  "ready": true,
  "execution": "router",
  "wallet": "0xyouragentwallet",
  "token_in": "ETH", "token_out": "USDG",
  "amount_in": "1835200000000000",
  "notional_usd": 5,
  "route": {"id": "uni:v3:100:0bd7-5fc5", "label": "Uniswap v3 0.01%", "venue": "uniswap", "router": "0x204faca1764b154221e35c0d20abb3c525710498"},
  "quote": {"amount_out": "4996120", "vs_mid_bps": 1.6, "gas": 152300, "gas_usd": 0.0084},
  "amount_out_min": "4971140",
  "amount_out_expected": "4983630",
  "deadline": 1791168497,
  "fee": {"bps": 25, "side": "input", "recipient": "0x...", "token": "0x0000000000000000000000000000000000000000", "command": "TRANSFER", "amount": "4588000000000"},
  "steps": [{"kind": "swap", "why": "Uniswap v3 0.01%: ETH -> USDG", "tx": {"...": "..."}, "decoded": {"...": "..."}}],
  "tx": {"to": "0x204faca1764b154221e35c0d20abb3c525710498", "data": "0x3593564c...", "value": 1835200000000000, "chainId": 4663, "type": 2, "gas": 272359, "maxFeePerGas": 40168000, "maxPriorityFeePerGas": 0},
  "simulation": {"ok": true, "amount_out": "4983630", "fee_transfer": {"amount": "4588000000000", "exact": true}, "gas_total": 209507, "funded_by_overrides": false},
  "checks": [{"code": "MAX_SLIPPAGE", "ok": true, "detail": "50 bps of 300 max"}],
  "approval": null,
  "permit": null,
  "risks": [],
  "fee_bps": 25,
  "fee_class": "major",
  "fee_tier": 25,
  "fee_side": "input",
  "fee_token": "0x0000000000000000000000000000000000000000",
  "shield": false,
  "shield_available": true,
  "timings": {"quote_ms": 50.1, "simulate_ms": 53.5, "total_ms": 106.5}
}
  • ready is true when tx can be signed and sent as it is. It is false while a Permit2 signature is needed first, or when the simulation failed.
  • tx is the transaction to sign: an unsigned EIP-1559 transaction with a zero tip, maxFeePerGas at twice the base fee, and a gas limit 30% over the simulated gas.
  • steps lists every transaction or signature the trade needs, in order, each with what it is for (why) and Orblivion's decoding of it (decoded). tx is the last step's.
  • simulation ran exactly the transactions in steps against the latest block. funded_by_overrides: true means your wallet doesn't hold the input yet, so the simulation lent it the balance: a dry run, which the SDKs refuse to send.
  • checks are the policy checks that passed. A check that fails refuses the plan with its code.
  • risks, fee_bps, fee_class, fee_tier, fee_side, fee_token, shield are always at the top level. ETH to USDG is a major trade: 0.25% here, less at higher volume, paid in ETH from the input (ETH ranks above USDG: see which token pays the fee). See Tokens and risks and Fees and gas.

Execution shapes#

Every plan says how it executes in execution:

executionWhenWhat you sign
routeralmost every trade: Uniswap v2, v3 or v4 pools, graduated agent tokens includedone call to the Universal Router, execute(bytes,bytes[],uint256). Orblivion's fee is one command inside it: a fixed TRANSFER from the input, or a PAY_PORTION of the output.
curvean Orbio agent token still on its Pons bonding curve, from a plain wallettwo transactions: the fee transfer, then the curve's buy or sell paying your wallet (plus an exact approval to the curve when the input is a token). See Two-step curve trades.
batchthe same curve trade, from a wallet delegated to MetaMask's EIP7702StatelessDeleGatorone transaction from your wallet to itself that runs the fee transfer, the approval and the curve call atomically. See Batches.

Router plans pass through at most three hops, and only through WETH (or ETH), USDG and ORBIO. A plan names a v4 hook only when Orblivion knows it or disclosed it (see hooks).

Approvals and Permit2#

Native ETH needs no approval. A token input reaches the router through Permit2, which needs two things:

  1. An ERC-20 approval to Permit2, sent as its own transaction when what's left doesn't cover the trade. Its size follows approval_mode:
approval_modeThe approvalA new one is needed
budget (default)approval_budget_usd worth (default $25, at most $10,000), never less than the tradewhen the budget left doesn't cover the next trade
exactexactly this tradeevery trade (one more transaction, about $0.002 of gas)
unlimitedthe maximumnever. Only with allow_unlimited_approval: true, otherwise UNLIMITED_APPROVAL.
  1. A Permit2 allowance for the router, signed off-chain (EIP-712) and carried inside the swap. By default it is exactly this trade's amount, for 30 minutes (permit_scope: "trade"); "30d" allows the router up to the ERC-20 approval left for 30 days, so you sign once a month. A wallet that can't sign typed data can use permit_mode: "transaction", a separate Permit2.approve transaction instead.

Every plan from a token input carries approval: the mode, the budget, the allowance left, whether this trade needs a new approval and why (first, budget_exhausted or none), and what is left after the trade.

The permit round trip#

When the swap needs a Permit2 signature, the first prepare answers ready: false with what to sign:

JSON
{
  "ready": false,
  "permit": {"typed_data": {"domain": {"name": "Permit2", "chainId": 4663, "verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"}, "...": "..."}, "digest": "0x...", "then": "sign typed_data with the wallet and send the same body again with permit_signature and this permit_now"},
  "permit_now": 1791168377,
  "steps": [{"kind": "approve", "...": "..."}, {"kind": "sign_permit", "...": "..."}, {"kind": "swap", "needs_signature_first": true, "...": "..."}]
}
  1. If steps starts with an approve, check it (the token is your input, the spender is Permit2, the amount is what you expect for your mode), sign it and send it first.
  2. Check the typed data: domain Permit2 on chain 4663, the token is your input, the spender is the Universal Router, the amount is exactly this trade's, and the expiry is near. Rebuild the digest yourself and compare it with digest. Then sign it.
  3. Send the same body again with permit_signature and permit_now. The answer is ready: true, with the swap carrying your permit, simulated.

Taking approvals back#

POST /api/v1/live/revoke with your wallet (and tokens, default ["USDG"]) returns the transactions that undo them: approve(Permit2, 0) while an allowance is left, and Permit2.lockdown while the router holds a live Permit2 allowance. They are simulated against your wallet first; nothing_to_revoke: true means there is nothing left to undo. Send them like any other step.

Slippage and the minimum output#

Every trade carries an on-chain minimum: the transaction reverts rather than deliver less. Orblivion sets it from your slippage limit:

  • slippage_bps counts from the quote before Orblivion's fee. Without it, each pair has its own default for the size: the tokens' measured tolerance plus the fee (limits.slippage_tolerance_by_size in GET /api/v1/config).
  • Each pair has a cap, and no request may go above it (MAX_SLIPPAGE); the policy's ceiling is 300 bps.
  • amount_out_min is the quote less your slippage, after the fee. It is what the calldata enforces.
  • min_amount_out in the request is a floor of your own, in output atoms, such as a limit price. When no route can meet it now the plan is refused with LIMIT_NOT_REACHABLE, before anything is built.
  • The route itself is held to an independent price: the pair's reference pool mid for core pairs (ROUTE_VS_MID), and for other tokens a Chainlink feed, a time-weighted pool average or a median of distinct pools, where one exists (ROUTE_VS_REF). This catches a pool someone pushed just before your trade.
  • The deadline is 120 seconds from the build. A swap signed and left unsent expires.

The SDKs check all of this again before signing: the minimum must be at most your slippage under the quote and the simulation, and within 3% of the input's value at Chainlink prices they read themselves (MIN_OUT, QUOTE, PRICE). For a token without a feed they quote the exact route themselves over their own RPC (QUOTE_OWN).

Batches#

Orblivion builds one kind of batch: an EIP-7702 batch for a wallet whose code is MetaMask's EIP7702StatelessDeleGator v1.3.0. The wallet sends one transaction to itself, execute(bytes32 mode, bytes executionCalldata), in batch mode with the default exec type, so the calls run in order and all of them revert if one does:

  1. Orblivion's fee: a token transfer to the fee recipient, or an ETH transfer;
  2. for a token input to a curve, approve(curve, exactly the amount traded);
  3. the trade: the curve's buy or sell paying the wallet, or a Universal Router call (with no PAY_PORTION, since the batch's transfer is the fee).

That is the order when the fee comes from the input, as on every curve buy. A curve sale pays its fee in the pair (ORBIO or ETH, which outrank the token), so its batch runs approve(curve, the whole input), then the sell, then the fee: a transfer of exactly floor(min_out × bps / 10,000) of the pair, where min_out is the sale's guaranteed minimum. See which token pays the fee.

The SDKs read your wallet's code themselves and accept a batch only in exactly that shape (BATCH_* rules). A plain wallet gets the two-step form below instead. curve_fee_mode (auto, batch, two_step) picks one: auto uses the batch when the wallet is delegated, and batch from a plain wallet is refused (FEE_MODE).

Two-step curve trades#

An Orbio agent token trades on its Pons bonding curve until it graduates, and only against its pair token (usually ORBIO; CURVE_PAIR otherwise). The curve takes no fee parameter, so from a plain wallet the trade is two transactions:

  1. fee_transfer: Orblivion's fee, floor(amount_in × bps / 10,000) of the input, to the fee recipient (always the input here, a sale's token included, so the fee can't be skipped: fee_side: "input");
  2. swap: the curve's buy(amount, minOut, yourWallet) or sell(amount, minOut, yourWallet) with the rest.

Send them together in one submit, as {"raw_txs": [<approve>, <fee_transfer>, <swap>]} in order, signed with consecutive nonces. Orblivion sends the approval and the fee, waits up to 20 seconds for the fee to be mined, and sends the swap only once the fee has succeeded. It never relays a fee on its own (FEE_FIRST). Each step carries valid_until, its build plus 120 seconds; an expired plan is refused.

Curves also refuse two things Orblivion won't build: a buy in a token's first seconds, when the curve charges a snipe tax (SNIPE_TAX: try again a few seconds later), and a buy that would take the curve to graduation and be only partly filled (CURVE_GRADUATED: buy less, or trade the pool once it has graduated).

Relay and submit#

HTTP
POST /api/v1/live/submit
Authorization: Bearer pit_w_...
Content-Type: application/json

{"raw_tx": "0x02f9..."}

Orblivion relays a transaction only if it is as built: its to, value and calldata equal a step of a plan Orblivion built for the same API key in the last 30 minutes, and it is signed by the wallet that plan was for (or, for a session trade, its executor). Anything else is NOT_AS_BUILT, with the fields that differ in diffs. The check is one lookup, about a millisecond; the relay keeps a warm connection to the sequencer.

Answer
{
  "tx_hash": "0x...",
  "as_built": {"kind": "swap", "wallet": "0xyouragentwallet", "...": "..."},
  "timings": {"check_ms": 0.7, "relay_ms": 238.4, "total_ms": 239.1}
}
  • A server can run with relaying off; submit then answers 403 SUBMIT_DISABLED and you send the signed transaction through any Robinhood Chain RPC yourself. submit_enabled in GET /api/v1/config says which.
  • 502 SEQUENCER_UNREACHABLE means the sequencer didn't answer. The transaction may or may not have arrived: look for its receipt before you sign anything else with the same nonce.
  • 422 SUBMIT_REJECTED means the sequencer, or Orblivion's own decoding at the relay, refused it.

Transactions are public as soon as they are sequenced. See Security model.

Receipts#

Receipts come from the chain: poll any Robinhood Chain RPC with eth_getTransactionReceipt and the tx_hash that submit returned. With blocks every tenth of a second, a receipt is usually there within a second of sending. The SDKs poll for it themselves (several looks in flight, every 100 ms), then check what was mined against what they signed.

Fast mode#

For an agent that holds its own key and trades a major pair, fast mode cuts the path from decision to relay to one call. The SDK keeps what a trade needs warm in the background (its own Chainlink marks over your RPC, the wallet's nonce, the base fee, the input's allowances, and your fee tier from GET /api/v1/live/fast/terms), builds the transaction itself on a route both sides pin, signs it, and sends it to POST /api/v1/live/fast. Orblivion rebuilds the calldata with its own builder and relays only an exact match.

client = OrblivionClient(account=Account.from_key(key))
fast = client.fast().start()              # keeps marks, nonce, gas, allowances and your fee tier warm
r = fast.trade("ETH", "USDG", amount_usd=10)
print(r.path, r.tx_hash, r.timings["decision_to_accepted_ms"])
  • Pairs: ETH, WETH, USDG, NVDA and SPY, both ways where a deep pool without a hook exists (the terms list them). Stock tokens only while the US stock-token session is open. cbBTC, ORBIO, Orbio agents, Pons curves and long-tail tokens are never fast: they go through prepare (your SDK takes the normal path for them before it signs anything).
  • Each route has a size cap (routes.max_usd in the terms): the largest size, at Orblivion's marks, at which its pinned pool was measured to stay within 10 bps (at $1,000) or 25 bps (above) of the best route prepare would take. A larger trade takes the normal path before your SDK signs anything (ROUTE_CAP; Orblivion refuses it too).
  • The minimum comes from your SDK's own quote of the pinned route (kept warm, at most 3 s old, scaled to the size) less the tolerance, at most Orblivion's default for the pair, as prepare sets it from its own quote. The pool must trade within the pair's band of the Chainlink marks beyond its own fee: 75 bps for a pair against USDG, 125 for a cross pair such as ETH and SPY. Further under, your SDK takes the normal path (POOL_FAR), and Orblivion refuses the pool's simulated delivery too. Orblivion also refuses a minimum more than the pair's cap under what the trade delivers now (MIN_OUT, so a stale quote can't sell you cheap), any minimum too far under its own marks, and, on NVDA and SPY pairs, a delivery too far under the reference pools' mid (ROUTE_VS_MID).
  • The size is checked twice: your SDK holds its RPC's Chainlink marks to Orblivion's before it sizes a trade (MARKS_DIFFER takes the normal path), and the request carries the USD you asked for and your client's notional cap, which Orblivion holds the signed amount to at its own marks (AMOUNT_USD, CLIENT_CAP). A wrong or lying RPC can't turn $100 into $10,000.
  • The fee is your wallet's major tier (and Shield's add-on), in ETH, WETH or USDG by the fee currency rule, to Orblivion's pinned recipient: exactly what prepare would build.
  • Falling back: anything stale, an ineligible pair, or a first trade from a token you haven't approved to Permit2 yet, and the SDK takes the normal path before it signs anything (r.path == "fallback"). Pass fallback=False to get the error instead.
  • One trade, never two. Once a fast transaction is signed, the SDK signs another only after a refusal Orblivion answers before relaying, and only after asking your own RPC about the first: if the chain has it, that is the trade (r.note says so); if your wallet's nonce has moved past it, the next transaction takes a fresh nonce; otherwise the retry or the normal path's first transaction reuses the same nonce, so at most one of them can land. A refusal that may follow a relay (NONCE without that proof, IN_FLIGHT, SEQUENCER_UNREACHABLE, SUBMIT_REJECTED, or no answer at all) raises with the hash: look it up before trading again.
  • simulate=False skips the server's simulation of the exact signed transaction (one round trip to the node, about 50 to 220 ms). Every other check still runs, the wallet must hold what the transaction spends, and such relays are limited to one a second per key; a transaction that would revert is then relayed and costs its gas.
  • Dry run: dry_run=True sends check_only: Orblivion runs every check and the simulation, and relays nothing.

Where to host your agent#

Every trade ends with a round trip to Robinhood Chain's sequencer, and fast mode leaves little else. Run your agent in US East, close to the sequencer and to Orblivion's servers, and use an RPC near it. Measured on 7 Oct 2026 from a development machine far from US East: the sequencer was 246 ms away on a kept-alive connection (241 ms to open one), and the two public RPCs the SDKs use by default 49 ms and 211 ms. From there each of those is paid on every trade; from US East they should be far shorter, which wasn't measured.

Errors#

Every refusal is JSON with a stable code. The common ones while trading:

CodeStatusWhat to do
RISK_NOT_ACCEPTED422the trade carries severe risks you didn't accept; they are in unaccepted. Read them, then accept them by name or don't trade.
RISK_BLOCKED422a Dossier Shield policy refused it (blocked, policy).
NO_ROUTE422no venue Orblivion can build a trade through.
MAX_SLIPPAGE, MAX_NOTIONAL, MIN_NOTIONAL422the request is outside the pair's slippage cap or the size limits.
LIMIT_NOT_REACHABLE422your min_amount_out can't be met now.
ROUTE_VS_MID, ROUTE_VS_REF422the route's price is too far from the reference; try again, or a smaller size.
FEE_FIXED422the request named a fee; the server sets it from the trade's class, the wallet's tier and Shield. Cap it with max_fee_bps instead.
FEE_BPS422the plan's fee would be above your max_fee_bps. Raise the cap, or don't trade.
WALLET_MISMATCH403the wallet isn't your key's.
NOT_AS_BUILT422the signed transaction isn't one Orblivion built for this key.
TOKEN_CHANGED409the token's proxy now runs other code than when it was screened; it is screened again, so retry shortly.
RATE_LIMITED429wait for Retry-After seconds.

The full list, with every status, is on Errors.