# Ophis Docs (full text) > The complete docs.ophis.fi corpus in one file, generated at build time for LLM context windows. Each section header carries the canonical per-page URL. Index: https://docs.ophis.fi/llms.txt --- # Ophis DEX Aggregator Documentation URL: https://docs.ophis.fi/ # Ophis **Ophis is an intent-based DEX aggregator.** You describe a swap in plain English, _"swap 100 USDC for ETH on Base"_, and a competitive solver auction fills it at the best price. The trade settles on-chain with MEV protection built in, and you sign every order with your own wallet. Ophis is a fork of [CoW Protocol](https://docs.cow.fi/cow-protocol)'s frontend with an added natural-language intent-parsing layer. The full source is at [github.com/ophis-fi/ophis](https://github.com/ophis-fi/ophis). ``` swap 100 USDC for ETH on Base │ ▼ parse intent → sign order → solver auction → settle ``` ## Core principles - **Intent-based, not router-based.** You sign your _desired outcome_; solvers compete on _how_ to deliver it. - **MEV-protected by design.** Orders settle through a batch auction where trades clear at a uniform price, mitigating front-running and sandwiching. This is a mechanism-level defense, not an absolute guarantee against every adversarial condition. - **Self-custodial.** Ophis never holds funds. Every order is signed by your wallet (EIP-712 or ERC-1271) and executed by an authorized solver from the allowlisted solver set. - **Transparent, chain-aware fees.** Every supported chain charges a 1 bp base plus 80% of reference-quote improvement on volatile pairs (99 bps cap), or 50% on stable pairs (20 bps cap). On CoW-hosted chains, [CoW Protocol's own fees apply on top](./fees.md). - **Open.** The full frontend, intent-parser proxy, and infra runbooks are public. ## What's in these docs | Section | What you'll find | | --- | --- | | [Getting started](./getting-started.md) | Make your first swap; how the three-step flow works; supported networks. | | [How it works](./architecture.md) | Intent lifecycle, batch auctions, the parser proxy, and settlement. | | [Fees & rebates](./fees.md) | All-chain price-improvement pricing, hosted upstream fees, and rebates. | | [Affiliate program](./affiliate.md) | Share a referral code, earn 8% of the net fee on every trade your referrals route. | | [Intent API](./intent-api.md) | The public `POST /api/intent` endpoint, parse English into a structured order. | | [AI agent integration](./ai-agents.md) | Wire the intent API into LangChain, AutoGPT, or your own agent. | | [Security & audits](./audits.md) | Custody model, settlement contracts, and audit posture. | | [FAQ](./faq.mdx) | Common questions about fees, networks, MEV, and custody. | ## Quick links - **App:** [ophis.fi](https://ophis.fi) - **Business portal:** [business.ophis.fi](https://business.ophis.fi) - **Machine-readable summary:** [ophis.fi/llms.txt](https://ophis.fi/llms.txt) - **OpenAPI spec:** [ophis.fi/openapi.json](https://ophis.fi/openapi.json) - **Source:** [github.com/ophis-fi/ophis](https://github.com/ophis-fi/ophis) --- # Getting started URL: https://docs.ophis.fi/getting-started # Getting started Ophis turns a sentence into a settled, MEV-protected swap. There's no token-picker to fight and no router to configure, you describe the trade, review it, and sign. ## Your first swap 1. Open [ophis.fi](https://ophis.fi) and connect a wallet. 2. Type what you want in your own words, for example: - `swap 100 USDC for ETH on Base` - `buy 50 USDC of PEPE on ethereum` - `1 ETH to USDC on op` 3. Ophis parses your sentence into a structured order (sell token, buy token, amount, chain) and pre-fills the swap form. 4. Review the quote and **sign the order with your wallet.** Nothing leaves your wallet until a solver settles the trade. :::note[Non-custodial by design] Ophis never takes possession of your funds. The signed order is broadcast to the solver auction; your tokens move only when a solver settles the batch on-chain. ::: ## How it works Three steps from your sentence to settlement: ### 1 · Intent, parse the request You type the swap as a sentence. An open LLM (LibertAI's Qwen 3.6 27B, hosted on Aleph Cloud) extracts the sell token, buy token, amount, and chain into a structured order. The model runs behind a server-side proxy so its API key never reaches the browser. See the [Intent API](./intent-api.md) for the public endpoint. ### 2 · Auction, solver competition Your signed order is broadcast to a batch auction. Solvers race to find the best path, an on-chain DEX, a peer-to-peer match against another order in the same batch, or a cross-chain route, and bid for the right to settle it. On Optimism, Unichain, and Robinhood Chain, Ophis currently operates the solver itself, competing across several routing strategies, see [How it works](./architecture.md). ### 3 · Settle, on-chain, MEV-protected The winning solver settles your order in a batch where every trade clears at the same uniform price. Your signed limit price is enforced on-chain, and Ophis orders are not exposed as public-mempool router swaps. On Robinhood Chain, the sequencer is first-come-first-served, so paying a higher priority fee does not buy an earlier place in the ordering. For the full lifecycle, see [How it works](./architecture.md). ## Supported networks Ophis surfaces **13 EVM chains** as full source _and_ destination in the network selector: Ethereum, Arbitrum One, Avalanche, Base, BNB Smart Chain, Gnosis Chain, Ink, Linea, Optimism, Plasma, Polygon, Robinhood Chain, and Unichain (plus the Sepolia testnet). On any of these you can both pay from and receive into your EVM wallet. | | | | | -------- | --------------- | --------------- | | Ethereum | Arbitrum One | Avalanche | | Base | BNB Smart Chain | Gnosis Chain | | Ink | Linea | Optimism | | Plasma | Polygon | Robinhood Chain | | Unichain | | | In addition, **Solana** and **Bitcoin** are available as cross-chain _destinations only_ via [NEAR Intents](https://near.org/intents): trade from any EVM source chain to those networks without a second wallet. They are not source chains, you cannot start a swap from a Solana or Bitcoin balance. You paste a destination address and sign with your EVM wallet; NEAR Intents brokers the bridge. The current token catalog covers stablecoins, ETH/BTC pegs, DeFi blue-chips, AI/RWA, memes, and gaming. The in-app token selector is the live source of truth because token availability can change by chain. ### Robinhood Stock Tokens Robinhood Chain includes tokenized equities and ETFs. Ophis verifies a selected Stock Token against Robinhood's live canonical deployment registry and shows its corporate-action multiplier and trading restrictions in the swap form. A split can change the share-equivalent display without rebasing the raw ERC-20 balance; the executable trade price remains the signed Ophis solver quote. - [Robinhood Chain network configuration](https://docs.robinhood.com/chain/connecting/) - [Stock Token integration and multiplier](https://docs.robinhood.com/chain/stock-tokens/) - [Bridge assets to Robinhood Chain](https://docs.robinhood.com/chain/bridging/) Swaps are gasless. Wallet approvals, wrapping, and other direct transactions still require ETH. Robinhood's public RPC is rate-limited; production integrations should use a supervised provider endpoint and reserve the public RPC for wallet configuration and fallback use. :::tip[Building on Ophis?] Skip the UI entirely, the [Intent API](./intent-api.md) exposes the same natural-language parser as a public endpoint, and the [AI agent guide](./ai-agents.md) shows how to wire it into an agent. ::: --- # How it works URL: https://docs.ophis.fi/architecture # How it works Ophis sits on top of [CoW Protocol](https://docs.cow.fi/cow-protocol)'s batch-auction settlement layer and adds a natural-language front door. This page traces a swap from a sentence to an on-chain settlement. ## The intent lifecycle ``` plain English structured order batch auction settlement ┌──────────────┐ parse ┌──────────────────┐ sign ┌──────────────────┐ ┌──────────────┐ │ "swap 100 │ ─────▶ │ sell: USDC │ ───▶ │ solvers compete │ ─▶ │ uniform-price│ │ USDC for │ LLM │ buy: ETH │ wallet│ for best execution│ │ on-chain │ │ ETH on Base"│ │ amount: 100 │ │ (DEX / P2P / xchain)│ │ settlement │ └──────────────┘ │ chain: base │ └──────────────────┘ └──────────────┘ └──────────────────┘ ``` ### 1. Intent parsing Free-form text is sent to a [Cloudflare Pages Function](./intent-api.md) that proxies [LibertAI](https://libertai.io)'s **Qwen 3.6 27B** (open-weights, hosted on Aleph Cloud) with a pinned system prompt and `temperature: 0` for deterministic extraction. The proxy: - holds the LibertAI API key server-side (browsers never see it), - validates extracted tokens against an internal allowlist of 236 DEX-traded symbols, - validates the chain against the set the network selector can actually route to, and - returns a structured `ParsedIntent` the UI uses to pre-fill the form. The parser **only normalizes language**. It never places, signs, or executes a trade, that is always the user's wallet on the frontend. ### 2. Order signing Once the form is filled, the user signs an order with their own wallet (EIP-712 for EOAs, ERC-1271 for smart-contract wallets). The signature authorizes a _limit_, a minimum acceptable output, not a specific execution path. Solvers may only do better than the limit, never worse. ### 3. Batch auction & solver competition Signed orders collect into batches. For each batch, solvers search for the best way to settle every order simultaneously, routing through on-chain liquidity, matching orders against each other peer-to-peer (no liquidity pool needed), or bridging cross-chain. Solvers bid, and the one that maximises total surplus wins the right to settle. On Optimism, Unichain, and Robinhood Chain, where Ophis runs its own stack. Ophis currently operates the solver itself, competing across several routing strategies (a baseline on-chain router plus multiple DEX aggregators) that bid against each other per batch, so there is genuine price competition even though the solver is Ophis-operated. The on-chain allowlist (`GPv2AllowListAuthentication`) controls who may settle, and additional independent solvers can be authorized over time. On the CoW-hosted chains Ophis surfaces, CoW's established solver network competes. Your protection is identical either way: the limit price in your signed order is enforced on-chain, so any solver can only fill it at or better than the price you signed. ### 4. Uniform-price settlement The winning solver settles the batch on-chain. Every trade in a batch clears at the **same uniform price**, which is what eliminates these order-level MEV vectors by construction: - **No front-running**, there's no pending-order mempool race to win. - **No sandwiching**, the protocol does not reorder trades for value. - **No priority-gas auction**, execution order inside a batch is not for sale. ## What Ophis runs | Component | Description | | --- | --- | | **Frontend** | A fork of the CoW Swap frontend with the natural-language intent layer added. | | **Intent-parser proxy** | A Cloudflare Pages Function in front of LibertAI Qwen 3.6 27B. See [Intent API](./intent-api.md). | | **Self-hosted orderbook & solver** | On Optimism, Unichain, and Robinhood Chain, Ophis runs its own CoW Protocol orderbooks (`optimism-mainnet.ophis.fi`, `unichain-mainnet.ophis.fi`, and `robinhood-mainnet.ophis.fi`) and operates the solver. CoW-aligned chains use `api.cow.fi` and CoW's solver network. | | **Settlement contracts** | CoW Protocol's `GPv2Settlement` (unchanged code), deployed and operated by Ophis on Optimism, Unichain, and Robinhood Chain, alongside Ophis-specific allowlist + fee-handling contracts. See [Security & audits](./audits.md). | | **Rebate indexer** | Indexes volume-tier rebates that accrue to traders. See [Fees & rebates](./fees.md). | ## Cross-chain via NEAR Intents Solana and Bitcoin are available as **output destinations**. When a swap targets one of them, [NEAR Intents](https://near.org/intents) brokers the bridge: the user signs with their EVM wallet and provides a destination address on the target network. No second wallet, no manual bridging step. ## Where to go next - Make a swap: [Getting started](./getting-started.md) - Integrate programmatically: [Intent API](./intent-api.md) · [AI agents](./ai-agents.md) - Fee mechanics: [Fees & rebates](./fees.md) --- # Fees & rebates URL: https://docs.ophis.fi/fees # Fees & rebates Every supported chain pays a **1 bp Ophis base fee**. On every chain, Ophis also retains **80% of price improvement on volatile pairs, capped at 99 bps of volume**, or **50% on stablecoin pairs, capped at 20 bps**. On CoW-hosted chains, CoW Protocol applies its own upstream fee policy separately. What you pay **all-in** depends on where the order settles, so here is the complete number per chain type, with nothing left out: ## The all-in cost, per chain | | Ophis-operated chains (Optimism, Unichain, Robinhood Chain) | CoW-hosted chains (the other 10) | | --- | --- | --- | | Ophis fee | 0.01% base + 80% of price improvement (50% stables), capped at 0.99% (0.20% stables) | Same Ophis policy: 0.01% base + capped improvement capture | | Upstream protocol fee | **None** | CoW Protocol volume fee: 0.02% (0.003% on correlated pairs such as stablecoins) | | **All-in fixed cost** | **0.01%** | **0.03% volatile / 0.013% correlated stables** | | Price improvement | Trader receives the remainder after Ophis's capped capture; all improvement above the cap returns to the trader | Ophis's capped capture applies, and CoW Protocol's upstream improvement policy applies separately | Why the difference: on the 10 CoW-hosted chains, orders settle through CoW Protocol's hosted orderbook and solver network, which charges its own [protocol fees](https://docs.cow.fi/governance/fees) on top of the Ophis fee. On **Optimism, Unichain, and Robinhood Chain**, Ophis operates the entire stack itself (settlement contracts, orderbook, solvers), so there is no upstream fee. The 1 bp base and capped price-improvement policy are the complete Ophis charge. ## How it works - A **1 bp base fee** is applied on Ophis-operated chains. - Volatile pairs add 80% of reference-quote improvement, capped at 99 bps. - Stablecoin pairs add 50% of reference-quote improvement, capped at 20 bps. - A **1 bp base fee** is applied on every supported chain. - On CoW-hosted chains, the upstream CoW Protocol fees in the table above are charged in addition; Ophis does not receive them. ## Price-improvement capture on every supported chain Solvers compete to fill your order, and any execution that beats the quote you signed (the **surplus**, or price improvement) is upside you did not have to pay for. You see it on the order completion screen as the extra you received beyond the quote. The capture is measured against the backend's reference quote, not against a loose user slippage limit. For volatile pairs Ophis retains 80%, until the fee reaches 99 bps of volume. For stablecoin pairs it retains 50%, until the fee reaches 20 bps. The separate 1 bp base fee always applies. Where the order settles still matters: - **Optimism, Unichain, and Robinhood Chain:** the backend applies the capped capture model as a protocol policy. - **CoW-hosted chains:** the same Ophis policy is encoded in CIP-75 appData. CoW Protocol's own fee model also applies upstream. That upstream charge is not an Ophis fee and applies to every frontend using CoW-hosted settlement. ## What you save versus a typical AMM Every supported chain uses the same 1 bp Ophis base plus capped reference-improvement capture, so the realized Ophis charge depends on execution quality. CoW-hosted chains additionally pay CoW Protocol's separate upstream fees. ### Fixed-cost comparison (CoW-hosted volatile path) | Trade size | AMM at 0.25% | AMM at 0.30% | Ophis base (0.01%) | You save | Ophis + CoW fixed fees (0.03%) | You save | | --- | --- | --- | --- | --- | --- | --- | | $10,000 | $25 | $30 | **$1** | **$24 to $29** | $3 | $22 to $27 | | $100,000 | $250 | $300 | **$10** | **$240 to $290** | $30 | $220 to $270 | ### Same-chain stablecoin pair (e.g. USDC to USDT): 0.01% sovereign base | Trade size | AMM at 0.25% | AMM at 0.30% | Ophis (0.01%) | You save | Ophis on CoW-hosted (0.013%) | You save | | --- | --- | --- | --- | --- | --- | --- | | $10,000 | $25 | $30 | **$1** | **$24 to $29** | $1.30 | $23.70 to $28.70 | | $100,000 | $250 | $300 | **$10** | **$240 to $290** | $13 | $237 to $287 | The table isolates the fixed base so it can be compared with AMM fees. On Optimism, Unichain, and Robinhood Chain, the realized charge also includes 50% of reference-quote improvement, capped at 20 bps of volume; the trader receives the remainder and all improvement above the cap. On CoW-hosted chains, the fixed and improvement charges in the all-in table above apply. ## What you get back: monthly WETH rebates Beyond the published trading charge, a share of collected WETH fees **comes back to active traders**. Each month, **21.25% of the WETH fees collected by the Ophis fee Safe** is paid out as rebates, split across active wallets in proportion to their **30-day volume weighted by tier**. | Tier | 30-day volume | Weight | | --- | --- | --- | | Bronze | $20,000+ | 10% | | Silver | $50,000+ | 15% | | Gold | $100,000+ | 25% | | Palladium | $500,000+ | 35% | | Platinum | $1,000,000+ | 50% | A higher tier raises your weight in the split, so the same volume earns a larger share of the pool. To make it concrete, take an illustrative month where the WETH rebate pool is worth **$10,000** and the active weighted total across all ranked wallets sums to **100% in your slice of the split**. Your share scales with your tier weight relative to that total. As a simple read of the weights: | Your tier | 30-day volume | Tier weight | Illustrative monthly WETH rebate* | | --- | --- | --- | --- | | Bronze | $20,000 | 10% | ~$200 | | Silver | $50,000 | 15% | ~$450 | | Gold | $100,000 | 25% | ~$1,000 | | Palladium | $500,000 | 35% | ~$3,500 | | Platinum | $1,000,000 | 50% | ~$5,000 | *Illustrative only. The actual rebate depends on the size of that month's WETH pool and on the total weighted volume of every other ranked wallet sharing it, so figures move month to month. The mechanics, not the dollar amounts, are what is fixed: 21.25% of WETH fees, split by tier-weighted 30-day volume. Wallets below $20,000 of 30-day volume are unranked and do not share in the pool. Your current tier and progress to the next one are shown on the swap page. Add the returned **surplus** on top of every figure above: the rebate is a refund of fee, the savings table is fee you never paid, and the surplus is upside the solver found for you after the applicable capped Ophis capture on operated chains or upstream CoW capture on hosted chains. ## How it's collected The fee uses CoW Protocol's partner-fee model. The Ophis swap app and SDK write the 1 bp base on every supported chain. On hosted chains they also write a pair-aware `priceImprovementBps` entry with a hard `maxVolumeBps` cap; operated chains apply that second component in the backend instead to avoid duplication. On the **Ophis-operated stacks (Optimism, Unichain, Robinhood Chain)**, the backend also enforces an **anti-abuse minimum** at settlement, so a fee is guaranteed on chain rather than relying on the frontend: it rejects any order to the Ophis fee recipient whose partner fee falls below **1 bp**. This exists to reject a zero-fee bypass. On CoW-hosted chains no sovereign floor is enforced, the same `appData` rate applies (validated by CoW's backend), and CoW's protocol fees (see the all-in table above) are charged by CoW on top. For the protocol-level details, see [CoW Protocol batch auctions](https://docs.cow.fi/cow-protocol/reference/core/auctions). :::note The rebate pool is the **WETH** the fee Safe holds; fees collected in other tokens are not currently part of it. Want to earn on trades you refer? See the [Affiliate program](./affiliate.md): share a code and earn a share of the verified base fee Ophis keeps on every trade your referrals route. ::: --- # Affiliate program URL: https://docs.ophis.fi/affiliate # Affiliate program The Ophis affiliate program pays a share of the verified 1 bp base fee Ophis keeps whenever a wallet you referred trades, settled monthly in WETH. Share a referral code. Every time someone you refer trades on Ophis, **you earn a share of the verified base fee Ophis keeps on that trade, for life.** It is paid in WETH, every month, from the same Safe that pays volume-tier rebates. ## How it works 1. **Connect a wallet** on [swap.ophis.fi](https://swap.ophis.fi). 2. Open your **Profile** and find the **affiliate section**. 3. **Mint your referral code.** It is tied to your connected wallet. 4. **Share your link:** `https://swap.ophis.fi/?ref=YOURCODE`. When a new trader arrives through your link and starts swapping, every trade they route accrues a share of the fee back to you. ## What you earn There are two tiers, and both numbers are published: | | Self-serve | Partner | | --- | --- | --- | | Share of the verified base fee Ophis keeps | **8%** | **12%** | | Referred volume counted | Capped at **$1,000,000/month** | **Uncapped** | | How to get it | Mint a code on the swap page | [Contact us](https://business.ophis.fi) to upgrade your code | - Paid **monthly in WETH**, from the Ophis fee Safe, on-chain. - **Through your referral link,** counts only **net-new wallets**: wallets that had not traded on Ophis before arriving through the link. Volume you route yourself through the SDK or widget is not net-new gated. - **Lifetime** attribution: once a referred wallet is bound to your code, you keep earning on its trades for as long as it trades. The share is taken on the verified 1 bp base fee Ophis **retains**, not raw volume. Improvement capture is excluded until actual transfers can be reconciled to the Ophis Safe. A quick read on the scale: drive **$1,000,000** of referred retail volume in a month and the self-serve share works out to roughly **$6 to $8** in WETH for that month ($9 to $12 on the partner tier), depending on the chains your referrals trade on. If you run your own integration, the referral share is the smallest of three earning layers: see [Partner economics](./partners.md#partner-economics-the-three-layers) for the chain-aware base, hosted partner rate, and how to charge your own fee on top. ## How attribution and payout work - **Attribution is off-chain.** A wallet that arrives through your referral link is bound to your code on its first qualifying activity and must be net-new (no prior Ophis trades). One referrer per referred wallet, and the first valid bind wins. Integrators who route their own flow attribute differently: tagging orders with your active code through the [SDK](./partners.md) or [widget](./widget.md) credits that volume to you with no bind, and the net-new rule does not apply there. - **Payout is monthly, in WETH.** At the end of each cycle, Ophis tallies the fees earned from your referrals' trades and batches the WETH owed to you in a single monthly payout from the fee Safe. ## Affiliate vs rebates These are two separate ways to earn, and you can use both: | | Affiliate program | Volume-tier rebates | | --- | --- | --- | | Who earns | You, on trades your **referrals** route | You, on **your own** trade volume | | What | 8% (self-serve) or 12% (partner) of the verified base fee Ophis keeps | Share of the WETH rebate pool, weighted by your tier | | Paid in | WETH, monthly | WETH, monthly | See [Fees & rebates](./fees.md) for the volume-tier rebate model. ## Ready to start 1. [Open swap.ophis.fi](https://swap.ophis.fi) and connect your wallet. 2. Mint your code in the affiliate section of your Profile. 3. Share `https://swap.ophis.fi/?ref=YOURCODE` and start earning. --- # How Ophis compares URL: https://docs.ophis.fi/comparison # How Ophis compares This page is a decision guide, not a sales pitch. It explains what an intent-based swap is, what Ophis shares with CoW Swap, and where the four front-ends below genuinely diverge so you can pick the right one for your trade. ## What "intent-based" means A traditional DEX swap is a transaction: you pick a route, sign it, and broadcast it yourself. An **intent-based** swap is different. You sign a statement of what you want ("sell 1,000 USDC, receive at least X ETH"), and a competitive network of **solvers** races to fill it. You never specify the path; you specify the outcome, and the order only settles if a solver meets or beats the price you signed. Those orders are then cleared in **batch auctions**. Instead of each trade hitting the chain alone, many orders settle together at a uniform clearing price. This is what gives the model its **MEV protection**: there is no public pending transaction for a bot to front-run or sandwich, and orders inside a batch can be matched directly against each other (coincidence of wants) before any pool is touched. ## Ophis is built on CoW Protocol Ophis is a fork of the **CoW Protocol** stack. The settlement contracts, the batch-auction mechanism, the MEV protection, and the non-custodial design are **shared with CoW Swap**, not reinvented. Funds move only when a solver settles the batch, and the on-chain `GPv2Settlement` contract code is unchanged. That matters for how you read the rest of this page: execution quality, MEV protection, and custody are on par with CoW Swap **because they are the same foundation**. The differentiation lives one layer up, in the **interface and the API**. The comparisons below are about that layer. ## Core differences ### Natural-language input vs token-picker CoW Swap, Matcha, and Velora all drive trades through a token-picker UI: choose a sell token, choose a buy token, choose a chain, set an amount. Ophis adds a natural-language front door. You type "swap 100 USDC for ETH on Base" and a parser turns the sentence into a structured order, which is then signed and settled through the same batch auction. The token-picker still exists underneath; the sentence is an additional way in, and it is the path the agent API uses too. ### Cross-chain scope This is where the four front-ends differ most concretely: - **Ophis**: 13 EVM chains as source or destination, plus **Solana** and **Bitcoin** as cross-chain destinations. - **CoW Swap**: EVM chains plus **Solana** as a destination. No Bitcoin. - **Matcha**: EVM chains plus **Solana**. - **Velora**: **EVM only**. Ophis and CoW Swap reach non-EVM destinations through **NEAR Intents**, a non-custodial cross-chain settlement layer. The practical point for a trader: you do not open a second wallet or hand custody to a bridge. You sign once on the source chain, and NEAR Intents brokers delivery to the Solana or Bitcoin address you named. Bitcoin as a destination is, among these four, unique to Ophis. ### Fee transparency The four projects price trades on different models: - **Ophis**: on every supported chain, a **1 bp base** plus 80% of reference-quote improvement on volatile pairs (99 bps cap), or 50% on stable pairs (20 bps cap). On the 10 CoW-hosted chains, CoW Protocol's own fees apply on top (a 0.02% protocol volume fee, 0.003% on correlated pairs, plus 50% of any quote improvement, capped at 0.98% of volume), bringing the fixed all-in to **0.03% / 0.013%** there. The fixed part is knowable before you trade. - **CoW Swap**: a **0.02% (2 bps)** protocol volume fee (0.003% on correlated pairs) plus **50% of the quote improvement** a solver finds beyond your quote (capped at 0.98% of volume), so part of the cost depends on how the batch fills. - **Matcha**: a **tiered** model, roughly **0.25%** on most pairs and **0.05%** on stablecoin pairs. - **Velora**: a **15 bps (0.15%)** interface fee on most swaps, with a reduced **1 bp (0.01%)** on stablecoin pairs. A worked comparison on a **1,000 USDC** trade (non-stablecoin output, e.g. to ETH) makes the structure visible: | Front-end | Fixed fee on 1,000 USDC | Improvement (surplus) split | | --- | --- | --- | | Ophis-operated chain, volatile pair | **0.10 USDC** base (0.01%) | Trader receives 20% until Ophis's 99 bps capture cap binds; all improvement above the cap goes to the trader | | Ophis on CoW-hosted chains | **0.30 USDC** fixed (0.03%) | Ophis's 80%/99 bps capture applies, plus CoW Protocol's upstream improvement fee | | CoW Swap | **0.20 USDC** (0.02%) | 50% of quote improvement retained by CoW Protocol | | Matcha | **2.50 USDC** (0.25%) | Positive slippage, route-dependent | | Velora | **1.50 USDC** (0.15%) | Positive slippage, route-dependent | On a same-chain stablecoin-to-stablecoin swap of 1,000 USDC, Ophis charges a **0.10 USDC** (0.01%) base on every supported chain, plus 50% of reference-quote improvement capped at 2.00 USDC. It charges **0.13 USDC** fixed all-in on CoW-hosted chains, Matcha **0.50 USDC** (0.05%), and Velora **0.10 USDC** (0.01%). The takeaway is not that one number is always lowest. It is that the Ophis fee schedule is **published per chain**: solver-aligned and capped on every supported chain, with separate upstream CoW fees on hosted chains. ### Where the surplus goes Both Ophis and CoW Swap run batch auctions where solvers compete to **beat** the price you signed. The extra value a solver finds beyond your quote is the **surplus** (price improvement). Where the order settles determines how improvement is shared: - On **Optimism, Unichain, and Robinhood Chain**, Ophis retains **80% of reference-quote improvement on volatile pairs, capped at 99 bps of volume**, or **50% on stable pairs, capped at 20 bps**. The trader receives the remainder and all improvement above the applicable cap. - On the **10 CoW-hosted chains**, the same Ophis capture applies first: **80% of reference-quote improvement on volatile pairs, capped at 99 bps of volume**, or **50% on stable pairs, capped at 20 bps**. CoW Protocol also applies its separate upstream policy: **50% of quote improvement, capped at 0.98% of volume**. The trader receives what remains after both independently capped policies, plus all improvement beyond their applicable caps. ### Agent-first API Every front-end here exposes some programmatic surface, but they target different callers. CoW Swap and Velora publish **orderbook / REST** APIs and SDKs aimed at integrators wiring up an order flow. Matcha exposes the **0x Swap API**. Ophis is built for agents: a **public `POST /api/intent` endpoint** that takes a natural-language sentence and returns a structured order **with no API key**, plus a hosted **MCP server** so an LLM agent can discover and call the swap surface as a tool. The same sentence a person types is the same sentence an agent posts. ## Where each excels - **CoW Swap**: the most mature production solver network and the deepest liquidity reach across EVM chains. If solver-network maturity is your first priority, this is the reference implementation. - **Matcha**: the broadest EVM chain coverage of the four. - **Velora**: competitive low fees, especially the **1 bp** stablecoin rate. - **Ophis**: natural-language input, **Bitcoin** as a destination, a flat and predictable fee, and an agent-first API. It is the option built for English-in / order-out and for autonomous agents. ## Trade-offs, stated plainly Ophis runs its **own solver and orderbook on Optimism, Unichain, and Robinhood Chain**, where its stack is self-hosted; on the other chains it surfaces, it relies on CoW's hosted infrastructure and solver network. CoW's production solver network is **more mature and more battle-tested** than the Optimism-focused stack Ophis operates directly. If you are trading large size on a chain where you want the deepest, most-proven solver competition, CoW Swap is the more conservative pick. Ophis's advantage is the interface and API layer described above, on top of the shared settlement foundation. ## Reference table | | **Ophis** | **CoW Swap** | **Matcha** (0x) | **Velora** (ex-ParaSwap) | | --- | --- | --- | --- | --- | | **How you trade** | Natural language, e.g. "swap 100 USDC for ETH on Base" | Token picker (signed intents) | Token picker | Token picker | | **Settlement** | CoW Protocol batch auctions (shared foundation) | CoW Protocol batch auctions | 0x aggregation / RFQ | Aggregation across DEXs | | **Cross-chain scope** | 13 EVM chains + Solana + Bitcoin (via NEAR Intents) | EVM + Solana (via NEAR Intents); no Bitcoin | EVM + Solana | EVM only | | **Fee model** | Every supported chain: 1 bp Ophis base + 80% of volatile improvement (99 bps cap), or 50% of stable improvement (20 bps cap); hosted chains additionally pay upstream CoW fees | 0.02% protocol volume fee (0.003% correlated) + 50% of quote improvement, capped at 0.98% of volume | Tiered: ~0.25% on most pairs, ~0.05% on stablecoin pairs | 0.15% (15 bps) on most swaps; 0.01% (1 bp) on stablecoin pairs | | **Surplus (price improvement)** | Trader receives the remainder after the capped Ophis capture on every chain; hosted chains additionally follow CoW's upstream policy | 50% of quote improvement retained (capped 0.98% of volume), remainder to the trader | Returned via positive slippage, route-dependent | Returned via positive slippage, route-dependent | | **Agent API** | Public `POST /api/intent` (no key) + hosted MCP server | Orderbook REST API and SDK | 0x Swap API | REST API and SDK | | **Rebates** | 21.25% of WETH fees paid back monthly as volume-tier rebates | Not applicable | Not applicable | Not applicable | | **MEV protection** | Yes (batch auctions) | Yes (batch auctions) | Partial / route-dependent | Partial / route-dependent | ## Read next - [Fees & rebates](./fees.md): the full fee model, stablecoin treatment, and how rebates accrue. - [How it works](./architecture.md): the intent lifecycle, batch auctions, and per-chain settlement. Live service status is on the [Status](./status.md) page. - [FAQ: How is Ophis different](./faq.mdx#how-is-ophis-different-from-1inch-or-matcha): the short version of this page. :::note Competitor fee and chain details reflect each project's public documentation as of July 2026 and may change. The CoW-hosted all-in figures were additionally verified against live production quotes (the quote API's `protocolFeeBps` field) on 2026-07-03. Sources: [CoW Protocol fees](https://docs.cow.fi/governance/fees), [Matcha fees](https://help.matcha.xyz/en/articles/3953360-are-there-any-fees-to-make-a-trade), [Velora UI fees](https://help.velora.xyz/en/articles/6554779-paraswap-ui-fees). ::: --- # Intent API URL: https://docs.ophis.fi/intent-api # Intent API The Intent API parses a free-form swap request into a structured `ParsedIntent` that the Ophis frontend, or your own app or agent, can use to pre-fill a swap form or construct a deep link. It is Ophis's **only bespoke integration API**. To place orders programmatically you use the standard CoW Protocol orderbook API per chain (see [AI agent integration](./ai-agents.md)). The Intent API itself does not place, sign, or execute trades; order signing always happens in the user's wallet. - **Base URL:** `https://ophis.fi` - **Endpoint:** `POST /api/intent` - **Auth:** none, no API key required. - **Backed by:** LibertAI Qwen 3.6 27B (open-weights, on Aleph Cloud), behind a Cloudflare Pages Function proxy. The LibertAI key is held server-side and never reaches callers. - **Machine-readable spec:** [openapi.json](https://ophis.fi/openapi.json) ## Access control | Control | Behaviour | | --- | --- | | **Origin allowlist** | Requests with a non-null `Origin` header are checked against `https://ophis.fi` and `https://swap.ophis.fi`. Browser calls from other origins get `403 FORBIDDEN`. | | **Non-browser callers** | curl and server-side scripts that omit `Origin` entirely are **allowed**, subject to the rate limit. This is the path agents use. | | **Rate limit** | 30 requests per IP per rolling 60-second window. Exceeding returns `429` with a `Retry-After` header. | | **Caching** | Identical normalized text (lowercased + trimmed) within an origin bucket is served from a 5-minute edge cache. Cache hits return the header `x-ophis-cache: hit` and don't consume an upstream model call. The rate-limit counter still increments on a cache hit. | :::note[Origin is a speed bump, not a security boundary] The `Origin` check raises the bar for casual browser abuse but can be omitted by any non-browser client. The real protections are the per-IP rate limit and the fact that the endpoint only normalizes text, it moves no funds. ::: ## Request `POST /api/intent` with a JSON body: ```json { "text": "swap 100 USDC for ETH on Base" } ``` | Field | Type | Constraints | | --- | --- | --- | | `text` | string | Required. 1–280 characters. The swap request in your own words. Case-insensitive. | The model is pinned to `temperature: 0`, so identical normalized inputs produce identical outputs. ### Example ```bash curl -sS https://ophis.fi/api/intent \ -H 'content-type: application/json' \ -d '{"text":"swap 100 USDC for ETH on Base"}' ``` ## Response On success, `200 OK` with a `ParsedIntent`: ```json { "ok": true, "data": { "intent": "swap", "entities": [ { "type": "amount", "value": "100", "raw": "100", "start": 5, "end": 8 }, { "type": "sellToken", "value": "USDC", "raw": "USDC", "start": 9, "end": 13 }, { "type": "buyToken", "value": "ETH", "raw": "ETH", "start": 18, "end": 21 }, { "type": "chain", "value": "base", "raw": "Base", "start": 25, "end": 29 } ] } } ``` ### `ParsedIntent` | Field | Type | Description | | --- | --- | --- | | `intent` | `"swap"` \| `"unknown"` | `unknown` (with an empty `entities` array) if the text isn't a swap request. | | `entities` | `Entity[]` | The recognised entities, in any order. | ### `Entity` | Field | Type | Description | | --- | --- | --- | | `type` | `"sellToken"` \| `"buyToken"` \| `"amount"` \| `"chain"` | The kind of entity. `sellToken` is what you pay with; `buyToken` is what you want. | | `value` | string | Canonical form (e.g. `USDC`, `0.5`, `optimism`). | | `raw` | string | The exact substring from the input. | | `start` | integer | 0-indexed start offset of `raw` (inclusive). | | `end` | integer | 0-indexed end offset of `raw` (exclusive). `text.slice(start, end) === raw`. | **Token values** are validated against an internal allowlist (236 DEX-traded symbols). Unknown symbols are filtered out, the response still includes the other entities, with the unknown one omitted. **Chain values** are lowercase slugs, limited to the chains the network selector can route to: ``` ethereum arbitrum avalanche base bnb gnosis ink linea optimism plasma polygon unichain ``` ## Errors Errors return `{ "ok": false, "error": { "code", "message" } }`: | Status | `code` | When | | --- | --- | --- | | `400` | `BAD_INPUT` | Missing/empty `text`, `text` over 280 chars, or an invalid JSON body. | | `403` | `FORBIDDEN` | The `Origin` header is present but not on the allowlist. | | `429` | `RATE_LIMITED` | More than 30 requests in 60s from your IP. Honour `Retry-After`. | | `500` | `UPSTREAM` | Operator configuration error (the model key is unset). | | `502` | `UPSTREAM` / `INVALID_JSON` | The upstream parser was unreachable, returned a non-2xx, or produced output that failed schema validation. | | `504` | `TIMEOUT` | The upstream parser didn't respond within 5 seconds. | The full set of error codes is `TIMEOUT | UPSTREAM | INVALID_JSON | BAD_INPUT | RATE_LIMITED | FORBIDDEN`. ```json { "ok": false, "error": { "code": "RATE_LIMITED", "message": "too many requests" } } ``` ## Response headers Every response sets `cache-control: no-store`, `x-content-type-options: nosniff`, `x-frame-options: DENY`, and `referrer-policy: no-referrer`. A cache hit additionally sets `x-ophis-cache: hit`. ## Trust model Ophis is non-custodial. This endpoint **does not place, sign, or execute trades**, it only normalizes natural language into structured entities. Order signing always happens in the user's wallet on the frontend. To submit orders programmatically, use the [CoW Protocol orderbook API](https://docs.cow.fi/cow-protocol/reference/apis/orderbook) directly against the relevant orderbook host. --- # AI agent integration URL: https://docs.ophis.fi/ai-agents # AI agent integration Ophis is designed to be agent-friendly. The [Intent API](./intent-api.md) accepts free-form natural language and returns structured JSON your agent can map directly to a pre-filled swap link. The agent does the parsing and routing; in this deep-link flow, **the human always reviews and signs.** The MCP server and framework adapters can also support programmatic signing; use the policy controls in [Autonomous agent trading](#autonomous-agent-trading-advanced) before giving an agent signing authority. :::tip[New: the full walkthrough] For a narrative guide, including the MEV and key-safety pitfalls of letting an agent trade, read [How to let an AI agent swap tokens](https://ophis.fi/blog/let-an-ai-agent-swap-tokens) on the Ophis blog. ::: ## MCP server (recommended) The fastest way to give an MCP-capable agent full Ophis access is the hosted **Model Context Protocol** server: ``` https://mcp.ophis.fi/mcp ``` It speaks streamable-HTTP MCP and exposes fourteen tools: The current server release is **v0.1.1**. Its package metadata, runtime handshake, discovery response, and official registry manifest are checked as a single versioned unit in CI. | Tool | What it does | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `parse_intent` | Parse a natural-language request into a structured intent. | | `resolve_token` | Resolve a token symbol to its canonical address from the trusted Ophis/CoW token list; fails closed (anti-spoof). Call this before quoting or building so you never trade against a spoofed address. | | `get_quote` | Fetch an executable quote for a parsed intent. | | `build_order` | Build a bounded, ready-to-sign order (receiver unconditionally pinned to the owner). | | `submit_order` | Submit a signed order to the correct per-chain orderbook. | | `validate_order` | Offline preflight for an order you built outside `build_order`: catches the silent-failure modes (wrong appCode, wrong orderbook host, wrong EIP-712 domain, appData-hash mismatch, unpinned receiver) before you sign. | | `lookup_tier` | Look up a wallet's 30-day volume tier / rebate status. | | `get_integrator_earnings` | Look up what an integrator's own-fee / referral routing earned, by appCode: routed volume, the Ophis base fee, your stacked fee, and rebate paid-to-date with payout tx links. | | `list_chains` | Resolve supported chains and their settlement / orderbook hosts. | | `get_balances` | Read a wallet's native and ERC-20 balances on one chain via a public RPC. | | `get_portfolio` | Read a wallet's token balances across multiple chains. | | `get_gas` | Fetch the current gas price for a chain. | | `get_token_chart` | Fetch a token's OHLCV price chart. | | `expected_surplus` | Estimate how much better an Ophis sell-quote beats the open market (`beatBps`). | Point any MCP client (Claude, Cursor, or a custom agent) at that URL. Ophis never holds keys: `build_order` returns a bounded order the agent signs locally; the signature is the trust boundary (see the warning below). A bare request without an `Accept: text/event-stream` header returns HTTP 406; that is the transport negotiating, not an outage. ### Connect your MCP client The server is public and keyless, so there is nothing to sign up for. Copy the block for your client. **Claude Code** (one command): ```bash claude mcp add --transport http ophis https://mcp.ophis.fi/mcp ``` **Claude Desktop** (`claude_desktop_config.json`, via the `mcp-remote` bridge): ```json { "mcpServers": { "ophis": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.ophis.fi/mcp"] } } } ``` **Cursor** (`~/.cursor/mcp.json`, or the project `.cursor/mcp.json`): ```json { "mcpServers": { "ophis": { "url": "https://mcp.ophis.fi/mcp" } } } ``` **VS Code** (`.vscode/mcp.json`): ```json { "servers": { "ophis": { "type": "http", "url": "https://mcp.ophis.fi/mcp" } } } ``` **OpenAI Agents SDK, LangChain, or any custom client**: point a streamable-HTTP MCP transport at `https://mcp.ophis.fi/mcp`. The [LangChain tool](#langchain-tool) and function-calling examples below show the call path without an MCP client at all. After connecting, ask the agent to "quote 100 USDC to ETH on Optimism" and it will call `resolve_token`, `get_quote`, and `build_order`; it returns a bounded order for you (or your wallet) to sign. If you'd rather make a single REST call than wire up the full toolset, use the [Intent API](./intent-api.md) directly, as shown next. ## The integration flow 1. **Parse.** `POST` the user's request (or your agent-generated trade idea) to `https://ophis.fi/api/intent`. 2. **Read.** Receive a `ParsedIntent` with normalized `sellToken`, `buyToken`, `amount`, and `chain` entities. 3. **Build a deep link.** Map the chain slug to its chain ID and construct `https://swap.ophis.fi/#//swap//`. 4. **Hand off.** Open the link for the user to review and sign. Ophis never auto-signs, every order requires explicit wallet approval. :::warning[The signature is the trust boundary] Ophis intentionally does **not** implement [x402](https://x402.org) or any HTTP-native payment automation. In the Intent API / deep-link flow, an order only becomes real when the user signs it in their wallet. Agents using this flow must hand off to the user for review and signing. Autonomous integrations instead need the deterministic signing policies described below. ::: Server-side callers (no browser `Origin` header) are allowed, subject to the 30 req/min/IP rate limit. Honour `429` + `Retry-After`. ## Minimal example (curl) ```bash curl -sS https://ophis.fi/api/intent \ -H 'content-type: application/json' \ -d '{"text":"swap 100 USDC for ETH on Base"}' ``` ```json { "ok": true, "data": { "intent": "swap", "entities": [ { "type": "amount", "value": "100", "raw": "100", "start": 5, "end": 8 }, { "type": "sellToken", "value": "USDC", "raw": "USDC", "start": 9, "end": 13 }, { "type": "buyToken", "value": "ETH", "raw": "ETH", "start": 18, "end": 21 }, { "type": "chain", "value": "base", "raw": "Base", "start": 25, "end": 29 } ] } } ``` ## Python helper ```python import requests INTENT_API = "https://ophis.fi/api/intent" SWAP_APP = "https://swap.ophis.fi" # The 13 EVM chains the Intent API can return, mapped to their chain IDs. # Keep in sync with the API's supported-network list; build_deeplink() # raises on any future slug not listed here rather than misrouting it. CHAIN_SLUG_TO_ID = { "ethereum": 1, "optimism": 10, "bnb": 56, "gnosis": 100, "polygon": 137, "base": 8453, "ink": 57073, "linea": 59144, "arbitrum": 42161, "avalanche": 43114, "plasma": 9745, "unichain": 130, "robinhood": 4663, } def parse_intent(text: str) -> dict: """Call the Ophis Intent API and return the ParsedIntent payload.""" resp = requests.post(INTENT_API, json={"text": text}, timeout=10) resp.raise_for_status() body = resp.json() if not body["ok"]: raise RuntimeError(f'{body["error"]["code"]}: {body["error"]["message"]}') return body["data"] def build_deeplink(parsed: dict) -> str: """Turn a ParsedIntent into a swap deep link for the user to sign.""" by_type = {e["type"]: e["value"] for e in parsed["entities"]} sell = by_type.get("sellToken", "_") buy = by_type.get("buyToken", "_") chain_slug = by_type.get("chain") if chain_slug is None: chain_id = 1 # no chain in the request -> default to Ethereum elif chain_slug in CHAIN_SLUG_TO_ID: chain_id = CHAIN_SLUG_TO_ID[chain_slug] else: # The parser may return a chain this map doesn't cover yet. Fail # loud instead of silently routing the user to the wrong chain. raise ValueError(f"unmapped chain slug {chain_slug!r}; update CHAIN_SLUG_TO_ID") # The user sets/confirms the amount and signs in the app. return f"{SWAP_APP}/#/{chain_id}/swap/{sell}/{buy}" intent = parse_intent("swap 100 USDC for ETH on Base") print(build_deeplink(intent)) # https://swap.ophis.fi/#/8453/swap/USDC/ETH ``` ## LangChain tool Wrap the API as a [LangChain](https://python.langchain.com) tool your agent can call when a user wants to trade: ```python from langchain_core.tools import tool @tool def ophis_swap_intent(text: str) -> dict: """Parse a natural-language swap request into a structured Ophis intent and a deep link the user can open to review and sign. Use this whenever a user wants to swap, buy, or sell a crypto token. The link must be shown to the user, never auto-execute a trade.""" parsed = parse_intent(text) return {"intent": parsed, "deeplink": build_deeplink(parsed)} ``` The tool returns both the structured intent (so your agent can reason about the trade) and a link (so the user can sign it). ## AutoGPT / function-calling agents Any function-calling agent. AutoGPT commands, OpenAI Assistants, or a custom tool loop, can register the parser with this schema: ```json { "type": "function", "function": { "name": "ophis_parse_intent", "description": "Parse a natural-language crypto swap request into a structured intent (sellToken, buyToken, amount, chain). Returns a deep link the user opens to review and sign. Never auto-executes a trade.", "parameters": { "type": "object", "properties": { "text": { "type": "string", "description": "The swap request in natural language, e.g. 'swap 100 USDC for ETH on Base'. Max 280 characters." } }, "required": ["text"] } } } ``` Implement the handler by `POST`ing `{ "text": }` to `https://ophis.fi/api/intent` (see the Python helper above), then surface the resulting deep link to the user. ## Drop-in framework adapters Everything above keeps a human in the signing loop. If instead you are building an agent that executes swaps itself and you are on a common framework, you do not have to hand-roll the order flow in the next section. Four published npm packages wrap quote, EIP-712 sign, relayer approval, and submit into one call, and each stamps your referral code into every order when one is supplied, so the rebate accrues: | Package | Version | For | Registers | | ------------------------------------------------------------------------------ | ------- | --------------------------------------------------------- | ------------------------------------ | | [`@ophis/agentkit-ophis`](https://www.npmjs.com/package/@ophis/agentkit-ophis) | v0.3.2 | [Coinbase AgentKit](https://github.com/coinbase/agentkit) | an `OphisActionProvider_swap` action | | [`@ophis/plugin-goat`](https://www.npmjs.com/package/@ophis/plugin-goat) | v0.3.2 | [GOAT SDK](https://github.com/goat-sdk/goat) | an `ophis_swap` tool | | [`@ophis/plugin-elizaos`](https://www.npmjs.com/package/@ophis/plugin-elizaos) | v0.3.2 | [elizaOS](https://github.com/elizaOS/eliza) | a `swap` action | | [`@ophis/agent-swap`](https://www.npmjs.com/package/@ophis/agent-swap) | v0.3.2 | any custom EOA framework | the `executeOphisSwap()` core | The v0.3.2 adapter family is built and published against `@ophis/sdk` v0.4.2, so its fee policy, chain list, orderbook hosts, settlement contracts, and vault relayers match the current SDK. Coinbase AgentKit, in one line: ```ts import { AgentKit } from '@coinbase/agentkit'; import { ophisActionProvider } from '@ophis/agentkit-ophis'; const agentKit = await AgentKit.from({ walletProvider, // any EvmWalletProvider (Viem, CDP, Privy, ZeroDev) actionProviders: [ophisActionProvider({ referralCode: process.env.OPHIS_REFERRAL_CODE })], }); ``` GOAT SDK: ```ts import { ophis } from '@ophis/plugin-goat'; const tools = await getOnChainTools({ wallet: viem(walletClient), plugins: [ophis({ referralCode: process.env.OPHIS_REFERRAL_CODE })], }); ``` elizaOS (the agent signs with its own `EVM_PRIVATE_KEY`; set `OPHIS_REFERRAL_CODE` in the character settings to earn the rebate): ```ts import { ophisPlugin } from '@ophis/plugin-elizaos'; export const character = { name: 'Trader', plugins: [ophisPlugin], // registers a natural-language `swap` action }; ``` The AgentKit and GOAT tools take `sellToken`, `buyToken`, `sellAmount` (whole units, e.g. `"1.5"`), and an optional `slippageBps` (default `50` = 0.5%); the elizaOS action reads the tokens and amount from the user's message and uses the default 0.5% slippage. Each quotes against the Ophis orderbook, signs the order EIP-712 with the agent's own wallet, approves the CoW vault relayer once, submits, and returns the order UID plus an explorer URL. ERC-20 to ERC-20 only (native-ETH sells need CoW eth-flow, a separate path, so wrap to WETH first). The agent's wallet is the order owner **and** receiver, so funds only ever move through the audited CoW settlement contract, back to the same wallet. The 1 bp base fee applies to every supported pair. Every drop-in adapter (AgentKit, GOAT, elizaOS) and the platform integrations below detect stable pairs from a verified stablecoin list so the reduced price-improvement policy is selected automatically: 50% capped at 20 bps for stable pairs, versus 80% capped at 99 bps for volatile pairs. The `referralCode` is optional: omit it and swaps still work and settle, you just forgo the rebate. Mint one below, then ship, no redeploy of the swap path needed to start earning. ### More platform integrations Beyond the npm packages above, Ophis maintains swap integrations for more agent platforms, each built on the same audited Ophis order flow (the TypeScript ones reuse `@ophis/agent-swap`; the Python ones mirror the same order construction and fund-safety guards): [elizaOS](https://www.npmjs.com/package/@ophis/plugin-elizaos) (published, in the table above), plus **HeyAnon**, **Swarms**, the **MetaMask Agent Wallet**, and **Bankr**. Their source lives under [`integrations/`](https://github.com/ophis-fi/ophis/tree/main/integrations) in the Ophis repo; each is being submitted to its platform's own registry, so availability follows that platform's review. The MCP server and Intent API above already work with any of these agents today. ## Markdown skill family (shell-capable agents) Agents that can run shell commands (Claude Code and similar local runtimes with `curl`, `jq`, and Foundry's `cast`) do not need the MCP transport at all: Ophis publishes a self-describing **agent-skill family** the agent reads and executes directly. - Index (with a sha256 digest per file, verify after download): [`https://ophis.fi/.well-known/agent-skills/index.json`](https://ophis.fi/.well-known/agent-skills/index.json) - Umbrella skill: [`https://ophis.fi/.well-known/agent-skills/ophis/SKILL.md`](https://ophis.fi/.well-known/agent-skills/ophis/SKILL.md) - Sub-skills: `ophis-quote`, `ophis-swap`, `ophis-order-status`, `ophis-cancel`, `ophis-surplus-report`. The umbrella's frontmatter carries a **machine-readable policy block**: the pinned per-chain settlement and vault-relayer contracts (the only allowed `approve` spenders), the EIP-712 signing domains, the orderbook hosts, and slippage latches. Policy-enforcing runtimes can apply it mechanically; CI in the Ophis repo pins the block against the deployed addresses so the published skills cannot drift. The skills cover all three Ophis-operated chains (Optimism, Unichain, and Robinhood Chain); for other chains use the MCP server above, which resolves per-chain contracts via `list_chains`. The same canonical family is published as [`@ophis/agent-skills`](https://www.npmjs.com/package/@ophis/agent-skills) v0.1.1 for runtimes that install skills from npm. ## Get a referral code Every order these adapters (or the SDK below) build already carries the Ophis partner fee. Add your **referral code** and that same order also credits _you_ with the [affiliate rebate](./affiliate.md) on its volume, currently 8 to 12 percent, paid on-chain. The code rides in the order's appData, so there is nothing for the end user to sign or opt into. 1. Open the [Rewards page](https://swap.ophis.fi/#/rewards) and connect a wallet. 2. Mint a code (about 30 seconds). It is yours permanently. 3. Pass it to any adapter as `referralCode`, or export `OPHIS_REFERRAL_CODE` and the adapters pick it up automatically. The code is **optional**: without one your agent still swaps normally, it just earns no rebate. You can ship first and add the code later. ## Submitting orders programmatically The Intent API only normalizes language, it does not place orders. To submit orders programmatically, build and sign a [CoW Protocol order](https://docs.cow.fi/cow-protocol/reference/apis/orderbook) yourself. Four things must each be exactly right, every one fails **silently** (a rejected order, a wrong-chain trade, or zero fee collected) if you guess. If your agent runs on Coinbase AgentKit or GOAT, the [drop-in adapters](#drop-in-framework-adapters) above already get all four right, hand-roll this only if you are on neither. The `@ophis/sdk` helpers below are also what those adapters call under the hood. The helpers below live in **`@ophis/sdk`**, published on npm (v0.4.2, public). Install it with `npm install @ophis/sdk`, or copy the values from the call-outs if you prefer to vendor them. ### 1. Resolve the orderbook host from the chain ID :::danger[Optimism, Unichain, and Robinhood Chain do not live on api.cow.fi] Optimism, Unichain, and Robinhood Chain break the `api.cow.fi/` pattern. Ophis self-hosts their orderbooks at `optimism-mainnet.ophis.fi`, `unichain-mainnet.ophis.fi`, and `robinhood-mainnet.ophis.fi`. Posting one of their orders to `api.cow.fi/` (a host that does not serve Ophis) **silently bypasses the Ophis solver and zeroes the partner fee**. Resolve hosts via `@ophis/sdk` `getOphisOrderbookUrl` per chain rather than hardcoding. ::: ```typescript import { getOphisOrderbookUrl } from '@ophis/sdk'; const orderbookUrl = getOphisOrderbookUrl(10); // -> https://optimism-mainnet.ophis.fi // Throws on an invalid or unsupported chainId rather than guessing a host. ``` ### 2. Build the partner-fee appData correctly The appData base is **1 bp on every supported chain and pair**. The keyless MCP `build_order` and high-level SDK builders select it automatically. Use the CIP-75 **volume** shape, **not** the price-improvement shape `{ priceImprovementBps, maxVolumeBps, recipient }`: the two shapes use different denominators, so slotting a value into the wrong field is a silent magnitude error. Hash the appData with cow-sdk's deterministic serializer, **never** `keccak256(JSON.stringify(doc))`. JSON key order isn't stable, so the hash won't match what solvers expect. For a manual builder, call `ophisVolumeBpsForChainAndPair(chainId, isStablePair)`. This keeps manual builders aligned with the canonical policy. The drop-in adapters above derive stable-pair status from a verified stablecoin list. ```typescript import { MetadataApi, stringifyDeterministic } from '@cowprotocol/cow-sdk'; import { keccak256, toUtf8Bytes } from 'ethers'; import { buildOphisAppDataPartnerFee } from '@ophis/sdk'; // buildOphisAppDataPartnerFee(chainId) REQUIRES a chainId and THROWS on a // missing/invalid one (a forgotten arg fails loud, not as a silent `undefined`). // It returns the metadata.partnerFee value on every chain in the SDK's // OPHIS_FEE_CHAIN_IDS (the Ophis-operated chains plus the CoW-hosted chains the // fork serves), or `undefined` on any other chain. // // On Optimism, Unichain, and Robinhood Chain this returns the required 1 bp // base. Their backends enforce the same 1 bp anti-bypass floor and separately // apply capped price-improvement capture. const partnerFee = buildOphisAppDataPartnerFee(10); // -> { volumeBps: 1, recipient } const metadataApi = new MetadataApi(); const doc = await metadataApi.generateAppDataDoc({ appCode: 'ophis', metadata: { partnerFee, hooks: {}, // pin empty, appData hooks are arbitrary on-chain calls }, }); const fullAppData = await stringifyDeterministic(doc); const appDataHash = keccak256(toUtf8Bytes(fullAppData)); // bytes32 -> order.appData ``` ### 3. Sign with the correct EIP-712 domain CoW orders are signed with **EIP-712 typed data** (`signTypedData`), never `signMessage`. The `verifyingContract` is chain-specific, and the Ophis-operated chains do **not** use CoW's canonical settlement. :::danger[The Optimism, Unichain, and Robinhood Chain settlements are not the canonical CoW one] On Optimism, Ophis's GPv2Settlement is `0x310784c7…B859`, on Unichain it is `0x108A678716e5E1776036eF044CAB7064226F714E`, and on Robinhood Chain it is `0x886d9fd312F442C4E1f3cdeAE7b4AB73493e57cD`, **not** the canonical `0x9008D19f…ab41`. cow-sdk defaults to the canonical address, so signing an OP order with the SDK default yields a domain separator the deployed contract rejects, every order fails. Build the domain from the chain ID instead. ::: ```typescript import { getOphisOrderDomain } from '@ophis/sdk'; // CoW's EIP-712 order struct is named `Order` (the Solidity library is // GPv2Order, but the EIP-712 type name, which feeds the type hash, is // `Order`; a wrong name produces a valid-looking but unusable signature). const ORDER_TYPES = { Order: [ { name: 'sellToken', type: 'address' }, { name: 'buyToken', type: 'address' }, { name: 'receiver', type: 'address' }, { name: 'sellAmount', type: 'uint256' }, { name: 'buyAmount', type: 'uint256' }, { name: 'validTo', type: 'uint32' }, { name: 'appData', type: 'bytes32' }, { name: 'feeAmount', type: 'uint256' }, { name: 'kind', type: 'string' }, { name: 'partiallyFillable', type: 'bool' }, { name: 'sellTokenBalance', type: 'string' }, { name: 'buyTokenBalance', type: 'string' }, ], }; // ethers v6, signer.signTypedData(domain, types, value). The domain's // verifyingContract must be the Ophis OP settlement (getOphisOrderDomain). const signature = await wallet.signTypedData(getOphisOrderDomain(10), ORDER_TYPES, order); // NOT wallet.signMessage(order), that produces an invalid order signature. ``` ### 4. Pin the order `receiver` A CoW order's `receiver` is part of the signed payload and is fully caller-controlled. Pin it to the order owner: a non-owner receiver sends the bought tokens elsewhere on settlement, and the signature makes that irreversible. In the UI a wallet prompt gates this; an autonomous signer has no such gate, so guard it in code before signing. ```typescript import { assertReceiverIsOwner } from '@ophis/sdk'; assertReceiverIsOwner(owner, order.receiver); // throws if receiver !== owner ``` ## Autonomous agent trading (advanced) Everything above keeps a **human in the signing loop**. For an agent that signs _without_ human review, off-chain helpers are not enough, a compromised or prompt-injected agent will sign whatever it is told. Safety has to be enforced where the agent cannot reach it: 1. **Funds in a smart account (Safe).** The agent never holds the fund-owning key; it only _proposes_ orders. The account's EIP-1271 validator (or a Safe module) approves only order hashes that satisfy policy. 2. **A deterministic policy gate** between the (untrusted) LLM and any signature, owning every order field: - token resolution from a chain-scoped allowlist only, never an LLM-emitted address; - `receiver` pinned to the account; - `appData` pinned to the Ophis canonical, hooks forced empty; - limit price within X% of an independent, staleness-checked oracle (CoW guarantees you won't fill _below_ your limit, not that your limit is sane); - per-trade notional + rolling daily caps; short `validTo`; avoid `presign`. 3. **Containment:** a bounded vault-relayer allowance (the blast radius if policy fails once), a guardian key that can revoke signing or pause, keys in an HSM/TEE, and a tamper-evident audit trail. 4. **Defense in depth:** enforce the policy in two places, the EIP-1271 validator/signer **and** server-side at orderbook ingestion. :::warning[The signing gate must be in code, not prose] In the human-mediated flow, "the human reviews and signs" is a documented social contract, not an enforced boundary. Autonomous signing is fine to pursue, but only once that promise is replaced by the policy-enforced kit above. Otherwise an autonomous integrator is one unpinned `receiver` away from draining itself. ::: --- # Embeddable swap widget URL: https://docs.ophis.fi/widget # Embeddable swap widget Let your users swap without leaving your site. The Ophis widget is an iframe of [swap.ophis.fi](https://swap.ophis.fi). Orders route through the Ophis solver and carry the Ophis partner fee automatically. It is a thin, Ophis-branded layer over the battle-tested CoW Protocol widget. ## React (recommended) ```bash npm install @ophis/widget-react react react-dom ``` ```tsx import { OphisWidget } from '@ophis/widget-react'; export function Swap() { return ( console.log('Ophis widget ready')} /> ); } ``` Pass a `provider` (an EIP-1193 injected wallet) to let users connect inside the widget. `baseUrl` and `appCode` are injected for you; the Ophis iframe applies the complete fee policy when no explicit override is supplied. ## Vanilla JS No React? Use the underlying library and point it at Ophis: ```bash npm install @cowprotocol/widget-lib ``` ```ts import { createCowSwapWidget } from '@cowprotocol/widget-lib'; const container = document.getElementById('ophis-widget')!; createCowSwapWidget(container, { params: { baseUrl: 'https://swap.ophis.fi', // the Ophis host appCode: 'ophis', tradeType: 'swap', width: '450px', height: '640px', }, }); ``` > Leave `partnerFee` unset to receive Ophis's complete base + improvement > policy. Supplying it is an explicit volume-fee override; the iframe honors > that `bps` instead of layering the default improvement policy on top. ## Configuration | Field | Default (via `@ophis/widget-react`) | Notes | | --- | --- | --- | | `baseUrl` | `https://swap.ophis.fi` | The iframe host. Override for a self-hosted/staging Ophis. | | `appCode` | `ophis` | Tags orders in appData. Set it to a referral code you have minted and activated to earn the [affiliate rebate](./affiliate.md) on widget orders (an arbitrary label earns nothing). | | `partnerFee` | Unset; the iframe applies the complete policy | An explicit override is authoritative. | | `partnerFee.recipient` | Ophis Safe when overridden | Always pinned by the React wrapper. | | `chainId`, `sell`, `buy`, `theme`, `tokenLists` | upstream defaults | Full [CoW widget params](https://www.npmjs.com/package/@cowprotocol/widget-lib) pass through. | ## Theming Pass a `theme` (`'light'` or `'dark'`) or a full palette object: see the upstream widget docs. The widget inherits the Ophis app styling by default. ## Live demo + copy-paste snippets A runnable demo and one-file snippets for React, Next.js, vanilla JS and a raw iframe live in the repo at [`examples/widget-embed/`](https://github.com/ophis-fi/ophis/tree/main/examples/widget-embed). Run `npx serve .` in that folder and open `index.html` to see the widget embedded against a third-party origin. ## Notes - The widget is GPL-3.0, like the rest of Ophis. - Optimism, Unichain, and Robinhood Chain orders settle on the Ophis self-hosted orderbooks; the CoW-hosted chains (Ethereum, Base, Arbitrum, Polygon, BNB, Gnosis, Avalanche, Linea, Plasma, Ink) route via `api.cow.fi`. Host selection is handled inside the widget app, 13 chains in total. - **Self-hosting an Ophis fork?** The host must allow third-party framing (CSP `frame-ancestors *`, no `X-Frame-Options: SAMEORIGIN`), or integrators' iframes are blocked. (`swap.ophis.fi` already ships this.) Clickjacking is mitigated structurally: every fund-moving action signs in a wallet popup outside the frame. - For programmatic / agent integrations (no iframe), use the [AI agent guide](./ai-agents.md) and [`@ophis/sdk`](https://www.npmjs.com/package/@ophis/sdk) directly. --- # Partner integration (SDK) URL: https://docs.ophis.fi/partners # Partner integration (SDK) :::important All-chain pricing The SDK applies the same economics on every supported chain: a required 1 bp base plus 80% of reference-quote improvement on volatile pairs (99 bps cap), or 50% on stable pairs (20 bps cap). Operated-chain backends apply the improvement component directly; hosted orders encode it in CIP-75 appData. Integrator own-fees remain separate, and Ophis takes 0% of that markup. ::: This guide is for teams that run their **own** swap or treasury tool (for example a vault rebalancing console) and want to route orders through Ophis, charge the Ophis fee, and earn a rebate, signing with a smart-contract wallet (a Safe via EIP-1271, or an MPC signer behind EIP-1271). If you want to **embed** the Ophis swap UI instead, use the [widget](./widget.md). The widget carries the Ophis base fee automatically, and it can earn referral attribution too: set its `appCode` to a referral code you have minted and activated (the code string from your affiliate dashboard) and the indexer credits every widget order tagged with that code to you, with no wallet bind required. Use the SDK path described here when you need what the iframe cannot express: signing with a Safe or MPC signer via EIP-1271, stacking your own fee on top of the base, or controlling per-order `appData`. The whole integration is a **standard CoW Protocol order** built with `@cowprotocol/cow-sdk`, with a few values overridden from `@ophis/sdk`. If your tool already places CoW orders, the changes are small. ## Install ```bash npm i @cowprotocol/cow-sdk @ophis/sdk # plus an EVM util lib for keccak256 + EIP-712 (ethers v6 or viem) ``` `@ophis/sdk` is dependency-free and provides only the Ophis-specific values. It does not bundle cow-sdk, so install both. No Ophis-side deployment is required: the Optimism orderbook and settlement already exist and are live. ## Quick start: the high-level helpers (recommended) Since `@ophis/sdk` v0.1.0 the whole integration is a handful of helper calls that get the silent-failure details right for you: the correct `appCode`, the partner fee, your referral tag, wallet enrollment, the per-chain relayer / host / signing domain, the receiver pin, and the `sendOrder` wire shape. **The same code works on every served chain** because the helpers branch on `chainId` internally. ```ts import { OrderBookApi, MetadataApi, SigningScheme, SupportedChainId, stringifyDeterministic, } from '@cowprotocol/cow-sdk'; import type { OrderCreation } from '@cowprotocol/cow-sdk'; import { keccak256, toUtf8Bytes } from 'ethers'; import { enrollOphisTrader, buildOphisOrderMetadata, buildOphisOrderCreation, getOphisOrderbookUrl, getOphisOrderDomain, getOphisVaultRelayer, } from '@ophis/sdk'; // `owner` is the order owner. This guide signs with a smart-contract wallet (a // Safe via EIP-1271). For a connected EOA signer, see the note after the snippet. const signingScheme = SigningScheme.EIP1271; // SigningScheme.EIP712 for an EOA (see note) // 0. Register the wallet with the rebate indexer once, on wallet-connect. Without // this the indexer never fetches its trades and the rebate never accrues. // Best-effort by default: an indexer outage (non-2xx / network error / timeout) // RESOLVES to { enrolled: false } instead of throwing, so INSPECT the result — // re-call on the next connect, and warn your user the rebate may not index yet. // A wallet left un-enrolled past a monthly payout cutoff permanently misses that // trade's rebate; pass { blocking: true } to hard-require enrollment instead. const enrollment = await enrollOphisTrader(owner); if (!enrollment.enrolled) { console.warn(`Ophis enrollment not confirmed (${enrollment.status ?? 'indexer unreachable'}); retrying on next connect`); } // 1. One-time per sell token: approve it to the correct Vault Relayer. On Optimism // getOphisVaultRelayer returns the Ophis relayer, NOT cow-sdk's canonical one. // The approval must be sent BY the order owner: for a Safe owner submit it as a // Safe transaction. Approving from a connected EOA sets the EOA's allowance, not // the Safe's, so the relayer cannot pull the Safe's token and the first sell fails. await sellTokenAsOwner.approve(getOphisVaultRelayer(chainId), amount); // owner-executed, one-time per token // 2. appData: appCode 'ophis' + the partner fee + your referral code in one call. const doc = await new MetadataApi().generateAppDataDoc( buildOphisOrderMetadata({ chainId, referralCode: 'yourcode', isStablePair, signer: owner }), ); const fullAppData = await stringifyDeterministic(doc); // never JSON.stringify const appDataHash = keccak256(toUtf8Bytes(fullAppData)); // bytes32 // 3. Build your quoted order, pin the receiver to the owner, sign appData = the hash // with the scheme that matches the signer. const order = { ...quote, receiver: owner, appData: appDataHash }; const signature = await signOrder(order, getOphisOrderDomain(chainId), signingScheme); // 4. Submit against the right host. Optimism (10) is Ophis self-hosted and not in // cow-sdk's SupportedChainId, so cast the chainId; the orderbook accepts it. const orderBookApi = new OrderBookApi({ chainId: chainId as SupportedChainId, baseUrls: { [chainId]: getOphisOrderbookUrl(chainId) } as Record, }); // buildOphisOrderCreation is dependency-free, so it returns a plain object; cast it to // cow-sdk's OrderCreation for sendOrder (the wire shape already matches at runtime). await orderBookApi.sendOrder( buildOphisOrderCreation({ order, owner, fullAppData, appDataHash, signature, signingScheme, }) as unknown as OrderCreation, ); ``` For a **connected EOA** signer instead of a Safe: set `signingScheme = SigningScheme.EIP712`, produce a normal EIP-712 signature, and send the token approval from the EOA itself (not a Safe transaction). That is the whole integration. The sections below explain what each helper does per chain (Optimism, Unichain, and Robinhood Chain are self-hosted, the others are CoW-hosted) and the lower-level primitives, if you would rather wire the steps yourself. ## Two cases: self-hosted (Optimism, Unichain, Robinhood Chain) vs CoW-hosted chains Ophis serves two kinds of chain, and they differ only in **where the order is posted** and **which settlement contract signs**: | | Self-hosted (Optimism, Unichain, Robinhood Chain) | CoW-hosted chains (Mainnet, Base, Arbitrum, Gnosis, Polygon, Avalanche, BNB, Linea, Plasma, Ink) | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | Orderbook host | `optimism-mainnet.ophis.fi` / `unichain-mainnet.ophis.fi` / `robinhood-mainnet.ophis.fi` (Ophis, per chain via `@ophis/sdk`) | `api.cow.fi/` (cow-sdk default) | | Settlement (EIP-712 `verifyingContract`) | Ophis, per chain via `@ophis/sdk`: Optimism `0x310784c7FCE12d578dA6f53460777bAc9718B859`, Unichain `0x108A678716e5E1776036eF044CAB7064226F714E`, Robinhood `0x886d9fd312F442C4E1f3cdeAE7b4AB73493e57cD` | CoW canonical `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` (cow-sdk default) | | Partner fee | 1 bp from `buildOphisAppDataPartnerFee(chainId)`; improvement policy is applied by the backend | Base + pair-aware improvement entries from `buildOphisAppDataPartnerFee(chainId, isStablePair)` | | Fee enforcement | Enforced floor at settlement | Carried in `appData`, validated by CoW | On CoW-hosted chains you change **nothing** about host or settlement (cow-sdk defaults are correct); you only add the Ophis `partnerFee` fragment. On Optimism you also override the host and the settlement contract. ## Optimism integration ### 1. Point the orderbook at the Ophis host ```ts import { OrderBookApi, SupportedChainId } from '@cowprotocol/cow-sdk'; import { getOphisOrderbookUrl } from '@ophis/sdk'; // Optimism (10) is Ophis self-hosted and not in cow-sdk's SupportedChainId, so // cast it; the orderbook accepts it at runtime. Use the cast chainId as the // computed baseUrls key so the record type lines up. const opChainId = 10 as SupportedChainId; const orderBookApi = new OrderBookApi({ chainId: opChainId, // optimism-mainnet.ophis.fi, NOT api.cow.fi. The CoW host does not serve // Ophis on Optimism: it would bypass our solver and charge no Ophis fee. baseUrls: { [opChainId]: getOphisOrderbookUrl(10) } as Record, }); ``` ### 2. Build the appData with the Ophis partner fee ```ts import { MetadataApi, stringifyDeterministic } from '@cowprotocol/cow-sdk'; import { keccak256, toUtf8Bytes } from 'ethers'; import { buildOphisAppDataPartnerFee, ophisVolumeBpsForChainAndPair, OPHIS_PARTNER_FEE_RECIPIENT, } from '@ophis/sdk'; // The chain-aware fragment uses the CIP-75 Volume shape: 1 bp on Ophis-operated // chains; on CoW-hosted chains it uses 1 bp for // same-chain stablecoin pairs. // const partnerFee = buildOphisAppDataPartnerFee(10); const partnerFee = { recipient: OPHIS_PARTNER_FEE_RECIPIENT, volumeBps: ophisVolumeBpsForChainAndPair(chainId, isStablePair), }; const doc = await new MetadataApi().generateAppDataDoc({ appCode: 'ophis', // REQUIRED: 'ophis', NOT your app's name (see "attribution" below) metadata: { partnerFee, hooks: {} }, }); const fullAppData = await stringifyDeterministic(doc); // deterministic, never JSON.stringify const appDataHash = keccak256(toUtf8Bytes(fullAppData)); // bytes32, signed as order.appData ``` On the Ophis-operated chains (Optimism, Unichain, Robinhood Chain), the appData base is **1 bp** for every pair. The backend enforces that 1 bp anti-bypass floor and separately applies the capped price-improvement policy. Do not duplicate the improvement charge in appData. On hosted chains, use `buildOphisAppDataPartnerFee(chainId, isStablePair)` so the equivalent capped entry is included exactly once. ### 3. Sign with the Ophis EIP-712 domain ```ts import { getOphisOrderDomain } from '@ophis/sdk'; // { name: 'Gnosis Protocol', version: 'v2', chainId: 10, // verifyingContract: '0x310784c7FCE12d578dA6f53460777bAc9718B859' } const domain = getOphisOrderDomain(10); ``` The `verifyingContract` is the Ophis self-deployed GPv2Settlement on Optimism, **not** CoW's canonical address. Signing against the canonical address produces a domain separator the deployed contract rejects, so every order would fail. ### 4. Pin the receiver to your vault ```ts import { assertReceiverIsOwner, ophisOrderReceiver } from '@ophis/sdk'; // For a vault, owner = the vault Safe. assertReceiverIsOwner throws unless // order.receiver === owner (undefined / zero are treated as owner and pass), so // proceeds land back in the vault. ophisOrderReceiver(owner) resolves it for you. order.receiver = ophisOrderReceiver(vaultSafe); assertReceiverIsOwner(vaultSafe, order.receiver); ``` ### 5. Sign via the vault Safe (EIP-1271) and submit The Ophis Optimism orderbook supports the `eip1271` signing scheme, so a Safe (or an MPC signer behind EIP-1271) can sign: 1. Compute the EIP-712 order digest over the `domain` from step 3 and the `Order` struct (the 12 GPv2 fields: `sellToken`, `buyToken`, `receiver`, `sellAmount`, `buyAmount`, `validTo`, `appData`, `feeAmount`, `kind`, `partiallyFillable`, `sellTokenBalance`, `buyTokenBalance`). 2. Have the vault Safe produce the EIP-1271 signature (`isValidSignature`). 3. Submit with `orderBookApi.sendOrder({ ...order, from: vaultSafe, signingScheme: SigningScheme.EIP1271, signature, appData: fullAppData, appDataHash })` (import `SigningScheme` from `@cowprotocol/cow-sdk`). The order is **signed** with `appData` set to the bytes32 hash, but the **submit body** carries `appData` = the full JSON string and `appDataHash` = the hash. `OrderCreation` has no `fullAppData` field; sending the hash as `appData` with no `appDataHash` uses a deprecated form the orderbook is phasing out. Your tool already does steps like this against CoW Swap. The only deltas are the three overrides above (host, `verifyingContract`, `partnerFee`). :::note One-time token approval (the first on-chain step) Before its first CoW **sell** of a given token, the order owner approves that token to the CoW **Vault Relayer** (the contract that pulls the sell token at settlement). Resolve the relayer per chain with `getOphisVaultRelayer(chainId)` from `@ophis/sdk`: it returns the canonical `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` on CoW-hosted chains and the Ophis-operated relayers (Optimism `0x83847EaB41ad9ea43809ce71569eB2e9daF51830`, Unichain `0xaB29E2a859704C914E55566Ae9b3A7EDE25959cb`) on the self-hosted chains. **Do not use cow-sdk's relayer address on an Ophis-operated chain** (the Ophis-operated chains): cow-sdk only knows the canonical relayer, but the Ophis OP settlement pulls from the Ophis relayer, so an approval to the canonical address leaves first sells unfillable. The `approve` moves no funds: it only lets the relayer pull the sell token when one of your signed orders settles. It is per token and one-time (approve a large or unlimited amount once to skip it on later trades), and it is the only on-chain transaction; the swaps themselves are gasless. This is standard CoW behaviour, not Ophis-specific. ::: ## Other (CoW-hosted) chains Use cow-sdk exactly as you do today (its default `api.cow.fi` host and canonical settlement are correct), and add **only** the Ophis partner-fee fragment: ```ts import { buildOphisAppDataPartnerFee, ophisVolumeBpsForChainAndPair, OPHIS_PARTNER_FEE_RECIPIENT, OPHIS_FEE_CHAIN_IDS, } from '@ophis/sdk'; if (OPHIS_FEE_CHAIN_IDS.includes(chainId)) { // Standard rate: buildOphisAppDataPartnerFee(chainId). For a same-chain // stablecoin pair use the reduced 1 bp rate, same as on Optimism: const partnerFee = { recipient: OPHIS_PARTNER_FEE_RECIPIENT, volumeBps: ophisVolumeBpsForChainAndPair(chainId, isStablePair), }; // ...put it in metadata.partnerFee, sign with the CoW canonical domain } ``` The fee recipient is one CREATE2-deterministic Safe on every chain, so the fragment is identical everywhere; only the host and settlement differ, and only on the Ophis-operated chains (Optimism, Unichain, and Robinhood Chain). CoW-hosted chains do not enforce the floor, so the 1 bp stable rate there is your choice, kept consistent with the Ophis-operated chains. ## Partner economics: the three layers An SDK integration earns on three layers, and all three numbers are published: 1. **Your users get the chain's published integration pricing.** On sovereign chains that is the 1 bp base plus capped improvement capture described above. The same policy applies on CoW-hosted chains, with CoW Protocol's own fees on top (see [Fees & rebates](./fees.md#the-all-in-cost-per-chain)). 2. **You earn a share of Ophis's verified 1 bp base fee** on every trade you route: 8% on the self-serve tier, **12% on the partner tier** (uncapped referred volume; ask us to upgrade your code). Paid monthly in WETH, on-chain. Improvement capture is excluded until receipts can be reconciled to the Ophis Safe. 3. **You can charge your own fee on top** of an ERC-20 order: up to 90 bps under the registered-partner ceiling. The hosted aggregate cap is 190 bps, leaving room for Ophis's 100 bps worst case and the full partner entry. You keep 100% of it before any hosted-chain CoW service fee. Ophis takes no cut of your fee. See [Charge your own fee](#charge-your-own-fee) below. What layer 2 pays per **$1,000,000 of referred monthly volume** (non-stable, labeled estimates; exact value depends on chain mix): | Tier | Share of verified base fee | Roughly, per $1M/month | | --------------- | -------------------------- | ---------------------- | | Self-serve (8%) | 8% of the base Ophis keeps | $6 hosted / $8 operated in WETH | | Partner (12%) | 12% of the base Ophis keeps | $9 hosted / $12 operated in WETH | Layer 2 alone is not a business; it is a kicker. The business case for an operator is **layer 3**: your own fee entry, charged alongside the chain's published Ophis pricing. Ophis takes 0% of your entry. A bot that sets its own fee to 80 bps embeds 81 bps of appData volume fees on Optimism, Unichain, or Robinhood Chain (your 80 plus the 1 bp base); the separate capped improvement charge can also apply. This remains around the 85 to 90 bps wallet swap products charge ([MetaMask Swaps charges 0.875%](https://support.metamask.io/trade/swap/user-guide-swaps/)). On CoW-hosted chains, add the upstream CoW Protocol fees from [Fees & rebates](./fees.md#the-all-in-cost-per-chain) to the user's all-in. ## Charge your own fee The order's `appData` **`partnerFee` field accepts an array** (appData v1.4.0 and later), so your integration can stack its own fee entry, paid to your own address, next to the Ophis base entry: ```ts const ophisPartnerFee = buildOphisAppDataPartnerFee(chainId, isStablePair); const partnerFee = [ // Hosted: Ophis base + improvement entry. Operated: base only (backend adds improvement). ...(Array.isArray(ophisPartnerFee) ? ophisPartnerFee : [ophisPartnerFee]), // Your fee, your address, your rate (charged on top of the base) { recipient: YOUR_FEE_ADDRESS, volumeBps: 80 }, ]; ``` Ophis takes **0% of your fee**. The Ophis charge remains separate: 1 bp plus capped improvement capture on every chain, plus upstream CoW fees on CoW-hosted chains. The Ophis entries can realize at most 100 bps on a volatile pair (1 + 99) or 21 bps on a stable pair (1 + 20). The aggregate hosted settlement ceiling is 190 bps, while a registered integrator entry remains capped at **90 bps**. The array applies to **ERC-20 orders**. A **native-ETH** sell built with the `buildOphisEthFlowOrder` helper carries the single Ophis base `partnerFee` entry; to add your own fee on a native-ETH order, build the appData manually with the array shape above rather than using the helper. How your fee reaches you depends on the chain: - **CoW-hosted chains:** stacked fee entries are accepted and charged by CoW's production orderbooks (we verified this against live quotes in July 2026), and each recipient in the `partnerFee` array settles independently. Ophis still takes **0% of your fee**, but CoW's settlement applies its own **partner-fee service fee (25% by default under CIP-75) to every recipient's entry**, including a stacked non-Ophis one. So on hosted chains **you receive 75% of your own-fee, not 100%** (a 50 bps own-fee nets 37.5 bps). It is paid to your `recipient` address through CoW's **weekly partner-fee distribution** in WETH, with a **0.001 WETH minimum** (per CoW's terms a weekly amount below it can be voided, not carried forward), for **market-order trades** only ([CoW partner-fee docs](https://docs.cow.fi/governance/fees/partner-fee)). The aggregate of all entries is capped at 190 bps, so the maximum Ophis policy and a 90 bps registered integrator entry can coexist. CoW's 25% is a CIP-75 default and is negotiable with CoW DAO. We confirm the end-to-end payout to your recipient on the first settled trade. - **Optimism, Unichain, and Robinhood Chain (Ophis-operated):** a stacked own-fee to a third-party recipient is paid to you through a two-step onboarding, both of which Ophis now supports end to end: 1. _Ingress (allowlisting)._ Your recipient is added to the backend fee-recipient allowlist, so your order settles and your fee is charged (a reviewed backend change plus a redeploy; the onboarding step is below). 2. _Payout._ Ophis meters your charged own-fee per settled trade and pays it to your recipient monthly in WETH from the sovereign chain's Ophis Safe, taking 0% of it. Execution is a 2-of-3 Safe signature. The payout runs once you are allowlisted and we have enabled and funded it for your recipient; amounts are USD-valued from routed volume, not exact per-token restitution. The 1 bp base plus capped price-improvement policy also applies to these sovereign orders. To turn on your sovereign own-fee, [contact us](https://business.ophis.fi) and follow the onboarding step below. ## Earning a rebate (the referral layer) Layer 2 is separate from the fee your users pay. The fee itself is set in `appData` at settlement (the chain-aware base, plus your own entry if you add one). The **referral share** of 8% or 12% is a distinct earning: it is a portion of the verified 1 bp base fee Ophis keeps, paid back to you monthly in WETH. Tag each order with your referral code and Ophis pays it out each cycle. Improvement capture remains excluded until receipts can be reconciled to the Ophis Safe. ```ts import { ophisVolumeBpsForChainAndPair, OPHIS_PARTNER_FEE_RECIPIENT, buildOphisReferrerMetadata, } from '@ophis/sdk'; const doc = await new MetadataApi().generateAppDataDoc({ appCode: 'ophis', // REQUIRED: 'ophis', NOT your app's name (see below) metadata: { // Same chain-aware partner-fee fragment as above. partnerFee: { recipient: OPHIS_PARTNER_FEE_RECIPIENT, volumeBps: ophisVolumeBpsForChainAndPair(chainId, isStablePair), }, ...buildOphisReferrerMetadata('your-code'), // -> metadata.ophisReferrer.code hooks: {}, }, }); ``` The rebate indexer reads `metadata.ophisReferrer.code` from every settled order, credits your referred USD volume **across all served chains**, and pays out monthly in WETH from a single Gnosis Safe. Your code must exist before you tag orders with it. Higher tiers earn a larger share. See the [Affiliate program](./affiliate.md) for rates and tiers. :::warning Two requirements, or the rebate silently never accrues **1. `appCode` must be `'ophis'`**, not your app's name. The indexer only attributes orders carrying the Ophis appCode; an order with a custom appCode still settles and pays the fee, but earns no rebate, and there is no error anywhere. Your own identity is the referral code in `metadata.ophisReferrer.code`, a separate field from `appCode` (which records _which app_ placed the order, always `'ophis'` here). **2. Each order-owner wallet must be registered with the indexer.** The indexer fetches trades per tracked owner (CoW's trades API cannot be enumerated globally), so a programmatic integrator that never loads the Ophis frontend must enroll every owner (vault Safe) once, with a public idempotent call: ```bash curl https://rebates.ophis.fi/tier/ ``` or ask us to register them. Until an owner is registered, its orders are never fetched and nothing accrues, even with the correct `appCode` and referral code. ::: A future option for Optimism is an **enforced lower fee** at settlement (rather than a post-hoc rebate), via a signed fee credential. That is a separate, not-yet-shipped capability; talk to us if you want it. ## Verifying your earnings: `GET /earnings/:appCode` A keyless, read-only endpoint on the rebate indexer lets you verify what your own routing earned and where it paid out. Look yourself up by the `appCode` you tag into appData: your **widget** top-level appCode, or your **SDK** `metadata.ophisReferrer.code` (the indexer stores either as the integrator identity). ```bash curl https://rebates.ophis.fi/earnings/your-code ``` It reports **cumulative (lifetime)** figures only. To keep it safe as a public surface it never exposes a current-cycle 30-day volume, an estimated current-cycle earning, or a next-payout time (those stay on the signature-gated partner dashboard). ### What Ophis guarantees, and what accrues under CoW terms Optimism (10), Unichain (130), and Robinhood Chain (4663) are Ophis-operated. The earnings indexer and automated sovereign payout currently cover chains 10 and 130; Robinhood fee and rebate reporting remains unavailable until that indexer lane is deployed. On the CoW-hosted chains, partner fees are disbursed by CoW under CoW terms; Ophis neither pays nor guarantees them. The response splits each figure **sovereign** vs **hosted**. The sovereign label means Ophis-controlled settlement: Ophis pays the **referral rebate** from its Safe regardless of chain, and it now also pays a stacked third-party **own-fee** monthly in WETH from the sovereign chain's Ophis Safe, taking 0% of it, once your recipient is onboarded (allowlisted) and we have enabled and funded the payout for it. No partner is onboarded for sovereign own-fee payout yet, so until we turn it on for your recipient the sovereign own-fee is charged and reported but not paid to your address. The response carries a top-level `disclaimer` with the scope. Three earnings streams appear: - **Own-fee** (`ownFeeAccruedUsd`): the partner-fee entry you stack to **your own** recipient in the appData `partnerFee` array, next to the Ophis base entry. `sovereignGuaranteed` (the historical field name) is the own-fee **charged** on Optimism and Unichain, and it is now paid to your recipient monthly in WETH from the sovereign chain's Ophis Safe (Ophis takes 0% of it), once your recipient is onboarded (allowlisted) and we have enabled and funded the payout for it (see [Charge your own fee](#charge-your-own-fee)). `sovereignPaidToDateWeth` / `sovereignPaidToDateUsd` are the **exact** amounts already paid to your recipient from executed Ophis Safe own-fee batches, and `payouts` lists each executed sovereign own-fee batch with its on-chain tx and a block-explorer link (your proof of where it paid out). Sovereign amounts are USD-valued from routed volume, not exact per-token restitution. `hostedAccrued` is the own-fee charged on CoW-hosted chains, where payout runs through CoW's weekly partner distribution under CoW's terms, net of CoW's 25% service fee (Ophis does not guarantee CoW's payout, and we confirm a stacked recipient's first hosted settlement). Treat the accrued figures as charged/gross and the paid-to-date figures as the amounts realized. - **Referral rebate** (`referral`): the monthly WETH rebate Ophis pays your wallet from the Gnosis Safe when your `appCode` is a registered referral code. `paidToDateWeth` / `paidToDateUsd` are **exact**, summed from already-executed Safe batches, and `payouts` lists each executed payout with its on-chain tx and a block-explorer link (your proof of where it paid out). - **Ophis base fee** (`ophisFeeAccruedUsd`): informational, the Ophis fee charged on your routed flow (not your earning). ### Response shape ```jsonc { "ok": true, "appCode": "your-code", "generatedAt": "2026-07-04T09:00:00.000Z", "sovereignChains": [10, 130], "disclaimer": "Earnings on Optimism (10) and Unichain (130) are settled and paid by Ophis end to end. Figures on CoW-hosted chains are accrued at settlement, paid out by CoW under CoW terms; not guaranteed by Ophis. ...", "routedVolumeUsd": { "total": 350000, "sovereign": 150000, "hosted": 200000 }, "ophisFeeAccruedUsd": { "total": 350, "sovereign": 150, "hosted": 200 }, "ownFeeAccruedUsd": { "total": 975, "sovereignGuaranteed": 375, // OP + Unichain: charged, paid monthly once onboarded + enabled "hostedAccrued": 600, // CoW-hosted: disbursed by CoW under CoW terms "recipient": "0xYourOwnFeeRecipient", "sovereignPaidToDateWeth": 0.05, // EXACT WETH paid from executed Ophis Safe own-fee batches "sovereignPaidToDateUsd": 150, "payouts": [ { "cycleMonth": "2026-06", "chainId": 10, "chainName": "Optimism", "txHash": "0x...", "explorerUrl": "https://optimistic.etherscan.io/tx/0x...", "amountWeth": 0.05, }, ], "note": "Own-fee is the partner-fee entry you stack to your own recipient ...", }, "referral": { "registered": true, "paidToDateWeth": 1.5, "paidToDateUsd": 4600, "payouts": [ { "cycleMonth": "2026-06", "chainId": 100, "chainName": "Gnosis", "txHash": "0x...", "explorerUrl": "https://gnosisscan.io/tx/0x...", "amountWeth": 1.0, }, ], "note": "Referral rebate Ophis pays your wallet monthly ... per referrer wallet.", }, "byChain": [ { "chainId": 10, "chainName": "Optimism", "sovereign": true, "routedVolumeUsd": 100000, "trades": 5, "ophisFeeAccruedUsd": 100, "ownFeeAccruedUsd": 250, }, { "chainId": 8453, "chainName": "Base", "sovereign": false, "routedVolumeUsd": 200000, "trades": 10, "ophisFeeAccruedUsd": 200, "ownFeeAccruedUsd": 600, }, ], } ``` Agents can poll the same data through the Ophis MCP server's `get_integrator_earnings` tool (it calls this endpoint). The own-fee amount is decoded from settled appData on every chain, so the charged amount is attributed everywhere, but a charged amount is not itself a payout. On Optimism and Unichain a stacked third-party own-fee is now paid to your recipient monthly in WETH from the sovereign chain's Ophis Safe (Ophis takes 0% of it), once your recipient is onboarded (allowlisted) and we have enabled and funded the payout for it; the `sovereignPaidToDateWeth` / `sovereignPaidToDateUsd` and `payouts` fields report the exact sovereign amounts already paid and their on-chain txs. The hosted figure is the gross amount charged at settlement, paid out under CoW's terms; CoW's 25% service fee applies to a stacked non-Ophis recipient (you receive 75%), and we confirm the end-to-end payout on your recipient's first hosted settlement. Treat the charged figures as gross, and the sovereign paid-to-date figures as the amounts realized. ## Selling native ETH (eth-flow) A CoW order sells an ERC-20 token, so selling **native ETH** needs the on-chain eth-flow path: the user calls the `CoWSwapEthFlow` contract's payable `createOrder`, which wraps the ETH to WETH and places the order on their behalf. `@ophis/sdk` builds that call for you with the Ophis partner fee embedded, so native-ETH sells route through Ophis instead of forcing the user to wrap first. `buildOphisEthFlowOrder` returns the eth-flow contract address, the `msg.value`, the order struct (as a ready-to-send tuple), the ABI, and the full appData you must upload. It pins the receiver to the taker, sets the eth-flow `feeAmount` and `value` correctly, and (when you pass a `hashAppData` function) refuses to build an order whose committed hash does not match the JSON you upload. ```ts import { MetadataApi, OrderBookApi, stringifyDeterministic } from '@cowprotocol/cow-sdk'; import { keccak256, toUtf8Bytes } from 'ethers'; import { buildOphisOrderMetadata, buildOphisEthFlowOrder, isOphisEthFlowChain, getOphisOrderbookUrl, } from '@ophis/sdk'; // 0. Native ETH not supported on this chain? Wrap to WETH and use the order path above. if (!isOphisEthFlowChain(chainId)) throw new Error('wrap ETH to WETH first'); // 1. Build the Ophis appData (partner fee + referral code), same as an ERC-20 order, and hash it. const doc = await new MetadataApi().generateAppDataDoc( buildOphisOrderMetadata({ chainId, referralCode: 'yourcode', isStablePair }), ); const fullAppData = await stringifyDeterministic(doc); const appDataHash = keccak256(toUtf8Bytes(fullAppData)); // 2. Build the eth-flow order. `owner` is the taker; `buyToken` is what they receive. const built = buildOphisEthFlowOrder({ chainId, owner, buyToken, sellAmount, buyAmount, fullAppData, appDataHash, validTo, quoteId, hashAppData: (s) => keccak256(toUtf8Bytes(s)), // optional: fail closed on a hash mismatch }); // 3. Upload the full appData so solvers honor the partner fee (the on-chain order // only commits the hash), then call createOrder with the exact value. const orderBookApi = new OrderBookApi({ chainId, baseUrls: { [chainId]: getOphisOrderbookUrl(chainId) }, }); await orderBookApi.uploadAppData(appDataHash, built.appDataToUpload); // built.ethFlowContract + built.abi give you the contract; call with value === built.value: await ethFlow.createOrder(built.orderTuple, { value: built.value }); ``` Native ETH is supported on Optimism, Unichain, Robinhood Chain, Base, and the other CoW-hosted chains. Robinhood uses the Ophis-operated EthFlow deployment at `0xC1Ee77e8a1B85D5EED702a9bB435f434408A4d29`; resolve it through `getOphisEthFlowAddress(4663)` rather than hardcoding it. The order carries the Ophis partner fee exactly as an ERC-20 order does. ## Caveats - **Use the SDK path when you need more than the base rail.** The widget carries the Ophis base fee and can earn referral attribution through its `appCode`, but it cannot stack your own fee on top, redirect the recipient, or control per-order `appData`: those need the SDK. - **Optimism, Unichain, and Robinhood Chain are the self-hosted chains.** Other chains are CoW-hosted, where Ophis charges the fee but cannot enforce a floor or an on-chain discount. - **Do not use the `api.cow.fi` host on an Ophis-operated chain.** It bypasses the Ophis solver and charges no Ophis fee. ## Quick reference | Step | Optimism | CoW-hosted chains | | -------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | Orderbook host | `getOphisOrderbookUrl(10)` | cow-sdk default `api.cow.fi/` | | EIP-712 domain | `getOphisOrderDomain(10)` | cow-sdk default (canonical settlement) | | Partner fee | `volumeBps: ophisVolumeBpsForChainAndPair(chainId, isStablePair)` to the Ophis recipient | same; `buildOphisAppDataPartnerFee(chainId)` resolves the correct chain base | | Rebate tag | `buildOphisReferrerMetadata(code)` | `buildOphisReferrerMetadata(code)` | | Receiver | `assertReceiverIsOwner(vault, receiver)` | `assertReceiverIsOwner(vault, receiver)` | | Signing | EIP-1271 (Safe / MPC) | EIP-1271 (Safe / MPC) | --- # Security & audits URL: https://docs.ophis.fi/audits # Security & audits :::note[TL;DR] Ophis is non-custodial: no order moves without your wallet signature, and a solver can never exceed your signed sell amount, receiver, or limit price. Trades settle through immutable CoW Protocol contracts that have no admin, owner, or proxy, and are MEV-protected by construction via uniform-price batch auctions. The only mutable surface is the solver allowlist, governed by a 2-of-3 multisig behind a 24-hour timelock. ::: Ophis is built so that the protocol **cannot move your funds without your signature**, and so that execution is fair by construction rather than by trust. This page describes the security measures in place. Every on-chain property below is independently verifiable from the addresses listed. ## Custody Ophis is **non-custodial**. The protocol cannot move user funds without an EIP-712 (or ERC-1271) signature from your wallet. Ophis never holds, escrows, or takes possession of your tokens: you sign each order, the order fixes the sell token, sell amount, minimum buy amount (your limit price), receiver and expiry, and an authorized solver settles it on-chain within exactly those limits. A solver can never pull more than your signed sell amount, send the proceeds anywhere but your signed receiver, or fill below your limit price. ## MEV protection by construction Orders settle through a batch auction in which trades clear at a uniform price. This is designed to mitigate common MEV vectors at the mechanism layer: - **No front-running**: there is no pending-order mempool race to win. - **No sandwiching**: the protocol does not reorder trades for value. - **No priority-gas auction**: execution order within a batch is not for sale. When the winning settlement transaction is broadcast, its calldata can be visible in the public mempool like any transaction. The signed sell amount, receiver, and limit price remain enforced by the settlement contract. Batch settlement materially mitigates common MEV; it is not an absolute guarantee against every adversarial or infrastructure condition. ## Smart contracts Ophis runs its **own deployment** of CoW Protocol's GPv2 settlement stack on Optimism, Unichain, and Robinhood Chain. The contracts that hold or move value are **immutable**: they have no admin, no owner, and no proxy, so no operator (and no compromise of Ophis's backend or frontend) can upgrade, pause, or re-point them: | Contract | Address (Optimism) | Property | | --- | --- | --- | | `GPv2Settlement` | `0x310784c7FCE12d578dA6f53460777bAc9718B859` | Immutable, no admin/proxy | | `GPv2VaultRelayer` | `0x83847EaB41ad9ea43809ce71569eB2e9daF51830` | Immutable, only ever honors the Settlement above | | `CoWSwapEthFlow` | `0x764fE4aa1FF493cf39931c7923C8ff5837596504` | Immutable, native-ETH sells (see below) | | Contract | Address (Unichain) | Property | | --- | --- | --- | | `GPv2Settlement` | `0x108A678716e5E1776036eF044CAB7064226F714E` | Immutable, no admin/proxy | | `GPv2VaultRelayer` | `0xaB29E2a859704C914E55566Ae9b3A7EDE25959cb` | Immutable, only ever honors the Settlement above | | `CoWSwapEthFlow` | `0x38C03729153BCCF6a281DaF41D7C6a14C543F1D7` | Immutable, native-ETH sells (see below) | | Contract | Address (Robinhood Chain) | Property | | --- | --- | --- | | `GPv2Settlement` | `0x886d9fd312F442C4E1f3cdeAE7b4AB73493e57cD` | Immutable, no admin/proxy | | `GPv2VaultRelayer` | `0xB52C38097c19cd38238c62DD36027a7918eFa890` | Immutable, only ever honors the Settlement above | | `CoWSwapEthFlow` | `0xC1Ee77e8a1B85D5EED702a9bB435f434408A4d29` | Immutable, native-ETH sells (see below) | The core settlement contract is CoW Protocol's audited code, so CoW's settlement audits apply to it directly: - CoW Protocol contract audits: [github.com/cowprotocol/contracts](https://github.com/cowprotocol/contracts) - CoW Protocol documentation: [docs.cow.fi/cow-protocol](https://docs.cow.fi/cow-protocol) Two pieces are **Ophis-specific** (not stock CoW) and were reviewed in Ophis's own security audits: a hardened `GPv2AllowListAuthentication` (two-step manager transfer) and the partner-fee settlement-buffer handling. ### Audit methodology and tools Ophis used the following open-source security skills, guidance, analysis tools, and formal-verification technology across applicable review scopes. Reproducible scope and results are recorded in the repository's [`audit/`](https://github.com/ophis-fi/ophis/tree/main/audit) and [`docs/audits/`](https://github.com/ophis-fi/ophis/tree/main/docs/audits) reports. A tool or proof applies only to the scope named in its report; for example, an access-control proof does not prove unrelated Rust or TypeScript code. ### Native-ETH sells (EthFlow) Selling native ETH is placed as an **on-chain order** to the immutable `CoWSwapEthFlow` contract, which is constructor-wired to the Settlement and WETH. These orders carry the same signed limit price and receiver as any other order, and they are **refundable by you on-chain after the order expires**, so even if no solver ever settles it, you reclaim your ETH directly from the contract without trusting any operator. ## Solver governance The only mutable on-chain surface is the **solver allowlist** (which addresses are permitted to settle batches). It is governed conservatively: - Adding a solver, or changing the allowlist's manager or implementation, flows through an on-chain **24-hour TimelockController**: every such change is publicly visible and delayed a full day before it can take effect. - The timelock's proposer and executor is a **2-of-3 multisig** (Gnosis Safe, hardware-wallet signers); the deployer's admin rights were renounced and the timelock self-administers. - A misbehaving solver can be **evicted in a single transaction** by the multisig: fast removal is allowed; only additions and upgrades are delayed. | Contract | Address (Optimism) | | --- | --- | | Solver allowlist (`GPv2AllowListAuthentication`) | `0xAAA13bC6C1A505ccE6B4BF262fdDf4c703B9BD70` | | TimelockController (24h) | `0x8fEe42897a0113BbeC86e4caCCaC5787D7AEC373` | ## Key custody Authority is split and held in **multisigs, not single keys**: - The **protocol multisig** and the **partner-fee multisig** are each a **2-of-3 Gnosis Safe** with hardware-wallet signers: no single key can move governance or fees. - The only single-key components are non-custodial operational hot wallets (the solver that signs settlements carries a small gas float, never a treasury, and can only call `settle()` within your signed limits: it cannot drain wallets). The partner-fee multisig (`0x858f0F5eE954846D47155F5203c04aF1819eCeF8`) holds only collected protocol fees, kept entirely separate from trader funds, which Ophis never custodies. See [Fees & rebates](./fees.md) for how the fee is calculated. ## Infrastructure - The trading backend is **self-hosted** behind Cloudflare; only the public orderbook API is internet-reachable, and the settlement driver is bound to loopback only. - The settlement signing key is held under **OS-level isolation** (dedicated no-shell account, restrictive permissions, rendered to RAM at runtime), not in plaintext alongside the application. - On-chain state is read through a **multi-source RPC consensus** layer that **fails closed**: if the sources disagree or are unavailable, the driver stops rather than acting on an unverified view. - The frontends ship with a strict **Content-Security-Policy**, are deployed from a **branch-protected, SHA-pinned CI pipeline** with **signed build provenance**, and the edge enforces HTTPS. ## Ophis-specific code The code unique to Ophis is open source and auditable end to end: | Component | What it is | | --- | --- | | **Frontend** | A fork of the CoW Swap frontend with the natural-language intent layer. | | **Intent-parser proxy** | A Cloudflare Pages Function in front of LibertAI Qwen 3.6 27B; the model key is held server-side. See the [Intent API](./intent-api.md). | | **Rebate indexer** | Indexes the volume-tier rebates that accrue to traders. See [Fees & rebates](./fees.md). | Source: [github.com/ophis-fi/ophis](https://github.com/ophis-fi/ophis). ## Reporting a vulnerability Responsible disclosure is welcome. Email `clement@aleph.cloud` with the subject prefix `[OPHIS SECURITY]`; see [`SECURITY.md`](https://github.com/ophis-fi/ophis/blob/main/SECURITY.md) for the full policy and response targets. Operator contact: [contact form](https://swap.ophis.fi/#/contact). --- # Status URL: https://docs.ophis.fi/status # Status A directory of the live Ophis surfaces and their health endpoints. There is no synthetic uptime dashboard: each row links to the relevant live surface, or to a self-reporting health endpoint where one exists, so you can check current state directly. ## Services | Surface | URL | Health check | | ------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Swap app | [swap.ophis.fi](https://swap.ophis.fi) | Loads the intent UI. | | Landing | [ophis.fi](https://ophis.fi) | Loads. | | Docs | [docs.ophis.fi](https://docs.ophis.fi) | This site. | | Explorer | [explorer.ophis.fi](https://explorer.ophis.fi) | Order/trade search. | | Intent API | `POST https://ophis.fi/api/intent` | See [Intent API](/intent-api). | | MCP server | `https://mcp.ophis.fi/mcp` | Streamable-HTTP; a request without an `Accept: text/event-stream` header returns HTTP 406: that is expected, not an outage. See [AI agents](/ai-agents). | | Rebate indexer | [rebates.ophis.fi/health](https://rebates.ophis.fi/health) | JSON. `last_pipeline_run_at` is nullable until a nightly pipeline completes and is the liveness signal thereafter; do not treat `ok: true` alone as proof that the nightly pipeline has run. `last_fetch` moves only when a new tagged trade is inserted. Cloudflare `530` / error `1033` means the tunnel replica is disconnected. | | Optimism orderbook | [version check](https://optimism-mainnet.ophis.fi/api/v1/version) | HTTP 200 plus a version string confirms the public chain-10 orderbook is reachable. A non-200 response means do not route until it recovers. | | Unichain orderbook | [version check](https://unichain-mainnet.ophis.fi/api/v1/version) | HTTP 200 plus a version string confirms the public chain-130 orderbook is reachable. A non-200 response means do not route until it recovers. | | Robinhood orderbook | [version check](https://robinhood-mainnet.ophis.fi/api/v1/version) | HTTP 200 plus a version string confirms the public chain-4663 orderbook is reachable. A non-200 response means do not route until it recovers. | The Robinhood deployment is also covered by a daily read-only production canary. It verifies chain identity, Ophis settlement/relayer/EthFlow bytecode, WETH and USDG metadata, the official Stock Token registry and ERC-8056 multiplier, the default token list, and orderbook version. Its workflow is [`robinhood-mainnet-canary.yml`](https://github.com/ophis-fi/ophis/actions/workflows/robinhood-mainnet-canary.yml). ## Settlement model per chain Ophis settles across two kinds of chains: “Production” below means the contracts and configured routing surface exist; it does not promise current uptime. Use the linked checks above before routing. - **CoW-hosted chains**: Ethereum, BNB Chain, Gnosis, Polygon, Base, Arbitrum, Avalanche, Linea, Ink, and Plasma. Orders settle through CoW Protocol's production orderbooks (`api.cow.fi`) using the canonical CoW contracts. Their status mirrors [CoW Protocol's status](https://status.cow.fi). - **Ophis-operated chains**: Optimism (chain 10), Unichain (chain 130), and Robinhood Chain (chain 4663). Orders settle through Ophis's self-hosted orderbooks at `optimism-mainnet.ophis.fi`, `unichain-mainnet.ophis.fi`, and `robinhood-mainnet.ophis.fi` using Ophis-deployed (non-canonical) `GPv2Settlement` contracts. Always resolve per-chain settlement and orderbook hosts via the `@ophis/sdk` helpers or the MCP `list_chains` tool rather than hardcoding addresses. Solana and Bitcoin are supported as **destinations** via [NEAR Intents](https://near.org/intents), not as source-chain orderbooks. ## Incidents Operational incidents and maintenance are announced on [x.com/ophisfi](https://x.com/ophisfi). The canonical source of truth for what is deployed is the [`ophis-fi/ophis`](https://github.com/ophis-fi/ophis) repository. --- # FAQ URL: https://docs.ophis.fi/faq # FAQ ### How is Ophis different from 1inch or Matcha? Ophis uses batch auctions and a solver competition to settle every order at the best on-chain price, with [MEV protection](./architecture.md) by construction. The natural-language input lets you skip token-pickers entirely. The [fee model](./fees.md) is published by settlement path. On every supported chain, each trade pays a 1 bp base; Ophis retains 80% of reference-quote improvement on volatile pairs, capped at 99 bps of volume, or 50% on stable pairs, capped at 20 bps. CoW-hosted chains additionally apply CoW Protocol fees upstream. ### Do I need to connect a wallet? Yes, you sign your swap order with your own wallet. Ophis is non-custodial; the signed order is broadcast to the solver auction and your funds move only when a solver settles the batch. ### Which networks are supported? 13 EVM chains as source / destination: Ethereum, Arbitrum One, Avalanche, Base, BNB Smart Chain, Gnosis Chain, Ink, Linea, Optimism, Plasma, Polygon, Robinhood Chain, and Unichain. Plus **Solana** and **Bitcoin** as cross-chain destinations via NEAR Intents. See [Getting started](./getting-started.md#supported-networks). ### Is my trade protected from MEV? Yes. The batch auction settles every order in a batch at the same uniform clearing price. Front-running and sandwich attacks are eliminated by construction, there is no priority-gas auction to win because the protocol does not reorder transactions for value. See [Security & audits](./audits.md#mev-protection-by-construction). ### What happens if no solver matches my intent? The order expires after its configured validity window (30 minutes by default) and your funds stay in your wallet. You can resubmit, change parameters, or cancel at any time. ### Who runs the solvers? On the CoW-hosted chains Ophis surfaces, CoW's established solver network competes. On Optimism, Unichain, and Robinhood Chain, where Ophis runs its own stack, Ophis operates multiple routing lanes. Availability and pair coverage vary at runtime, so Ophis does not promise a fixed live count. The on-chain allowlist gates who may settle, and more solvers can be authorized over time. Your protection is the same either way: the signed order's limit price is enforced on-chain, so no solver can fill it worse than the price you accepted. ### How are partner fees collected? Every order carries Ophis's **1 bp base**. On Ophis-operated chains the backend adds the capped improvement policy. On CoW-hosted chains, `appData` carries both the 1 bp `Volume` entry and the pair-aware `PriceImprovement` entry: **80% capped at 99 bps** for volatile pairs, or **50% capped at 20 bps** for stable pairs. Full detail is on the [Fees & rebates](./fees.md) page. ### Is Ophis open source? Yes, full source at [github.com/ophis-fi/ophis](https://github.com/ophis-fi/ophis). Ophis is an open-source DEX aggregator frontend with an added natural-language intent-parsing layer, built on CoW Protocol's settlement primitives. ### Can I use Ophis's intent parser in my own app? Yes. The natural-language → structured-order endpoint is publicly available at `POST /api/intent` with a 30 req/min/IP rate limit, no auth, and no key. Build it into a Telegram bot, browser extension, agent framework, or your own UI. See the [Intent API](./intent-api.md) reference and the [AI agent integration](./ai-agents.md) guide. --- # Swap into native Bitcoin from an agent URL: https://docs.ophis.fi/agent-btc-cookbook # Swap into native Bitcoin from an agent Among intent-based batch-auction swap venues, Ophis packages a gasless, hard-limit path to **native Bitcoin** next to 13 EVM chains. The cross-chain rail (NEAR Intents) is shared by several venues; what Ophis packages is the keyless, bounded agent path onto it. This page shows how an agent or bot moves an EVM position into native BTC (or SOL) after a one-time source-token approval, with no Bitcoin-side signing and without clicking through a bridge UI. ## The mechanism A Bitcoin address cannot receive an ERC-20, so the cross-chain leg runs through [NEAR Intents](https://near.org/intents), a cross-chain settlement layer. The shape is: 1. Ask NEAR Intents (its 1-Click flow) for a **deposit address** on the source EVM chain that is bound to the agent's target BTC address. 2. Build a normal Ophis swap order whose **receiver is that deposit address**: sell the EVM token, buy the intermediate asset the deposit address expects. 3. The agent signs the EIP-712 order with its own key. A solver settles it, the proceeds land at the NEAR Intents deposit address, and NEAR Intents brokers delivery to the agent's Bitcoin address. The agent signs once, on the source chain. It supplies a destination Bitcoin address it controls, but signs nothing on the Bitcoin side and holds no gas token on any chain. The order is still a bounded intent: it cannot fill below the price the agent signed. ## Today vs the packaged tool The BTC and SOL destination flow is live in the Ophis **web app** today, where a person composes it. For an **agent**, there is no single wrapped tool yet: a keyless `swap_to_btc` / `swap_to_sol` MCP tool that combines the two legs is on the roadmap, not shipped; until it ships, an agent composes the two calls directly: ```ts // 1. Get a 1-Click deposit address for the target BTC address (NEAR Intents). // See the NEAR Intents 1-Click docs for the exact request shape and the // per-quote deposit address it returns. const deposit = await oneClickQuote({ fromChain: 'optimism', fromToken: sellToken, // the EVM asset the agent holds toAsset: 'BTC', toAddress: agentBtcAddress, // the agent's native BTC address }); // 2. Build an Ophis order with receiver = the deposit address, using @ophis/sdk. // getOphisOrderbookUrl / getOphisOrderDomain resolve the correct per-chain // host and signing domain. The order sells the EVM token for the asset the // deposit address expects, delivered to `deposit.depositAddress`. const order = { ...quotedOrder, receiver: deposit.depositAddress, }; // 3. The agent signs `order` as EIP-712 and submits it DIRECTLY to the // orderbook. Note: the keyless MCP `submit_order` tool pins the receiver to // the owner as a drain guard, so it will NOT relay this order (the receiver // is the 1-Click deposit address, not the owner by design). Submit it to the // chain's Ophis orderbook yourself. After the one-time sell-token approval, // no further gas is needed on the source chain. ``` See the [partner integration guide](./partners.md) for the exact order-build calls and the [AI agent integration guide](./ai-agents.md) for the keyless MCP path. ## Why an agent should care - **One signature, minimal gas.** The agent does not fund a wallet on the destination chain and does no Bitcoin-side signing; it supplies a BTC address to receive at and signs one intent on the source chain. The only on-chain gas is the one-time sell-token approval to the vault relayer before the first sell; after that the swap itself is gasless. - **Bounded, with one caveat.** The limit price caps the fill, and the signed receiver cannot be mutated after the agent signs. But note the ordering: a compromised or prompt-injected agent could request a 1-Click deposit address bound to an ATTACKER's BTC address in step 1, and then the signed receiver would correctly point at that attacker-bound deposit. The signature prevents tampering after construction, not a bad destination chosen before it. For an autonomous agent, enforce the destination out of band: an allowlist or attestation that the BTC address (and thus the 1-Click deposit address) is one the operator approved, checked in code before signing. - **Checkable.** Both legs are observable: the EVM settlement on chain and the NEAR Intents delivery. The EVM leg is non-custodial (a signed intent settled on-chain); the cross-chain delivery is brokered by NEAR Intents, so review its settlement model for the guarantees on that leg. ## Caveats - The BTC and SOL rails are provided by NEAR Intents, which several venues also use; the differentiator is the packaged gasless, hard-limit, keyless path from an agent, not exclusive access to the rail. - Delivery to Bitcoin is not instant; treat the second leg as asynchronous and poll or subscribe for its status before assuming completion. --- # How does an AI agent swap safely URL: https://docs.ophis.fi/agent-swap-comparison # How does an AI agent swap safely If you are building an autonomous agent that needs to swap tokens, the question that matters is not "which venue has the best price" but "what is the worst thing that happens if the agent, or the model driving it, misbehaves." This page lays out the properties that bound that worst case and how Ophis provides each. The properties that decide safety for an agent: - **Keyless to quote and build.** Can the agent get a quote and construct an order without provisioning an API key or OAuth token first? Fewer credentials in the agent's environment is less to leak. - **Machine-signable.** Does the interface return something the agent's own signer can sign directly, or does it hand off to a human in a browser? - **Bounded worst case.** When the agent signs, is the maximum loss knowable at signing time (a hard limit price), or is it an arbitrary transaction whose outcome depends on execution? - **MEV protection.** Is the order shielded from sandwiching by construction, or exposed in a public mempool? - **Gasless.** Does the agent need a native gas token on every chain, or does a solver pay the gas? ## How Ophis addresses each property - **Keyless to quote and build.** The Ophis MCP server at `https://mcp.ophis.fi/mcp` needs no API key, OAuth, or signup: an agent quotes and builds an order with no credential in its environment. - **Machine-signable.** `build_order` returns an EIP-712 order the agent signs directly with its own key. There is no handoff to a human browser step. - **Bounded worst case.** The order carries a hard limit price and a pinned receiver, so the maximum loss is known at signing time and it cannot settle below the price the agent signed. - **MEV protection.** Orders settle in a batch auction at a uniform clearing price, so there is no public pending transaction to sandwich, by construction rather than best-effort. - **Gasless (after a one-time approval).** Solvers pay the settlement gas. The one on-chain step is a single ERC-20 approval to the vault relayer before the first sell of a token; after that, swaps need no native gas token. - **Reach.** 13 EVM chains as source or destination, plus native Bitcoin and Solana as destinations through NEAR Intents. ## Where other venues sit Other agent-facing swap interfaces (1inch, OKX, Jupiter, deBridge, Coinbase's Base MCP, and CoW Swap's SDK) each cover a subset of these properties, and their capabilities move quickly, so check each venue's current docs rather than trust a snapshot here. Two things are worth knowing when you compare: - The safest primitive for an autonomous agent is an **intent order**: a gasless, limit-priced, receiver-pinned EIP-712 signature whose worst case is known before signing, not an arbitrary transaction or router calldata. Ophis and CoW Swap both use this batch-auction model; CoW Swap does not currently publish an MCP server, and Ophis adds the keyless MCP and native Bitcoin. - Several venues offer keyless or gasless paths and slippage-bounded swaps; what is specific to the batch-auction model is uniform-clearing-price MEV protection and a hard signed limit that the settlement contract enforces. ## What "safe" does and does not mean here An intent order bounds the blast radius: the receiver is pinned to the owner, so proceeds cannot be redirected to a third party, and the limit price caps the downside of any single fill. It does **not** turn a bad decision into a good one: if the agent chooses to sell the wrong token, or signs a limit price that is worse than the market, the order still executes within those bounds. Pair the intent primitive with a policy wallet (see [Agent wallet policies](./ai-agents.md#autonomous-agent-trading-advanced)) so the agent can only sign Ophis orders, to an allowlisted token set, with the receiver pinned to itself. ## Try it Point any MCP client at `https://mcp.ophis.fi/mcp` (no key), or read the [AI agent integration guide](./ai-agents.md). Prefer `build_order` to construct orders: it fetches a live quote, applies your slippage bound, pins the receiver to the owner, and applies the canonical all-chain Ophis policy: a 1 bp base plus 80% of reference-quote improvement capped at 99 bps on volatile pairs, or 50% capped at 20 bps on stable pairs. Operated-chain backends add the improvement policy; hosted orders encode it in CIP-75 appData. The returned order is bounded before your agent signs it. --- # Agent wallet policy packs URL: https://docs.ophis.fi/agent-wallet-policies # Agent wallet policy packs If you run an agent that holds its own key, the question is not "should the agent be able to swap" but "what is the worst a compromised agent can do with the swap". These packs answer that: drop-in [Turnkey](https://docs.turnkey.com/concepts/policies/overview) and [Privy](https://docs.privy.io/controls/policies/overview) wallet policies that let the key sign Ophis trades and nothing else. Ophis becomes the one swap an agent policy engine can allow without opening a drain. The packs live in the repo at [`examples/agent-wallet-policy-packs`](https://github.com/ophis-fi/ophis/tree/main/examples/agent-wallet-policy-packs). :::info[What a wallet policy can and cannot enforce] A static wallet policy cannot read a per-order limit price, so it cannot promise a good fill or that the wallet "cannot lose money". What it enforces is **anti-exfiltration pinning**. The constrained key can only produce two kinds of signature: a one-time ERC-20 `approve` whose spender is the Ophis vault relayer, and an Ophis order that carries the correct EIP-712 domain (name `Gnosis Protocol`, version `v2`, and the exact per-chain settlement as `verifyingContract`), delivers proceeds to the agent's own account (`receiver` pinned to self), and moves only tokens on your allowlist. The result is a **bounded blast radius**: a compromised or prompt-injected agent still cannot drain funds to a third party, approve an arbitrary spender, or sign against a non-Ophis contract. It **can** still sign a weak price within your token set, and CoW/Ophis guarantee only that a fill is no worse than the signed limit, not that the limit itself is sane. This pack bounds where value can go, not the price it trades at. To bound execution quality too, pair it with the in-code policy gate (limit versus an independent oracle, per-trade and rolling caps) described in [AI agent integration](./ai-agents.md). ::: ## The two actions an Ophis agent needs 1. **A one-time ERC-20 `approve`** to the per-chain vault relayer (the contract that pulls the sell token at settlement). Prefer a bounded amount over an unlimited approval. This is the only on-chain transaction. 2. **EIP-712 order signing** (off-chain, gasless). The swap settles without the agent paying gas. Each pack allowlists exactly these two paths and denies everything else, including key export and arbitrary contract calls. :::caution[The canonical domain is shared with CoW Swap] On the 10 non-sovereign chains, Ophis uses CoW Protocol's canonical GPv2 contracts, so the EIP-712 order domain is byte-identical to CoW Swap's. A policy that allowlists that domain therefore authorizes CoW-native order flow on that chain too, not Ophis exclusively. **Optimism (10)**, **Unichain (130)**, and **Robinhood Chain (4663)** run Ophis-deployed settlements, so those three carry an Ophis-exclusive domain. If you need Ophis-exclusive routing on a shared-domain chain, also enforce the orderbook host and appData in your in-code policy gate. ::: ## Addresses (the 13 production chains) The packs pin these values. They are mirrored from `addresses.json`, which CI diffs against `@ophis/sdk` (`OPHIS_SETTLEMENT_ADDRESSES` / `OPHIS_VAULT_RELAYER_ADDRESSES`) so the packs cannot silently drift when a chain is added. Addresses are EIP-55 checksummed. | Chain | ID | Settlement (`verifyingContract`) | Vault relayer (`approve` spender) | | --- | --- | --- | --- | | Ethereum | 1 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | | Optimism *(sovereign)* | 10 | `0x310784c7FCE12d578dA6f53460777bAc9718B859` | `0x83847EaB41ad9ea43809ce71569eB2e9daF51830` | | BNB Chain | 56 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | | Gnosis | 100 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | | Unichain *(sovereign)* | 130 | `0x108A678716e5E1776036eF044CAB7064226F714E` | `0xaB29E2a859704C914E55566Ae9b3A7EDE25959cb` | | Polygon | 137 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | | Robinhood Chain *(sovereign)* | 4663 | `0x886d9fd312F442C4E1f3cdeAE7b4AB73493e57cD` | `0xB52C38097c19cd38238c62DD36027a7918eFa890` | | Base | 8453 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | | Plasma | 9745 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | | Arbitrum | 42161 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | | Avalanche | 43114 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | | Ink | 57073 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | | Linea | 59144 | `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` | `0xC92E8bdf79f0507f65a392b0ab4667716BFE0110` | Sepolia (11155111) is a testnet, so the production packs cover the 13 networks above and exclude Sepolia. ## The order EIP-712 types Both providers pin fields inside the CoW order struct, so both need the same type definition. The EIP-712 primary type is `Order` (the Solidity library is `GPv2Order`, but the type name that feeds the type hash is `Order`). ```json { "Order": [ { "name": "sellToken", "type": "address" }, { "name": "buyToken", "type": "address" }, { "name": "receiver", "type": "address" }, { "name": "sellAmount", "type": "uint256" }, { "name": "buyAmount", "type": "uint256" }, { "name": "validTo", "type": "uint32" }, { "name": "appData", "type": "bytes32" }, { "name": "feeAmount", "type": "uint256" }, { "name": "kind", "type": "string" }, { "name": "partiallyFillable", "type": "bool" }, { "name": "sellTokenBalance", "type": "string" }, { "name": "buyTokenBalance", "type": "string" } ] } ``` ## Turnkey Turnkey is deny-by-default and an `EFFECT_DENY` policy overrides an `EFFECT_ALLOW`, so the pack ALLOWs exactly two paths and adds two DENY guards. Bind the agent to a **dedicated non-root API user**; keep root credentials offline with a guardian who can revoke signing. Schema: [policy language](https://docs.turnkey.com/concepts/policies/language), [EVM + EIP-712 examples](https://docs.turnkey.com/concepts/policies/examples/ethereum). Two subtleties, both documented in the pack README: - **The NO_OP deny is mandatory.** Turnkey can inspect `eth.eip_712.*` fields only when the caller submits structured typed data (`PAYLOAD_ENCODING_EIP712`). A pre-hashed digest (`HASH_FUNCTION_NO_OP`) is opaque, so the receiver/domain pins would not apply; the deny closes that path. Reference: [secure EIP-712 signing](https://www.turnkey.com/blog/hyperliquid-secure-eip-712-signing). - **Address casing.** Turnkey compares addresses as case-sensitive strings and the EIP-712 docs mandate **lowercase** hex for `eth.eip_712.*` conditions, while `eth.tx.*` examples use checksummed hex. Verify the casing each field expects against a real payload; a mismatch fails closed (denied). Also: `eth.tx.contract_call_args['spender']` decodes only after you upload the token ABI, and the arg key equals the ABI parameter name (canonical ERC-20 is `spender` / `value`; some tokens use `guy` / `wad`). ### Copy-paste: Optimism (10), the sovereign case ```json { "policies": [ { "policyName": "Ophis OP: allow approve() only to the vault relayer, allowlisted tokens", "effect": "EFFECT_ALLOW", "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_TRANSACTION_V2' && eth.tx.chain_id == 10 && eth.tx.value == 0 && eth.tx.function_name == 'approve' && eth.tx.contract_call_args['spender'] == '0x83847EaB41ad9ea43809ce71569eB2e9daF51830' && eth.tx.to in ['0xTOKEN_1', '0xTOKEN_2']" }, { "policyName": "Ophis OP: allow signing an Ophis order only", "effect": "EFFECT_ALLOW", "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.encoding == 'PAYLOAD_ENCODING_EIP712' && eth.eip_712.primary_type == 'Order' && eth.eip_712.domain.name == 'Gnosis Protocol' && eth.eip_712.domain.version == 'v2' && eth.eip_712.domain.chain_id == 10 && eth.eip_712.domain.verifying_contract == '0x310784c7fce12d578da6f53460777bac9718b859' && eth.eip_712.message['receiver'] == '0xagent_wallet_lowercase' && eth.eip_712.message['sellToken'] in ['0xtoken_1_lowercase', '0xtoken_2_lowercase'] && eth.eip_712.message['buyToken'] in ['0xtoken_1_lowercase', '0xtoken_2_lowercase']" }, { "policyName": "Ophis OP: deny the pre-hashed NO_OP bypass", "effect": "EFFECT_DENY", "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.hash_function == 'HASH_FUNCTION_NO_OP' && activity.params.encoding != 'PAYLOAD_ENCODING_EIP712'" }, { "policyName": "Ophis OP: deny key/seed export by the agent user", "effect": "EFFECT_DENY", "condition": "activity.action == 'EXPORT'" } ] } ``` ### Copy-paste: Base (8453), the shared-domain case Identical shape; only `chain_id` and the (canonical) `verifying_contract` + relayer change. Note the reminder above: on Base the domain is shared with CoW Swap. ```json { "policies": [ { "policyName": "Ophis Base: allow approve() only to the vault relayer, allowlisted tokens", "effect": "EFFECT_ALLOW", "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_TRANSACTION_V2' && eth.tx.chain_id == 8453 && eth.tx.value == 0 && eth.tx.function_name == 'approve' && eth.tx.contract_call_args['spender'] == '0xC92E8bdf79f0507f65a392b0ab4667716BFE0110' && eth.tx.to in ['0xTOKEN_1', '0xTOKEN_2']" }, { "policyName": "Ophis Base: allow signing an Ophis order only", "effect": "EFFECT_ALLOW", "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.encoding == 'PAYLOAD_ENCODING_EIP712' && eth.eip_712.primary_type == 'Order' && eth.eip_712.domain.name == 'Gnosis Protocol' && eth.eip_712.domain.version == 'v2' && eth.eip_712.domain.chain_id == 8453 && eth.eip_712.domain.verifying_contract == '0x9008d19f58aabd9ed0d60971565aa8510560ab41' && eth.eip_712.message['receiver'] == '0xagent_wallet_lowercase' && eth.eip_712.message['sellToken'] in ['0xtoken_1_lowercase'] && eth.eip_712.message['buyToken'] in ['0xtoken_1_lowercase']" }, { "policyName": "Ophis Base: deny the pre-hashed NO_OP bypass", "effect": "EFFECT_DENY", "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.hash_function == 'HASH_FUNCTION_NO_OP' && activity.params.encoding != 'PAYLOAD_ENCODING_EIP712'" }, { "policyName": "Ophis Base: deny key/seed export by the agent user", "effect": "EFFECT_DENY", "condition": "activity.action == 'EXPORT'" } ] } ``` For any other chain, take the parameterized [`turnkey/ophis-agent-policy.template.json`](https://github.com/ophis-fi/ophis/tree/main/examples/agent-wallet-policy-packs/turnkey) and substitute the row from the table above. ## Privy Privy policies are default-DENY: a method with no matching `ALLOW` is denied, and any `DENY` overrides an `ALLOW`. Conditions inside a rule are ANDed. Do not add a catch-all `"method": "*"` DENY (it would override the allows). Privy can inspect both the typed-data domain and message fields, so the receiver and token pins hold at signing time. Schema: [policies overview](https://docs.privy.io/controls/policies/overview), [EVM examples](https://docs.privy.io/controls/policies/example-policies/ethereum). Watch `chain_id` vs `chainId`: the transaction source uses snake_case (`ethereum_transaction.chain_id`), the typed-data domain uses camelCase (`ethereum_typed_data_domain.chainId`). Message-field conditions must carry the `typed_data` schema (the `Order` types above) so the engine can decode. The `approve.spender` and `receiver` paths follow Privy's documented `transfer.amount` / `owner.wallet` patterns; validate both in staging, since the Privy examples do not show `approve()` or a bare `receiver` field verbatim. ### Copy-paste: Optimism (10) Replace `ORDER_TYPES` with the `Order` type array above, `0xAGENT` with the agent wallet, and `0xTOKEN_1` / `0xTOKEN_2` with your token allowlist. ```json { "version": "1.0", "name": "Ophis-only agent wallet (Optimism)", "chain_type": "ethereum", "rules": [ { "name": "Allow approve() only to the vault relayer", "method": "eth_sendTransaction", "action": "ALLOW", "conditions": [ { "field_source": "ethereum_transaction", "field": "chain_id", "operator": "eq", "value": "10" }, { "field_source": "ethereum_transaction", "field": "value", "operator": "eq", "value": "0x0" }, { "field_source": "ethereum_transaction", "field": "to", "operator": "in", "value": ["0xTOKEN_1", "0xTOKEN_2"] }, { "field_source": "ethereum_calldata", "field": "function_name", "operator": "eq", "value": "approve", "abi": [{ "name": "approve", "type": "function", "stateMutability": "nonpayable", "inputs": [{ "name": "spender", "type": "address" }, { "name": "amount", "type": "uint256" }], "outputs": [{ "name": "", "type": "bool" }] }] }, { "field_source": "ethereum_calldata", "field": "approve.spender", "operator": "eq", "value": "0x83847EaB41ad9ea43809ce71569eB2e9daF51830", "abi": [{ "name": "approve", "type": "function", "stateMutability": "nonpayable", "inputs": [{ "name": "spender", "type": "address" }, { "name": "amount", "type": "uint256" }], "outputs": [{ "name": "", "type": "bool" }] }] } ] }, { "name": "Allow signing Ophis orders only", "method": "eth_signTypedData_v4", "action": "ALLOW", "conditions": [ { "field_source": "ethereum_typed_data_domain", "field": "chainId", "operator": "eq", "value": "10" }, { "field_source": "ethereum_typed_data_domain", "field": "verifyingContract", "operator": "eq", "value": "0x310784c7FCE12d578dA6f53460777bAc9718B859" }, { "field_source": "ethereum_typed_data_message", "field": "receiver", "operator": "eq", "value": "0xAGENT", "typed_data": { "types": { "Order": "ORDER_TYPES" }, "primary_type": "Order" } }, { "field_source": "ethereum_typed_data_message", "field": "sellToken", "operator": "in", "value": ["0xTOKEN_1", "0xTOKEN_2"], "typed_data": { "types": { "Order": "ORDER_TYPES" }, "primary_type": "Order" } }, { "field_source": "ethereum_typed_data_message", "field": "buyToken", "operator": "in", "value": ["0xTOKEN_1", "0xTOKEN_2"], "typed_data": { "types": { "Order": "ORDER_TYPES" }, "primary_type": "Order" } } ] }, { "name": "Never export the raw key", "method": "exportPrivateKey", "action": "DENY", "conditions": [] }, { "name": "Never export the seed phrase", "method": "exportSeedPhrase", "action": "DENY", "conditions": [] } ] } ``` For Base or any other chain, change the two `chain_id` / `chainId` values and the `verifyingContract` + relayer to that chain's row, using the parameterized [`privy/ophis-agent-policy.template.json`](https://github.com/ophis-fi/ophis/tree/main/examples/agent-wallet-policy-packs/privy). On Base the `verifyingContract` is the canonical `0x9008D19f...ab41` and the relayer is `0xC92E8bdf...0110` (shared with CoW Swap, per the caution above). ## A working trade path The pack only says what the key may sign. To get a valid, bounded order to sign, use the keyless [Ophis MCP server](./ai-agents.md) at `https://mcp.ophis.fi/mcp` or the `@ophis/sdk`: 1. **`build_order`** returns a ready-to-sign order with the `receiver` already pinned to the owner and slippage bounded against a live quote, plus the exact EIP-712 `domain` + `types`. 2. **Approve** the vault relayer once per sell token (a bounded amount). The pack's approve rule permits only this. 3. **Sign** the typed data with the constrained wallet. The pack lets it through only because the domain, receiver, and token set match. 4. **`submit_order`** relays the signed order; it also refuses any non-owner receiver as a second check. The full loop, with Turnkey and Privy signing snippets, is in [`example-constrained-trade.md`](https://github.com/ophis-fi/ophis/blob/main/examples/agent-wallet-policy-packs/example-constrained-trade.md). :::warning[Policy packs bound exfiltration, not price] These packs are the containment layer, not the whole autonomous-trading story. For an agent that signs without human review, also run the in-code policy gate from [AI agent integration](./ai-agents.md): token resolution from a chain-scoped allowlist, limit price within X% of an independent staleness-checked oracle, per-trade and rolling notional caps, and a short `validTo`. The pack stops the drain; the gate stops the bad price. ::: --- # Migrating from Odos URL: https://docs.ophis.fi/migrating-from-odos # Migrating from Odos :::tip[Start here: this is probably not your page] This page documents one narrow surface: the Odos v3 **quote shape**, signed by an **EOA**, on an **Ophis-operated chain**. Four common integrations look like they belong here and do not. Check this table before you read further. | If this is you | Go here instead | |---|---| | Your signature is validated by a **contract**: a Safe, a smart account, a vault, an MPC signer behind EIP-1271, or a DAO treasury module | **[Partner integration (SDK)](./partners.md).** This surface accepts only `eip712` and `ethsign` and rejects `presign` and `eip1271`, so a contract-validated signer has no path here at all. The SDK reaches those schemes directly | | You trade on **Ethereum, Base, Arbitrum, or any other CoW-hosted chain** | **[Partner integration (SDK)](./partners.md).** This surface serves only chains 10, 130 and 4663. The SDK covers those three plus every CoW-hosted chain | | You trade mostly **same-chain stable pairs** | This surface and the **[Partner integration (SDK)](./partners.md)** both use the 1 bp sovereign base; the SDK also covers hosted chains | | You compose swap **calldata inside your own contract call** | Neither page. Ophis returns an intent, not a transaction, and that is a settlement-model difference rather than a backlog item. See [What this surface is](#what-this-surface-is-and-what-it-is-not) | A vault rebalancing console, a treasury tool, or anything that signs as a contract is exactly the case the SDK guide was written for, even though the word "Odos" does not appear in its title. If none of the four rows describe you, you are in the right place: read on. ::: ## What this surface is, and what it is not This is a **quote-shape compatibility layer**, not a general migration path off Odos. Read that sentence before you plan any work. It accepts the Odos v3 request shape you already send (PathRequestV3 field names, single token in and single token out) and answers with the QuoteResponse field surface you already parse, plus a namespaced `ophis` block. Within a narrow envelope it will also carry you through signing and submission. The envelope is genuinely narrow, and the boundaries are structural rather than a backlog: | You need | Supported | |---|---| | Server-side price discovery, ERC-20 to ERC-20 | Yes | | Execution from an EOA, ERC-20 to ERC-20, on an enabled chain | Yes | | Executable calldata to compose inside your own contract call | **No, and not planned.** Ophis returns an intent, not a transaction | | Signing from a Safe, smart account, MPC or DAO treasury | **No.** EOA signing schemes only | | Native ETH in or out | **No** | | Multi-token in or out | **No.** Tracked with basket intents | | Zaps, limit orders, protected swaps, Solana | **No** | | The other 14 Odos endpoints (`/info/*`, `/pricing/*`, `/zap/*`) | **No.** 3 of 17 Odos paths are implemented | If you are composing swap calldata inside your own contract, this surface cannot serve you and no amount of future work will change that: the difference is the settlement model, not the API. Use the [SDK-first integration](./partners.md) or the [intent API](./intent-api.md) instead, or stay with a router-based aggregator. "Odos" is used on this page in its plain factual sense, to describe the wire shape this surface accepts. Ophis is an independent protocol, not a successor to or affiliate of Odos. ## Production status The current worker configuration and `/healthz` response expose chains 10, 130 and 4663. Each chain has an independent orderbook, so production configuration does not promise moment-to-moment availability. Check the per-chain runtime links in [Chains](#chains) before routing. - A `POST /sor/quote/v3` request with `userAddr` returns a signed `pathId`, `ophis.assemblable: true`, the unsigned order, and its EIP-712 signing envelope when that chain's orderbook is reachable and produces a quote. - Quote-only requests without `userAddr` remain supported and return `ophis.assemblable: false`. - A transient orderbook failure returns `UPSTREAM_UNAVAILABLE` (503) with `Retry-After`; it is an outage, not evidence that the chain was removed. - Integrator-priced `referralFee` is disabled in production and returns `PARTNER_FEE_UNAVAILABLE`. ## Pricing, for comparison The compat surface serves only Ophis-operated chains, so it embeds the **1 bp sovereign base** (`partnerFeePercent: 0.01`) on every pair. The sovereign backend then applies the current price-improvement policy: 80% of reference-quote improvement on volatile pairs capped at 99 bps of volume, or 50% on stable pairs capped at 20 bps. There is no API key, paid tier, or daily request cap; the only limit is a best-effort 60 requests per 60 seconds per IP and Cloudflare colo. ## Quote-only use 1. Change your base URL to `https://compat.ophis.fi`. 2. Send `POST /sor/quote/v3` without `userAddr`. 3. Read live quote fields from the response. On a quote-only response the keys are all still present and explicitly `null`: top-level `pathId`, plus `ophis.order`, `ophis.signing` and `ophis.fullAppData`. Nothing is omitted, so a parser that distinguishes a missing key from a null value sees a null. `ophis.assemblable` is `false`. ## What is the same | Piece | Status | |---|---| | `POST /sor/quote/v3` request field names | Accepted as-is (single input, single output) | | `POST /sor/assemble` with `userAddr` + `pathId` | Live; rebuilds the unsigned order from the signed, expiring `pathId` | | `POST /sor/swap/v3` (quote + assembly in one call) | Live | | QuoteResponse fields (`inAmounts`, `outAmounts`, `netOutValue`, `pathId`, ...) | Response fields are present; `pathId` is populated for assemblable requests with `userAddr` and null for quote-only requests | | `referralCode` attribution | Works today; the integer code becomes the Ophis referral code `odos` | | Slippage (`slippageLimitPercent`) | Applied to the draft when assembly is available; hard cap 50% | ## What changes, stated plainly Ophis is an intent protocol settled by competitive batch auctions, not a router that returns calldata. Three consequences are structural and this surface does not paper over them: 1. **No `transaction` object, ever.** `/sor/assemble` and `/sor/swap/v3` return `transaction: null`. The signable artifact is `ophis.order`, an EIP-712 CoW-style order. If your integration composes swap calldata inside your own contract call (atomic same-transaction execution), this surface does not cover that today. 2. **Settlement is asynchronous.** You submit a signed order, a solver wins it in a batch auction, and the settlement lands one or more blocks later. The order expires at `order.validTo` (the compat draft uses 20 minutes) if it cannot be filled at your limit. Orders settle at the clearing price; an execution better than your signed minimum is reflected in the executed amounts returned by the orderbook. 3. **You pay no gas.** `gasEstimate`, `gweiPerGas` and `gasEstimateValue` are 0 because you broadcast nothing. The winning solver pays settlement gas and that cost is already priced into the quoted amounts. The embedded estimate is visible in `ophis.executionCost`. Four more that will bite you in production if you do not plan for them: 4. **Contract-wallet signing schemes are not exposed.** `signingScheme` accepts `eip712` or `ethsign` and rejects anything else with `INVALID_REQUEST`. CoW's `presign` and `eip1271` are not available, so **anything whose signature is validated by a contract has no path here**: a Safe, a smart account, or a DAO treasury module. This is a regression versus Odos, whose limit-order router shipped a signature validator and tested contract wallets. Use the [SDK](./partners.md), which reaches those schemes directly. This is about *who validates the signature*, not about key custody. A threshold-ECDSA MPC signer that controls an ordinary EOA produces a standard EIP-712 or `ethsign` signature and works here unchanged. 5. **Native ETH is not supported, in or out.** There is no ethflow wrapping on this surface. Both the `0xEeee…EEeE` sentinel and the zero address are forwarded to the orderbook as if they were ERC-20s and currently surface as `UPSTREAM_UNAVAILABLE` (503, numeric 3000). **Do not build a retry loop on that response for a native-token pair.** 503 is documented below as the retryable class, and for this input it is not: retrying cannot succeed. Use the wrapped token (WETH and its per-chain equivalent) explicitly. 6. **`slippageLimitPercent` is not a revert bound.** On Odos it meant "revert the transaction if the price moves past this". Here it sets the limit price you sign into an order that then **rests for 20 minutes** (`order.validTo = now + 1200s`). Nothing reverts. The order either fills at or better than your limit within that window, or it expires. A value you chose for revert semantics is usually the wrong value for a resting limit. 7. **There is no cancel endpoint.** Once submitted, an order rests until it fills or `validTo` passes. If you need to cancel, use the orderbook API for the chain directly, or size `slippageLimitPercent` knowing you are committed for the full 20 minutes. One smaller deviation: - **Values are native-denominated, not USD.** `inValues`, `outValues` and `netOutValue` are denominated in the chain's native token and `ophis.valueCurrency: "native"` says so. `percentDiff` is pinned to 0 and `priceImpact` to null rather than fabricated from a feed Ophis does not have. If either native-price lookup fails, these display values are 0 and the response includes a `VALUES_UNAVAILABLE` warning; atom-denominated amounts remain exact. ## Settlement timing Because settlement is asynchronous, the quote response carries `ophis.expectedSettlementSeconds`. The deployed worker currently returns the configured value `24`. This is a static planning baseline, not a measurement, prediction, guarantee, or SLA. Use order status as the source of truth. Instead of busy-polling order status, you can block on a bounded long-poll: ``` GET /sor/settlement/{chainId}/{orderUid}?waitSeconds=20 ``` It holds the request open until the order reaches a terminal state (settled, expired, or cancelled) or the bounded wait elapses, then returns `{ settled, terminal, pending, status, txHash, executedSellAmount, executedBuyAmount, order, trades }`. `waitSeconds` is clamped (default 20, maximum 55); if it returns `pending: true`, reconnect after `ophis.pollAgainAfterSeconds`. It is a long-poll, not a callback webhook: a stateless edge worker cannot hold a background callback past the request, so the wait is always bounded and there are no hidden retries. ## Partner fees The production compat worker currently has integrator-priced partner fees disabled. Any non-zero `referralFee` returns `PARTNER_FEE_UNAVAILABLE`; it is not silently dropped. `referralCode` attribution remains enabled and maps the integer to `odos`. The code path for mapping `referralFee` to a CIP-75 Volume fee exists behind the deployment switch, but it is not a production capability while that switch is off. See [Partner integration](./partners.md) for the currently supported partner-fee integration. ## API flow ### 1. Quote ```bash curl -sS -X POST https://compat.ophis.fi/sor/quote/v3 \ -H 'content-type: application/json' \ -d '{ "chainId": 10, "inputTokens": [{ "tokenAddress": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", "amount": "1000000000" }], "outputTokens": [{ "tokenAddress": "0x4200000000000000000000000000000000000006", "proportion": 1 }], "slippageLimitPercent": 0.3 }' ``` The production response carries Odos-compatible quote fields plus metadata in the `ophis` block. Amounts, quote IDs, expiration, execution cost, and native-denominated display values come from the live orderbook response; this guide does not publish fixed sample output for them. With no `userAddr`, `pathId`, `ophis.order`, `ophis.signing`, and `ophis.fullAppData` are `null`. Add `userAddr` to request an assemblable quote containing a signed `pathId`, unsigned order, and EIP-712 signing envelope. ### 2. Sign `/sor/assemble` rebuilds the unsigned draft from the `pathId` (`userAddr` + `pathId`, with an optional `receiver` override). An assemblable quote also contains the draft directly. Sign it as EIP-712 typed data: ```ts import { createWalletClient, http } from 'viem'; const { order, signing } = quote.ophis; const signature = await wallet.signTypedData({ domain: signing.domain, types: signing.types, primaryType: signing.primaryType, message: order, }); ``` The signing domain pins the Ophis settlement contract for the chain (`0x310784c7FCE12d578dA6f53460777bAc9718B859` on Optimism). Before the first sell of a token, approve the Ophis vault relayer (`0x83847EaB41ad9ea43809ce71569eB2e9daF51830` on Optimism) for the sell amount, in place of the router approval you had. A note on `receiver`: proceeds are pinned to the signing account by default. If you override `receiver` to a different address, the response flags it and `/sor/submit` refuses the order unless you also send `acceptNonOwnerReceiver: true`. That friction is deliberate. ### 3. Submit and poll ```bash curl -sS -X POST https://compat.ophis.fi/sor/submit \ -H 'content-type: application/json' \ --data-binary @signed-order.json ``` Build `signed-order.json` from that same quote. The submit envelope must have this shape (the values below are placeholders): ```json { "chainId": 10, "order": { "sellToken": "0x...", "buyToken": "0x...", "receiver": "0x...", "sellAmount": "...", "buyAmount": "...", "validTo": 0, "appData": "0x...", "feeAmount": "0", "kind": "sell", "partiallyFillable": false, "sellTokenBalance": "erc20", "buyTokenBalance": "erc20" }, "signature": "0x...", "signingScheme": "eip712", "from": "0x...", "fullAppData": "...", "quoteId": 0 } ``` Set the positive top-level `chainId` to the chain used for the quote, copy `order`, `fullAppData`, and `quoteId` from `quote.ophis`, put the actual signature in `signature`, and put the signing account address under the exact `from` key. Do not reuse values from another quote. `signingScheme` defaults to `eip712` when omitted; include it explicitly as shown when using the typed-data signature from the preceding step. The relay re-validates everything (appData hash, receiver pinning, amount bounds) and forwards the order to the Ophis orderbook. It holds no keys and never signs. Then poll: ``` GET /sor/order-status/10/{orderUid} ``` until `status` is `fulfilled` (with `txHash` and executed amounts from the settlement trades), `expired`, or `cancelled`. To avoid a tight poll loop, block on the bounded long-poll instead: `GET /sor/settlement/10/{orderUid}` returns as soon as the order settles or the wait elapses (see Settlement timing above). ## Request field mapping | v3 request field | What Ophis does with it | |---|---| | `chainId` | Must be an enabled Ophis-operated chain (10, 130, or 4663) | | `inputTokens` | Exactly one entry: sell token + amount (exact-in). More than one returns `MULTI_TOKEN_UNSUPPORTED` | | `outputTokens` | Exactly one entry, `proportion` 1. Multiple outputs return `MULTI_TOKEN_UNSUPPORTED` | | `userAddr` | Order owner. Absent: quote-only, `assemblable: false`. Present: signed `pathId`, order draft, and signing envelope | | `slippageLimitPercent` | Signed limit in bips (x100). Above 50%: `INVALID_SLIPPAGE`, never silently clamped | | `simple` | `true` maps to the fast price quality, `false` to optimal | | `referralCode` | Integer code becomes Ophis referral code `odos` in the order's appData (attribution + rebates) | | `referralFee`, `referralFeeRecipient` | Production switch is currently off: a non-zero fee returns `PARTNER_FEE_UNAVAILABLE` | | `gasPrice` | Ignored + warning (solvers pay gas) | | `sourceWhitelist` / `sourceBlacklist` / `poolBlacklist` | Ignored + warning: routing is decided by competing solvers | | `disableRFQs`, `compact` | Silent no-ops | | `likeAsset` | Ignored + warning | | `pathViz`, `pathVizImage`, `pathVizImageConfig` | Requested from the Ophis route-visualization service when it is enabled; the graph and rendered SVG come back in `pathViz`/`pathVizImage`. Flag-gated, so it degrades to null with a warning when off | | `permit2` | Currently null + `PERMIT2_UNAVAILABLE` warning; approve the vault relayer instead | ## Response field mapping | v3 response field | Ophis value | |---|---| | `inTokens`, `outTokens`, `inAmounts`, `outAmounts` | Real quote values (atoms, decimal strings) | | `gasEstimate`, `dataGasEstimate`, `gweiPerGas`, `gasEstimateValue` | 0 (you pay no gas; embedded solver cost in `ophis.executionCost`) | | `inValues`, `outValues`, `netOutValue` | Native-denominated floats (`ophis.valueCurrency: "native"`) | | `priceImpact` | `null` (no independent mid-price feed; nothing is fabricated) | | `percentDiff` | `0` | | `permit2Message`, `permit2Hash` | `null` | | `partnerFeePercent` | CIP-75 sovereign base embedded in the order, as a percent (`0.01` = 1 bp). The backend's capped price-improvement policy is separate and is not represented as another appData Volume entry | | `pathId` | A stateless signed token, valid up to 60 s and consumed by `/sor/assemble`. Populated when the request carried `userAddr`; explicitly `null` (not omitted) on a quote-only request | | `pathViz`, `pathVizImage` | The route-visualization graph and rendered base64 SVG when requested and the feature is enabled, else `null` | | `blockNumber` | 0 + warning (quotes are auction-based, not block-pinned; use `ophis.expiration`) | | `ophis.expectedSettlementSeconds` | Static deployment baseline, currently `24`; not measured latency or an SLA. See Settlement timing | | `transaction` (assemble/swap) | Always `null`. Sign `ophis.order` instead | | `simulation` (assemble/swap) | Always `null`; the orderbook re-validates at submit | ## Errors Errors use one envelope: `{ traceId, error: { code, numericCode, httpStatus, message, docs } }`. `code` is a stable string (`MULTI_TOKEN_UNSUPPORTED`, `PARTNER_FEE_CAP_EXCEEDED`, `PATH_ID_EXPIRED`, `NO_ROUTE`, `CONFIG_MISSING`, ...). `numericCode` follows the Ophis API bands (2xxx quoting, 3xxx retryable upstream, 4xxx validation, and 5xxx server/configuration errors). Two doctrines to wire into your client: - `NO_ROUTE` (404) is an answer, not a failure. Retrying it cannot change it. - 503 responses carry `Retry-After` and are the only in-call retryable class. 429 means slow down globally; do not retry the same call. The one exception is a native-token pair, which returns 503 but can never succeed (see point 5 above). Quote the `traceId` when reporting a problem. ### Numeric code translation **The numeric bands do not mean the same thing on both sides.** If you switched the base URL and kept your error handling, translate before you ship. The dangerous one is 3000. | Odos | Meaning on Odos | Ophis | Meaning here | Client action must change? | |---|---|---|---|---| | 1000 `API_ERROR` | generic failure | varies | no single equivalent | Map per case | | **2000** `NO_VIABLE_PATH` | no path found | **2000** `NO_ROUTE` (404) | no solver quoted it | No. The only aligned code | | 2997/2998/2999 `ALGO_*` | quoting engine down or timed out | 3000 `UPSTREAM_UNAVAILABLE` | orderbook unreachable | Retry class changes | | **3000** `INTERNAL_SERVICE_ERROR` | **internal failure, give up** | **3000** `UPSTREAM_UNAVAILABLE` | **transient, retry with `Retry-After`** | **Yes. Same number, opposite instruction** | | 3100 `CONFIG_INTERNAL` | config service failed | 3100 `UPSTREAM_RATE_LIMITED` | upstream is rate limiting us | **Yes. Same number, unrelated meaning** | | 3110-3112 `TXN_ASSEMBLY_*` | assembly failed | n/a | nothing is assembled here | Delete the branch | | 3140-3143 `GAS_*` | gas estimation failed | n/a | you pay no gas | Delete the branch | | 4000 `INVALID_REQUEST` | malformed body | **4900** `INVALID_REQUEST` | same condition | Number changes | | 4001 `INVALID_CHAIN_ID` | unknown chain | **4903** `UNSUPPORTED_CHAIN` | chain not enabled here | Number changes | | 4004 / 4010 `INVALID_*_ADDR` | bad address | 4905 `INVALID_ADDRESS` | same condition | Number changes | | 4006 `TOO_SLIPPERY` | slippage unrealistic | 4904 `INVALID_SLIPPAGE` | above `MAX_SLIPPAGE_BIPS` | Number changes, and see point 6 | | 4007 `SAME_INPUT_OUTPUT` | tokens identical | 4900 `INVALID_REQUEST` | same condition | Number changes | | 4011/4012/4018/4019 `*_TOKEN_AMOUNT` | bad amount | 4906 `INVALID_AMOUNT` | same condition | Number changes | | 4015 `INVALID_TOKEN_PROPORTIONS` (`0 < p < 1`) | proportions do not sum to 1 | **4901** `MULTI_TOKEN_UNSUPPORTED` | a partial share is a split intent | **Different code and class** | | 4015 `INVALID_TOKEN_PROPORTIONS` (`p <= 0`, `p > 1`, non-numeric) | same on Odos | 4900 `INVALID_REQUEST` | malformed, not unsupported | Number changes | | 4016 `TOKEN_ROUTING_UNAVAILABLE` | no route for the pair | 2000 `NO_ROUTE` | same meaning, different band | Band changes | | 4201 `USER_ADDR_REQ` on `/sor/quote/v3` | `userAddr` missing | **200 OK** | quote-only is a supported mode, not an error | **Delete the branch** | | 4201 `USER_ADDR_REQ` on `/sor/swap/v3` | `userAddr` missing | **4911** `NOT_ASSEMBLABLE` | needs an owner to draft an order for | Different code | | 4201 `USER_ADDR_REQ` on `/sor/assemble` | `userAddr` missing | **4905** `INVALID_ADDRESS` | fails address validation | Different code | | 5001 `SWAP_UNAVAILABLE` | route unavailable | 2000 `NO_ROUTE` | same meaning, different band | Band changes | | n/a | none | **4901** `MULTI_TOKEN_UNSUPPORTED` | multi-token, or a single output whose proportion is neither 1 nor a whole share | New branch | | n/a | none | **4902** `PARTNER_FEE_UNAVAILABLE` | integrator fees are off on this deployment | New branch | | n/a | none | **4908** `PATH_ID_EXPIRED` (410) | re-quote, do not retry | New branch | | n/a | none | **5901** `CONFIG_MISSING` | server misconfigured, not your fault | New branch. Report it with the `traceId` | Compat-specific codes sit at 49xx and 59xx precisely so they can never collide with an orderbook-issued code. That also means **almost none of them match the Odos number for the same condition**. **Match on the string `code`, not `numericCode`.** The strings are stable and mean one thing each. The numbers collide across the two systems, and 3000 is a "give up" on Odos and a "retry" here. ## Chains | chainId | Network | Orderbook | Runtime check | |---|---|---|---| | 10 | Optimism | `https://optimism-mainnet.ophis.fi` (Ophis-operated) | [Version endpoint](https://optimism-mainnet.ophis.fi/api/v1/version) | | 130 | Unichain | `https://unichain-mainnet.ophis.fi` (Ophis-operated) | [Version endpoint](https://unichain-mainnet.ophis.fi/api/v1/version) | | 4663 | Robinhood Chain | `https://robinhood-mainnet.ophis.fi` (Ophis-operated) | [Version endpoint](https://robinhood-mainnet.ophis.fi/api/v1/version) | Other chains return `UNSUPPORTED_CHAIN` (4903), including chains Ophis serves through CoW-hosted orderbooks. Only the Ophis-operated sovereign chains are exposed here. Odos served 14 chains. If your volume was on Ethereum, Base, Arbitrum, Avalanche, BSC, Polygon, Linea, Sonic, Fraxtal, zkSync Era, Mantle or Mode, this surface does not cover it. ## Limits and lifetime - Best-effort edge rate limit: 60 requests per 60 seconds per IP and Cloudflare colo. Need more? Get in touch. - `pathId` lifetime: up to 60 seconds (never beyond the quote expiration). ## Help - SDK-first integration (no compat layer): [Partner integration](./partners.md) and the [intent API](./intent-api.md). - Agents and MCP: [AI agents](./ai-agents.md). - Issues: [github.com/ophis-fi/ophis](https://github.com/ophis-fi/ophis/issues). --- # Trade rewards URL: https://docs.ophis.fi/trade-rewards # Ophis trade rewards Ophis has prepared a finite reward pilot to study how traders use the product. The campaign is not active until Ophis publishes the Robinhood Chain distributor address and enables the claim service. There is no token launch or speculative airdrop. ## Fixed campaign - 105 winning tickets: 100 worth 1 USDG and 5 worth 10 USDG. - Total lifetime reward inventory: 150 USDG. - One ticket per wallet. - Every issued ticket wins; losing tickets are not created. - No claim deadline. An assigned reward remains claimable until it is claimed. - Rewards are paid in USDG on Robinhood Chain, and Ophis sponsors the claim transaction. The order of denominations is committed before the campaign starts. The unrevealed allocation seed is held separately from the public commitment so neither a trader nor the relayer can choose a ticket's denomination after a qualifying swap. Per-ticket odds are not presented in the swap UI. ## Eligibility A wallet must complete a settled Ophis swap with a priced value of at least **$100** on Ethereum, BNB Chain, Arbitrum, Optimism, Base, Robinhood Chain, Unichain, Plasma, Ink, Gnosis, Avalanche, or Polygon after the campaign starts. The indexer accepts only settled trades whose Ophis fee was verified; unrelated swaps and swaps between the same token do not count. Wallet age and wallet balance are not eligibility requirements. Each wallet can receive only one ticket. Ophis may exclude wallets involved in evident self-dealing, automation abuse, or coordinated attempts to manufacture qualifying volume. Exclusions are reviewed and recorded by an operator before rewards are assigned. ## Claiming Connect the qualifying wallet at [swap.ophis.fi/#/rewards](https://swap.ophis.fi/#/rewards). Once the signed assignment is confirmed on Robinhood Chain, select **Claim**. Ophis submits and pays for the transaction; the distributor sends USDG directly to the qualifying wallet. A relayer cannot redirect the payment. The campaign contract has no owner withdrawal function. The Ophis Safe can pause assignment and claims during an incident and rotate the offchain reward signer, but cannot sweep the committed USDG. --- # Vault manager rebalancing URL: https://docs.ophis.fi/vault-managers # Vault manager rebalancing If you run a vault or treasury from a Safe, rebalancing its underlying assets usually means handing an operations key the power to move funds. Ophis removes that trade-off: the curator key can trigger swaps, but an on-chain policy module checks every order against a fixed rulebook before anything is signed. A compromised curator key cannot redirect funds, cannot trade unlisted tokens, and cannot accept a bad price. The flow settles through CoW Protocol as one atomic, MEV-protected order. The vault Safe is both `order.from` and `order.receiver`, so funds never leave its control, and each order carries the Ophis partner fee. ## How it works Three parties, three roles: - **The vault Safe** holds the assets. Its owners keep full custody at all times and can disable the module whenever they want. - **The policy module** (`OphisVaultPolicyModule`) is a Safe module the owners enable once. It is the only path from the curator to the Safe. - **The curator** is a dedicated key (EOA, MPC signer, or multisig) that may call exactly two functions on the module: `rebalance` and `cancel`. It must not be a Safe owner and must not be an enabled Safe module; the module rejects both at deploy time. On every `rebalance(order)` call the module re-checks the full order on-chain and reverts if any rule fails: | Check | Guarantee | |---|---| | `receiver == the Safe` | Proceeds can only ever return to the vault | | Token allowlist | Only the underlyings the owners configured can trade | | Chainlink oracle floor | The order's minimum out must be within the configured band (default 50 bps) of the live oracle price. Stale or invalid oracle rounds fail closed | | Pinned appData | The order carries the exact Ophis fee metadata the owners froze at deploy; nothing can be swapped in | | Zero signed fee | The fee rides in appData only; a nonzero signed `feeAmount` is rejected | | TTL ceiling | Orders cannot outlive the configured window (the deploy scripts use 33 minutes: the builder's 30-minute order plus lag margin) | | Daily turnover cap | A rolling USD budget (leaky bucket) bounds how much value the curator can move per day | | L2 sequencer gate | On L2s, oracle reads are refused while the sequencer is down and during a grace period after recovery | Only when every check passes does the module set an exact-amount allowance to the settlement relayer and presign the order in the CoW settlement. After a fill (or a `cancel`) the allowance returns to zero. Fill-or-kill semantics mean each presign backs at most one fill. The module is immutable: no owner, no setters, no upgrade path. Changing policy means deploying a new module and switching over. ## Multi-token vaults The USDC/WETH pair in the examples is just the smallest case. The module is built with a LIST of tokens, each paired with its own Chainlink USD price feed and staleness window. The minimum is two, and there is no fixed upper bound beyond gas. A vault holding USDC, WETH, WBTC, and DAI, for instance, deploys with four entries. How the token set behaves: - **Any allowlisted token into any other.** The oracle floor is a cross-rate: for a sell of A into B the module reads A/USD and B/USD and requires the order to clear the implied price minus the slippage band. The curator rotates freely within the allowlist, always price-checked. A token that is not on the list is rejected as `TokenNotAllowed`. - **One shared daily budget, denominated in USD.** The turnover cap is global across every token, not per token, so a compromised curator cannot multiply its reach by rotating through many assets. One bucket bounds the whole vault. - **One live order per sell token.** Allowance tracking is per sell token, so there is never more than one in-flight presigned order per token sharing an allowance (the property the audit's regression invariant locks down). - **The set is fixed at deploy.** The module is immutable, so adding or removing a token means deploying a fresh module and re-enabling it. That is the deliberate trade for having no admin function that could inject a malicious token or feed. Two requirements for a token to be allowlistable: - It needs a Chainlink USD feed, which is how the module prices it. - It must not be fee-on-transfer or rebasing. The floor is computed on the gross sell amount, which those token types do not deliver in full, so they are excluded. A vault can still HOLD such assets; it just cannot rebalance them through this module. ## Operational chains The `@ophis/safe-swap` vault-order builder works on every chain with a live Ophis or CoW orderbook: - **Ophis self-hosted:** Optimism, Unichain, and Robinhood Chain (4663). - **CoW-hosted:** Ethereum, Base, Arbitrum One, Polygon, Gnosis Chain, BNB Smart Chain, Avalanche, Linea, Ink, and **Plasma (9745)**. Plasma orders use CoW's production Plasma orderbook and canonical settlement. Because Plasma does not have native USDC, the repository's real-contract fork check uses WETH9 to USDT0 and verifies the relayer allowance, presignature, and exact token pull. This operational list describes where vault managers can build and submit Safe orders. The stronger on-chain policy-module rollout is tracked separately below. ## Live deployments The module is live on six chains (and has settled real rebalances on five). The contracts are identical everywhere; the per-chain difference is which settlement they gate and which Chainlink feeds they read. | Chain | Module factory | Settlement | |---|---|---| | Ethereum (1) | `0xd6e80ca05b8bfebdaf6338b1f22f98f065ce96f4` | Canonical CoW | | Optimism (10) | `0xd6e80ca05b8bfebdaf6338b1f22f98f065ce96f4` | Ophis self-hosted | | Base (8453) | `0xd6e80ca05b8bfebdaf6338b1f22f98f065ce96f4` | Canonical CoW | | Arbitrum One (42161) | `0xd6e80ca05b8bfebdaf6338b1f22f98f065ce96f4` | Canonical CoW | | Unichain (130) | `0x251195c88639fa9364302D51E649910A2537ee9d` | Ophis self-hosted | | Plasma (9745) | `0x8bfd02f98854647f4235d770a284ddbbac0bf2ca` | Canonical CoW | Plasma uses the native WXPL/USDT0 allowlist (it has no USDC) with Chainlink XPL/USD and USDT0/USD feeds. Each vault deploys its own module instance through the factory, configured with its own Safe, curator, allowlist, and caps. ## Implementing it The end-to-end operator guide (with verified per-chain addresses, deploy scripts, and fork preflights) lives in the repo: [`docs/operations/vault-policy-module-trial-runbook.md`](https://github.com/ophis-fi/ophis/blob/main/docs/operations/vault-policy-module-trial-runbook.md). The short version: **1. Derive the appData hash to pin.** The module accepts only orders carrying one exact appData document (chain, fee metadata, your Safe as signer). Derive its hash once and pass it to the deploy. **2. Deploy your module through the factory.** Per-chain forge scripts with verified feed and token addresses are in [`contracts/script/`](https://github.com/ophis-fi/ophis/tree/main/contracts/script). The constructor probes every configured Chainlink feed and the settlement at deploy, so a mis-configured deploy reverts instead of shipping. **3. Enable the module on your Safe.** One `enableModule(module)` transaction signed by the Safe owners. From this point the curator can rebalance and the owners retain everything else. **4. Build and submit orders with `@ophis/safe-swap`.** The published npm package quotes against the orderbook, assembles a hardened, receiver-pinned order, posts it, and returns the exact struct the module expects: ```ts import { buildOphisSafePresign } from '@ophis/safe-swap' const { orderUid, order } = await buildOphisSafePresign({ chainId: 10, safe: VAULT_SAFE, sellToken: USDC, buyToken: WETH, sellAmount: '250000000000', // atomic units slippageBps: 30, ttlSeconds: 1500, // keep under the module's maxTtl }) // The curator then calls, from its own key: // module.rebalance(order, minBuyOverride) // The module re-derives the same orderUid, re-checks every field on-chain, // sets the exact allowance, and presigns. Solvers settle it like any CoW order. ``` Practical notes from the live rollout: - Give the module's `maxTtl` headroom over the order TTL (the deploy scripts use 1980s vs the builder's default 1800s) so block-timestamp lag never rejects a fresh order, while keeping the fill window tight. - The oracle floor band (50 bps) must cover the order's slippage plus the quote fee. On L1, fees on very small orders can exceed the band; size orders so the fee is a few basis points and this never matters. Production-size rebalances are unaffected. - If the floor rejects an order (`BelowFloor`), nothing was signed and no funds moved. Rebuild with tighter slippage or retry after the next oracle update. ## Security model The module's guarantee is deliberately narrow and testable: **a compromised curator key cannot drain the vault.** The worst it can do is trigger policy-valid rebalances between allowlisted tokens, at prices within the oracle band, bounded by the daily turnover cap. The disclosed residual is price bleed inside that envelope: at most the floor band per order, capped by the daily budget. The contracts went through a 12-agent adversarial audit, Trail of Bits semgrep rules, Echidna and Foundry invariant fuzzing (including a regression invariant for the one-live-order-per-token allowance discipline), and independent review, with fork preflights against real chain state gating every deploy. During the live rollout the oracle floor rejected mispriced orders in production exactly as designed. Vault owners keep an unconditional exit: `disableModule` ends the curator's access instantly, and `cancel` revokes any open order. ## Fees Rebalances use the canonical all-chain Ophis policy: a 1 bp base plus 80% of reference-quote improvement capped at 99 bps on volatile pairs, or 50% capped at 20 bps on stable pairs. Operated-chain backends add the improvement policy; hosted orders encode it in CIP-75 appData and separately pay CoW Protocol's upstream fees. See [Fees](/fees) for the full schedule. ## FAQ **What problem does this actually solve?** Running an active vault strategy normally means giving an operations key the power to move funds, which is the same power needed to steal them. The policy module separates the two. The curator key can trigger swaps but can only ever produce policy-valid orders: proceeds back to the vault, allowlisted tokens, oracle-priced, capped daily volume. A leaked or rogue curator key cannot redirect a single token out of the vault. At worst it can make slightly-off but policy-valid trades, bounded by the daily budget. **What does a vault manager have to do to start using it?** Four steps: deploy a module for your Safe from the per-chain factory; enable it on the Safe with one owner-signed `enableModule` transaction; build orders with the `@ophis/safe-swap` package; and have the curator key call `module.rebalance(order)`. Funds never leave the Safe, and the owners can `disableModule` at any time. The [runbook](https://github.com/ophis-fi/ophis/blob/main/docs/operations/vault-policy-module-trial-runbook.md) has the full sequence. **Can a vault rebalance more than two tokens?** Yes. The module takes a list of tokens, each with its own price feed, minimum two and no fixed upper bound. See [Multi-token vaults](#multi-token-vaults) for how the allowlist, the shared USD budget, and the fixed-at-deploy token set behave. **Why is the module factory at a different address on Unichain?** A contract's address is derived from its deployer and that deployer's nonce. On Ethereum, Optimism, Base, and Arbitrum the factory was the deployer's first transaction on each chain, so it landed at the same address everywhere. On Unichain the deployer had already transacted, so the factory came out at a different nonce and therefore a different address. It is the same factory contract in every case.