# 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.** Choose the tokens and amount, review the quote and spending limit, and sign with your own wallet. Solvers find a route that satisfies your signed order. The standalone app also accepts supported external balances through NEAR Intents. Select the source and destination in the same token pickers, review the quote and refund address, then fund the quoted deposit. See [Networks & assets](./networks-assets.md) for coverage and recovery. Ophis builds on [CoW Protocol](https://docs.cow.fi/cow-protocol). The [Intent API](./intent-api.md) also supports natural-language requests for developers and agents. The full source is at [github.com/ophis-fi/ophis](https://github.com/ophis-fi/ophis). ``` Choose tokens and amount → review quote → sign order → 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 per token pair, mitigating front-running and sandwiching. This is a mechanism-level defense, not an absolute guarantee against every adversarial condition. - **Wallet-authorized.** Standard ERC-20 orders keep funds in your wallet until settlement. Native-token orders and bridge routes can require onchain deposits; their custody, gas and recovery rules differ. See [Security & audits](./audits.md). - **Transparent, chain-aware fees.** The standard batch-auction policy 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). Arc applies the same capped backend improvement policy for in-market orders. - **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) | Standard pricing, hosted upstream fees and rebates. | | [Networks & assets](./networks-assets.md) | App versus SDK coverage, Arc bridges, token labels and feature availability. | | [Affiliate program](./affiliate.md) | Share a referral code, earn 8% of Ophis's verified base fee on eligible referred trades. | | [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:** [swap.ophis.fi](https://swap.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 Choose your tokens and networks, review the quote, then authorize the displayed order or fund the quoted deposit from your source wallet. ## Your first swap 1. Open [the swap app](https://swap.ophis.fi/#/swap). Connect your EVM wallet for batch-auction swaps; externally funded routes can use a separate source wallet. 2. Select the token and network you want to pay from, then enter the amount. 3. Open **You receive** to select the destination network and token. For a cross-chain route, check the destination-address and wallet requirements. 4. Review the quote, fees, destination address, and spending limit. For a batch-auction swap, approve the displayed amount if needed and **sign the order**. For an externally funded swap, also enter a source-chain refund address, confirm the quote, and send the specified deposit before its deadline, including any required memo. Keep the saved transfer until delivery or refund. :::note[Non-custodial by design] Ophis does not hold your signing key. Standard ERC-20 orders stay in your wallet until settlement; native-token orders and bridge routes can require an onchain deposit or transfer first. Their recovery and trust assumptions are route-specific; see [Networks & assets](./networks-assets.md). ::: ## How it works The following steps describe batch-auction orders. Externally funded NEAR and direct Circle routes follow their provider's deposit and delivery lifecycle; see [Networks & assets](./networks-assets.md). ### 1 · Choose tokens and amount The swap form opens directly. Select the assets and networks, enter the amount, and review the quote and spending limit. Developers and agents can still use the [Intent API](./intent-api.md) for natural-language requests. ### 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, Robinhood Chain, and Arc, 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 uses uniform clearing prices for each token pair. 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 supports batch-auction orders on **14 EVM chains**: Ethereum, Arbitrum One, Avalanche, Base, BNB Smart Chain, Gnosis Chain, Ink, Linea, Optimism, Plasma, Polygon, Robinhood Chain, Unichain, and Arc (plus the Sepolia testnet). Same-chain trading and cross-chain route availability are separate: a listed chain does not guarantee every source/destination pair. Arc is available in the swap app (chain ID 5042). Its native gas currency is USDC; Circle routes include USDC and mapped expanded assets, plus Ethereum WBTC to Arc cirBTC through a separate conversion leg. See the restrictions and recovery model in [Networks & assets](./networks-assets.md). SDK v0.4.3 and the hosted MCP chain lists also include Arc. | | | | | -------- | --------------- | --------------- | | Ethereum | Arbitrum One | Avalanche | | Base | BNB Smart Chain | Gnosis Chain | | Ink | Linea | Optimism | | Plasma | Polygon | Robinhood Chain | | Unichain | Arc | | The standalone app also supports **Solana, Bitcoin, Monad, Hyperliquid (Hypercore), X Layer, Sui, Tron, Starknet and Zcash** as sources and destinations through [NEAR Intents](https://near.org/intents), where live assets and quotes permit. Select the source in **You sell** to enter the externally funded flow; select the destination in **You receive**. These networks do not gain a CoW orderbook or SDK signing domain. See the [funding, address and recovery requirements](./networks-assets.md#externally-funded-swaps-through-near-intents). 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/) Offchain ERC-20 order signatures 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 a 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. The app opens a token-selection form; developers can optionally use the natural-language parser before building an order. Circle and other cross-chain routes have separate execution and recovery steps. ## 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` to reduce extraction variability. The proxy: - holds the LibertAI API key server-side (browsers never see it), - validates symbol syntax and source-text spans, not token contracts, - validates the chain against its 14 supported slugs, including Arc, 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, Robinhood Chain, and Arc, Ophis runs its own stack and 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 onchain using uniform clearing prices per token pair and enforcing each order's signed limits. Offchain ERC-20 orders avoid the user's public-mempool router transaction, and peer matching can avoid an AMM leg. These mechanisms mitigate MEV; they do not eliminate all risks in solver execution, external liquidity, sequencers or bridge delivery. See [Security & audits](./audits.md#mev-protection-by-construction). ## What Ophis runs | Component | Description | | --- | --- | | **Frontend** | A fork of the CoW Swap frontend with token selection, route review and provider-specific bridge flows. | | **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** | Ophis operates Optimism, Unichain, Robinhood Chain and Arc; see the hosts in [Status](./status.md). CoW-hosted chains use `api.cow.fi` and CoW's solver network. | | **Settlement contracts** | Ophis deployments of `GPv2Settlement`, alongside allowlist and fee-handling contracts. Deployment configuration and review scope matter; 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, Bitcoin, Monad, Hyperliquid (Hypercore), X Layer, Sui, Tron, Starknet and Zcash are available as sources and destinations through NEAR Intents for supported assets and quoted routes. From a batch-auction source, the user supplies the destination address and authorizes the source order. Selecting an additional source in the standalone app instead opens an externally funded flow: review the quote and refund address, confirm, then transfer the quoted deposit from the source wallet. It does not submit a CoW order on that external network. Destination delivery is separate from source settlement and can require recovery. Across and Circle routes have different coverage and wallet restrictions; see [Networks & assets](./networks-assets.md). ## 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 The standard batch-auction schedule has a **1 bp Ophis base fee**. On eligible orders, 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 Optimism, Unichain, Robinhood Chain and Arc, this backend fee applies only to **in-market orders**. Out-of-market limit orders pay the signed base without this backend improvement fee. On CoW-hosted chains, CoW Protocol applies its own upstream fee policy separately. The table separates Ophis and upstream batch-auction fees. It is not a total execution-cost estimate: gas, pool fees, price impact and bridge costs can also affect the net amount. Check the final route quote before signing. ## The all-in cost, per chain | | Ophis-operated chains (Optimism, Unichain, Robinhood Chain, Arc) | CoW-hosted chains (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) | | **Base + upstream volume components** | **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, Robinhood Chain, and Arc**, 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. ### Arc price-improvement policy Arc uses the same backend policy for in-market orders as the other Ophis-operated chains: **80% of reference-quote improvement capped at 99 bps**, or **50% capped at 20 bps** for recognized stablecoin pairs, including USDC/EURC. Eligibility follows the order's price relative to its reference quote, not the frontend's market/limit label. An out-of-market limit order has no backend improvement fee. The separate **1 bp base** remains in signed appData; clients must not duplicate the backend improvement policy in appData. Arc originally launched with an empty protocol-fee configuration. Historical base-only executions remain recorded at their original fees; enabling the standard policy does not recalculate past trades. Arc settled trades are indexed for volume-tier and affiliate rebates under the same eligibility rules. Use SDK v0.4.4 or later for Arc referral tags. This does not enable an Arc own-fee payout guarantee. ## How it works - 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. ## Standard price-improvement capture Solvers compete to fill your order. **Price improvement** is measured against the reference quote; **surplus** relative to your signed limit is a different measure when that limit includes slippage. Neither is a guaranteed return. 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, Robinhood Chain, and Arc:** the backend applies the capped capture model to in-market orders 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. CoW applies protocol policies before partner policies, with iterative fee calculation. Do not add their capture percentages as if both apply independently to the original improvement. See [CoW's current fee policy](https://docs.cow.fi/governance/fees). ### Bridge and conversion routes NEAR Intents quotes, including the standalone externally funded flow, request a **3 bps Ophis app fee**, alongside provider costs reflected in the quote. A direct NEAR deposit is not a batch-auction order and does not also use the 1 bp base and capped improvement schedule above. Direct Circle bridge and WBTC-conversion routes are not batch-auction swaps and must not be priced using the table above. Source approval/deposit, conversion and destination execution can require gas. See [Networks & assets](./networks-assets.md) for route restrictions and recovery. ## Comparing execution costs Compare the final executable output for the same amount and token pair, including wallet gas where applicable. An Ophis base fee is not a substitute for an AMM's pool fee: a solver route can itself use that AMM and incur its liquidity costs. Improvement capture and hosted protocol fees also affect the result. A lower advertised base rate alone does not establish a saving over a direct swap. ## WETH rebate calculation The rebate calculation allocates **21.25% of collected WETH fees** to eligible wallets in proportion to their **30-day volume weighted by tier**. WETH payout processing requires reconciled accounting, sufficient funds and Safe approval. An estimate or cycle date is not proof of payment; executed payout transactions establish what was paid. | 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 wallet's share is its `eligible 30-day volume × tier weight`, divided by the sum of that value across every eligible wallet. For example, $100,000 of Gold volume has a weighted value of $25,000. If the total weighted value is $250,000, that wallet receives 10% of the pool: $1,000 equivalent from an illustrative $10,000 WETH pool. Actual amounts depend on collected WETH and all participants; this is not a promised payout. 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. Rebates are separate from execution output. Neither a rebate nor positive surplus is guaranteed, and a tier weight is not a refund percentage of your own fees. ## 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 in the standard schedule apply that second component in the backend instead to avoid duplication. On the **SDK-supported Ophis stacks (Optimism, Unichain, Robinhood Chain, Arc)**, the backend also enforces an **anti-abuse minimum** in backend order validation, rather than relying only on frontend metadata: it rejects an order to the Ophis fee recipient whose partner fee falls below **1 bp**. This rejects declared sub-floor fee entries; absent fee metadata does not establish the same guarantee. It is not a Solidity minimum-fee invariant in the settlement contract. 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 Earn a share of your referrals’ trading fees. View rewards and payout status in your [dashboard](https://swap.ophis.fi/#/affiliate). Arc (5042) is included in affiliate and volume-tier rebate indexing. Share a referral code. When someone you refer trades on an indexed Ophis chain, **you earn a share of the verified base fee Ophis keeps on eligible attributed trades.** :::note[Payout status] WETH payouts require enabled processing, reconciled accounting, funds and Safe approval. The dashboard shows current status; estimated earnings are not completed payments. ::: ## 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 | - Payout denomination: **WETH**, with monthly processing when enabled. - **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. - **Dashboard counts combine both attribution paths.** A wallet appearing through a link and an eligible code-tagged trade counts once. Code tags do not create permanent binds. ## 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 | | Payout design | WETH, monthly when enabled | WETH, monthly subject to readiness and Safe approval | 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 Reviewed **October 1, 2026**. Compare the net output of current quotes for the same assets, size and destination. A headline interface fee alone does not measure execution cost, liquidity or safety. ## What "intent-based" means An intent specifies an outcome rather than a fixed route. In an Ophis batch-auction swap you authorize a sell amount, receiver, minimum buy amount and expiry. A solver may settle only within those limits. Uniform clearing prices apply per token pair, not across unrelated assets. Offchain orders and batch auctions mitigate common MEV. The solver's external liquidity transaction can still be public; this is not universal sandwich immunity. See [Security & audits](./audits.md). ## Ophis is built on CoW Protocol Ophis uses CoW settlement primitives. It operates its own stacks on Optimism, Unichain, Robinhood Chain and Arc, and uses CoW-hosted orderbooks on ten other chains. Shared foundations do not imply identical solver competition, execution quality, infrastructure or audit coverage. Standard signed ERC-20 orders keep funds in the wallet until settlement. Native-token deposits and bridge routes have separate custody and recovery rules. Ophis-specific changes are described in the security documentation. ## Core differences ### Natural-language input vs token-picker The swap app provides token selection and quotes. The optional public [Intent API](./intent-api.md) translates natural language into structured fields; it does not quote, authorize or settle a trade. Agents can use the MCP server or SDK for the subsequent workflow, retaining wallet authorization. ### Cross-chain scope The app supports **14 EVM chains**, including Arc. SDK v0.4.3 and hosted MCP chain mappings cover the same 14 networks, including Arc, for batch-auction orders. The standalone app also supports externally funded NEAR swaps from Bitcoin, Solana, Monad, X Layer, Sui, Tron, Hyperliquid (Hypercore), Starknet and Zcash; these networks can also receive supported routes. This does not extend the SDK's order-building coverage. Circle and Across routes have different source, asset and wallet restrictions; no provider covers every possible chain pair. See [Networks & assets](./networks-assets.md) for exact boundaries. A destination wallet may be needed, and source settlement is not proof of final delivery. ### Fee transparency The standard schedule charges a **1 bp base**, plus **80% of reference-quote improvement on volatile pairs capped at 99 bps**, or **50% on stable pairs capped at 20 bps**. CoW-hosted chains additionally apply upstream protocol fees. Bridge fees and gas are separate; use [Fees & rebates](./fees.md), not this summary, for cost assumptions. Arc follows the same backend improvement policy for in-market orders as Optimism, Unichain and Robinhood Chain. Competitor pricing can vary with route, pair and order type. Check current [CoW fees](https://docs.cow.fi/governance/fees), [Matcha help](https://help.matcha.xyz/) and [Velora documentation](https://docs.velora.xyz/docs) alongside a live quote. Velora documents intent, OTC, TWAP and cross-chain products; describing it as only a traditional same-chain aggregator is no longer accurate. ### Where the surplus goes Reference-quote improvement and surplus over the signed limit are not the same measure. On Optimism, Unichain, Robinhood Chain and Arc the backend applies Ophis's capped improvement policy. On hosted chains Ophis encodes its policy in appData; CoW applies protocol policies **before** partner policies, calculating fees iteratively. Do not add capture percentages against the original improvement. See [CoW's calculation order](https://docs.cow.fi/governance/fees). ### Agent-first API Ophis offers a keyless natural-language parser, a hosted MCP endpoint, SDKs and wallet-policy tooling. Parsing is not execution permission. Read tools, order building, signing and submission remain separate stages. See [AI agent integration](./ai-agents.md) for supported tools and chain mappings. ## Where each excels Choose by the actual route and workflow: supported contracts and networks, net output after fees, expiry/cancellation, bridge recovery and wallet control. Do not infer the best price or deepest liquidity from a site's chain count. ## Trade-offs, stated plainly Ophis-operated lanes depend on their configured venue liquidity and runtime availability. A displayed DEX identifies a liquidity venue, not necessarily an independent solver operator. CoW-hosted routes depend on upstream service and fee policies. Both can fail to find an acceptable fill. ## Reference table | Ophis surface | Current scope | | --- | --- | | Swap app | Batch-auction orders on 14 EVM chains; additional externally funded NEAR sources and route-specific bridges | | SDK v0.4.3 / hosted MCP | 14 EVM chains, including Arc | | Intent API | Parses fields; does not execute swaps | | Batch-auction fees | Standard base plus capped improvement; upstream fees on hosted chains | | MEV protection | Mechanism-level mitigation, not a universal guarantee | | Rebates | Eligible collected WETH, tier-weighted; not a guaranteed return | ## Read next - [Fees & rebates](./fees.md): fee scope, stablecoin treatment and rebates. - [How it works](./architecture.md): order lifecycle and per-chain settlement. - [Status](./status.md): service checks. - [FAQ](./faq.mdx): custody, cancellation and supported networks. --- # 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 a text parser, separate from the MCP server, compatibility API and rebate/reward APIs. 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 uses `temperature: 0` to reduce variation. This is not a guarantee of identical outputs across model runs or upgrades; validate every response. ### 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 | A case-insensitive match somewhere in the input. | | `start` | integer | Model-proposed, in-bounds start offset (inclusive); may be inaccurate. | | `end` | integer | Model-proposed, in-bounds end offset (exclusive); re-anchor `raw` before highlighting. | **Token values** use bounded symbol syntax: 2–12 letters/digits, at least one letter, excluding common non-token words. Values must derive from `raw`, which must occur in the input; exact offsets are not guaranteed. This is not contract verification; resolve the chain/address and request a quote before building an order. **Chain values** are the parser's supported lowercase slugs. This set is separate from the app's network selector: `arc` is recognized, but the app blocks an explicit chain disabled in its deployment. ``` ethereum arbitrum arc avalanche base bnb gnosis ink linea optimism plasma polygon robinhood 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.2**. 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 against trusted Ophis/CoW lists. Check the returned chain, exact address and any ambiguity before quoting or building. | | `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 14 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, "arc": 5042, } 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 apply referral attribution on indexed chains when a code is supplied: | Package | Version | For | Registers | | ------------------------------------------------------------------------------ | ------- | --------------------------------------------------------- | ------------------------------------ | | [`@ophis/agentkit-ophis`](https://www.npmjs.com/package/@ophis/agentkit-ophis) | v0.3.7 | [Coinbase AgentKit](https://github.com/coinbase/agentkit) | an `OphisActionProvider_swap` action | | [`@ophis/plugin-goat`](https://www.npmjs.com/package/@ophis/plugin-goat) | v0.3.7 | [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.7 | [elizaOS](https://github.com/elizaOS/eliza) | a `swap` action | | [`@ophis/agent-swap`](https://www.npmjs.com/package/@ophis/agent-swap) | v0.3.7 | any custom EOA framework | the `executeOphisSwap()` core | The v0.3.7 adapter family is built and published against `@ophis/sdk` v0.4.5, so its fee policy, orderbook hosts, settlement contracts, and vault relayers match the current SDK. The core and AgentKit can use an Arc wallet with ERC-20 addresses; configured wrapper defaults and explicit per-call codes now support Arc referral attribution. GOAT discovery and the elizaOS chain resolver retain their existing chain lists and do not expose Arc merely because their SDK dependency supports it. 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. Settlement validates that receiver and may use external liquidity interactions. Upstream CoW audits and Ophis reviews have separate scopes; see [Security & audits](./audits.md). 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 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 published skills cover 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 of the verified base fee Ophis keeps. Payouts require activation, reconciled accounting, funding and Safe approval; accrual is not payment. The code rides in the order's appData, so there is nothing for the end user to sign or opt into. 1. Open the [affiliate page](https://swap.ophis.fi/#/affiliate) and connect a wallet. 2. Mint a code tied to your wallet. 3. Pass it to any adapter as `referralCode`, or export `OPHIS_REFERRAL_CODE` and the adapters pick it up automatically. Arc is indexed for rebates. SDK v0.4.4 and MCP accept Arc referral codes; MCP applies its configured default unless a per-call code overrides it (an empty string opts out). The code is **optional**: without one your agent still swaps normally, it just earns no referral 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.5, 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, Robinhood Chain, and Arc do not live on api.cow.fi] Optimism, Unichain, Robinhood Chain, and Arc break the `api.cow.fi/` pattern. Ophis self-hosts their orderbooks at `optimism-mainnet.ophis.fi`, `unichain-mainnet.ophis.fi`, `robinhood-mainnet.ophis.fi`, and `arc-mainnet.ophis.fi` (Arc chain ID **5042**). Posting one of their orders to `api.cow.fi/` (a host that does not serve Ophis) **is an unsupported host/domain combination, not a fee-free route**. 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 const arcOrderbookUrl = getOphisOrderbookUrl(5042); // -> https://arc-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. Arc settled trades are ingested by the rebate indexer. Use SDK v0.4.4 or later for Arc referral tags; enrollment and verified settlement determine eligibility. 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, Robinhood Chain, and Arc 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, Robinhood Chain, and Arc settlements are not the canonical CoW one] On Optimism, Ophis's GPv2Settlement is `0x310784c7…B859`, on Unichain it is `0x108A678716e5E1776036eF044CAB7064226F714E`, on Robinhood Chain it is `0x886d9fd312F442C4E1f3cdeAE7b4AB73493e57cD`, and on Arc (chain ID **5042**) it is `0x78799F98276efba1EdeeD32eae03a3fd8Cdfec3A`, **not** the canonical `0x9008D19f…ab41`. cow-sdk defaults to the canonical address, so signing an Arc 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 use the Ophis-operated or CoW-hosted settlement path for the selected chain and carry the Ophis fee configuration automatically. See [networks and routes](./networks-assets.md) and the [chain-specific fee policy](./fees.md). The embed builds on the 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 eligible widget orders on indexed chains, including Arc (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, Robinhood Chain, and Arc 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, 14 app networks in total. The injected widget does not enable the direct Circle bridge flow; app and bridge support are separate capabilities. - **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 standard schedule is 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 when configured; hosted orders encode it in CIP-75 appData. Arc uses the same backend improvement policy for in-market orders. SDK v0.4.4 supports Arc referral tags, and eligible settled trades are indexed for rebates. 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 eligible widget orders on indexed chains (including Arc) 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. For Arc (5042), use SDK v0.4.4 or later for referral attribution and enroll the trader. ```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. Renew enrollment immediately before EVERY submission, including delayed // vault trades. The owner is the vault Safe, not its signing owner/curator. // This strict example stops on enrollment failure; retry enrollment before // submitting. Best-effort callers must inspect { enrolled } and report failure. await enrollOphisTrader(owner, { blocking: true }); // 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({ chainId, 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, Robinhood Chain and Arc 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, Arc) vs CoW-hosted chains Ophis serves two kinds of chain, and they differ in **where the order is posted** and **which settlement contract signs**: | | Self-hosted (Optimism, Unichain, Robinhood Chain, Arc) | 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` / `arc-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`, Arc `0x78799F98276efba1EdeeD32eae03a3fd8Cdfec3A` | CoW canonical `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` (cow-sdk default) | | Partner fee | 1 bp from `buildOphisAppDataPartnerFee(chainId)`; improvement policy is applied by the backend on every operated chain | Base + pair-aware improvement entries from `buildOphisAppDataPartnerFee(chainId, isStablePair)` | | Fee enforcement | Declared fee entries validated at backend ingress; not an on-chain minimum | 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 each Ophis-operated chain 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: that is an unsupported host/domain combination. 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), but an exact allowance can require renewal after a fill. Standard signed ERC-20 settlement is solver-paid; wrapping, native-token placement, hard cancellation and refunds can still require gas. ::: ## 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 } from '@ophis/sdk'; const partnerFee = buildOphisAppDataPartnerFee(chainId, isStablePair); if (!partnerFee) throw new Error('Unsupported Ophis fee chain'); // Put the complete value in metadata.partnerFee, then sign with the // CoW canonical domain. Hosted chains need both fee policy entries. ``` The fee recipient is one CREATE2-deterministic Safe. The helper returns a base volume object on SDK-supported operated chains and an array containing the base plus capped improvement policy on hosted chains. Do not replace that array with a volume-only entry. Arc is supported from SDK v0.4.3 with its own host and signing domain. ## Partner economics: the three layers These three layers describe indexed chains, including Arc. Arc applies the standard base plus capped improvement policy for in-market orders; referral indexing does not add an Arc own-fee payout guarantee: 1. **Your users get the chain's published integration pricing.** On sovereign chains, including Arc, 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 each eligible trade you route on an indexed chain (including Arc): 8% on the self-serve tier, **12% on the partner tier** (uncapped referred volume; ask us to upgrade your code). The payout design is monthly WETH, subject to activation, reconciliation, funding and Safe approval. Check the dashboard for current affiliate payout status. 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 when configured). ...(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 all supported chains, 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 ceiling for Ophis's registered hosted configuration is 190 bps, while a registered integrator entry remains capped at **90 bps**. The array can also be passed in the appData supplied to `buildOphisEthFlowOrder` for supported **native-ETH** sells. Keep the full Ophis policy and any onboarded own-fee entry: the helper validates arrays and requires the hosted volatile improvement policy. Do not bypass the helper's validation. 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 under Ophis's registered hosted configuration 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 and Unichain:** an onboarded third-party own-fee uses the two-step process below. Robinhood reporting exists, but its own-fee payout is not covered by these terms: 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. Payments to your recipient use 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. Affiliate payout execution requires activation, reconciliation, funding and Safe approval. Tag each eligible order on an indexed chain, including Arc, with your referral code. Improvement capture remains excluded until receipts can be reconciled to the Ophis Safe. ```ts import { buildOphisAppDataPartnerFee, buildOphisReferrerMetadata, } from '@ophis/sdk'; const partnerFee = buildOphisAppDataPartnerFee(chainId, isStablePair); if (!partnerFee) throw new Error('Unsupported Ophis fee chain'); const doc = await new MetadataApi().generateAppDataDoc({ appCode: 'ophis', // REQUIRED: 'ophis', NOT your app's name (see below) metadata: { partnerFee, ...buildOphisReferrerMetadata('your-code', chainId), hooks: {}, }, }); ``` The rebate indexer reads `metadata.ophisReferrer.code` from settled orders on its supported chains, credits your referred USD volume **across indexed chains, including Arc**. WETH payout execution requires activation, reconciliation, funding and Safe approval. Indexed estimates are not payments. 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) immediately before each submission, with a public idempotent call: ```bash curl --fail --max-time 10 https://rebates.ophis.fi/tier/ ``` Successful registration renews the inactivity clock; it does not change the wallet's first-seen date. Empty wallets may be pruned after seven inactive days only after orderbooks confirm no order history and supported production RPCs confirm no contract code. Contract wallets are retained conservatively: a queued Safe TWAP may not yet have a child order. Existing orders and failed verification also retain tracking. Do not rely on discovery coverage or on registration performed weeks before a vault first trades. Reconcile missing history before a payout cycle is finalized. ::: The partner dashboard counts distinct link-bound and eligible code-attributed wallets. An order tag does not create a permanent referral binding. Contract wallets authenticate with an EIP-1271 signature on their operator-approved authentication chain, configured in `CONTRACT_WALLET_AUTH_CHAINS`. Contact Ophis to confirm that chain before using a Safe for dashboard/referral/reward access; unconfigured or mismatched chains are rejected. This prevents a same-address Safe with different owners on another chain from accessing the global wallet record. Signed API requests include `chainId` and append `\nChain ID: ` to the action/address/issued message. Legacy EOA-only messages without a chain remain supported. Payout status and estimates are separate from executed payment records. ## 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), Robinhood Chain (4663), and Arc (5042) are Ophis-operated. The earnings indexer includes all four; the automated sovereign **own-fee payout guarantee** is limited to chains 10 and 130. Robinhood and Arc reporting do not imply own-fee payout coverage. 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 accounts for the **referral rebate** on indexed chains (affiliate payout execution requires activation, reconciliation, funding and Safe approval). A stacked third-party **own-fee** can be paid 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. Charged and reported own-fees are not proof of payment; check the executed payout records for your recipient. 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 can be paid to your recipient 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 rebate attributed to your wallet when your `appCode` is a registered referral code. Check the partner dashboard for current WETH payout readiness. `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; payout requires onboarding, activation and funding "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 attributed to your wallet; payout execution requires activation, reconciliation, funding and Safe approval. Paid-to-date reports executed batches only, 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 can be paid to your recipient 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. Arc uses native USDC and has no supported EthFlow deployment; use its ERC-20 USDC interface. ## 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, Robinhood Chain and Arc are SDK-supported self-hosted chains.** Arc referral attribution requires SDK v0.4.4 or later; the other SDK 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.** That is an unsupported host/domain combination, not a valid fee-free route. ## 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, chainId)` | `buildOphisReferrerMetadata(code, chainId)` | | 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] Standard signed orders enforce your sell amount, receiver and limit price in the settlement contract. Batch auctions mitigate common MEV; they do not remove every execution risk. Native-token, bridge, vault and OTC paths have additional contract and recovery assumptions. The governance addresses below describe the listed deployment, not every contract used by every route. ::: Ophis uses wallet-authorized orders and transactions. The enforced limits and recovery rules depend on the path you authorize. This page separates batch-auction settlement contracts from bridges, deposits and external escrow, and identifies the deployment and review scope behind each claim. ## Custody Standard ERC-20 swaps use EIP-712 wallet signatures, ERC-1271 validation, or explicit onchain presigning. Funds remain in the wallet until settlement. 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 those limits. The settlement enforces the authorized sell amount and any applicable signed fee, the receiver and the minimum buy amount. Native-token orders deposit into EthFlow before settlement. Bridge routes can deposit or burn assets before delivery and follow provider recovery rules. The optional OTC integration uses external escrow, while vault modules can authorize presigned orders. These are not the same custody or expiry model as an offchain ERC-20 order. Always check the contract, approval, receiver and minimum amount in your wallet; an immutable settlement does not prevent a compromised interface from asking you to authorize a harmful action. Externally funded NEAR swaps start with a source-chain deposit rather than a CoW order signature. Verify the asset, amount, destination, refund address, deadline and any memo before sending. Keep the saved transfer for delivery or refund tracking. The settlement addresses below do not authenticate a NEAR deposit address or extend CoW's audit scope to NEAR, Across or Circle routes. ## MEV protection by construction Batch-auction orders settle with a uniform clearing price per token pair. This is designed to mitigate common MEV vectors at the mechanism layer: - Orders are submitted offchain instead of broadcasting individual public swaps. - Uniform batch prices reduce ordering advantages within a token pair. - Signed limits constrain what a solver may execute. 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, Robinhood Chain, and Arc. The listed settlement, relayer and EthFlow contracts are **immutable**: they have no upgrade admin or proxy. Their bytecode and constructor wiring cannot be replaced by an Ophis operator. The separate mutable solver allowlist still affects who may settle orders; immutability does not guarantee availability. Addresses were cross-checked against the [SDK deployment map](https://github.com/ophis-fi/ophis/blob/main/packages/sdk/src/domain.ts) and public RPC code/relayer reads on October 1, 2026. Chain ID is part of the identity: never reuse a listed address on a different network. | Contract | Address (Optimism, chain 10) | 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, chain 130) | 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, chain 4663) | 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 Robinhood settlement remains [`0x886d9fd312F442C4E1f3cdeAE7b4AB73493e57cD`](https://robinhoodchain.blockscout.com/address/0x886d9fd312F442C4E1f3cdeAE7b4AB73493e57cD). It is an Ophis deployment, distinct from CoW's canonical settlement address on CoW-hosted chains. | Contract | Address (Arc, chain 5042) | Property | | --- | --- | --- | | `GPv2Settlement` | `0x78799F98276efba1EdeeD32eae03a3fd8Cdfec3A` | Immutable, no admin/proxy | | `GPv2VaultRelayer` | `0x895505F1FE6D762296685fF4a8201782FFdCB8E9` | Immutable, only ever honors the Settlement above | Arc deployment configuration is recorded in the [Arc release sources](https://github.com/ophis-fi/ophis/tree/main/infra/arc-mainnet/release). SDK v0.4.3 includes Arc signing and approval helpers; use chain ID 5042 and do not substitute another chain's addresses. Arc has no supported EthFlow deployment; use its ERC-20 token interfaces for batch orders. These immutability claims do not cover token issuers, bridges or every external contract a route touches. The core settlement derives from CoW Protocol. Its upstream audits are relevant to shared code, but are not an audit of every Ophis modification or deployment: - 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) Ophis-specific contract changes reviewed in internal/tool-assisted security reviews include a hardened `GPv2AllowListAuthentication` (two-step manager transfer) and partner-fee settlement-buffer handling. Ophis also maintains frontend, solver, bridge and integration code. A review of those contract changes is not a blanket audit of every component or later release. ### 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. Use of Pashov or Trail of Bits skills and tools is not an organizational audit, endorsement or certification by those firms. ### 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. The placement transaction authorizes the limit price and receiver; it is not an offchain EIP-712 order signature. Unfilled deposits 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 In the Optimism settlement deployment listed below, the mutable governance surface is the **solver allowlist** (which addresses may settle batches). Its governance is: - 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 The documented governance and fee custody use multisigs: - 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. - Settlement transactions use operational solver keys. Solver authorization does not waive order validation or let a solver exceed an order's limits. Safe threshold and membership are onchain properties; hardware-key custody is an operational policy, not something an address alone proves. The partner-fee multisig (`0x858f0F5eE954846D47155F5203c04aF1819eCeF8`) holds collected protocol fees separately from trader balances. Route-specific contracts can hold native-token or bridge deposits as described above. 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 use app-specific **Content-Security-Policy** headers. The swap app permits `unsafe-eval`, broad HTTPS connections and embedding for its integrations; the landing policy is tighter. The sites 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. | | Arc orderbook | [version check](https://arc-mainnet.ophis.fi/api/v1/version) | Reachability of the chain-5042 orderbook; not proof of liquidity or settlement for a token pair. | 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), Robinhood Chain (chain 4663), and Arc (chain 5042). Orders settle through Ophis's self-hosted orderbooks at `optimism-mainnet.ophis.fi`, `unichain-mainnet.ophis.fi`, and `robinhood-mainnet.ophis.fi`, and `arc-mainnet.ophis.fi` using Ophis-deployed `GPv2Settlement` contracts. Resolve SDK-supported chains through `@ophis/sdk` v0.4.3 or MCP `list_chains`; both include Arc. Solana, Bitcoin, Monad, Hyperliquid (Hypercore), X Layer, Sui, Tron, Starknet and Zcash are additional sources and destinations via [NEAR Intents](https://near.org/intents). External-source swaps use the standalone app's deposit flow, not source-chain CoW orderbooks or SDK EIP-712 signing domains. Provider and asset availability still determine executable routes. See [Networks & assets](./networks-assets.md) for Circle bridges, stock labels and the current confidential-routing and institutional-feature boundaries. ## 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 batch-auction swaps use solver competition and signed limits to [mitigate MEV](./architecture.md). The optional natural-language API parses requests; it does not execute them. The [fee model](./fees.md) is published by settlement path. The standard schedule charges 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. Bridge costs are separate. ### Do I need to connect a wallet? Connect an EVM wallet for batch-auction orders. Externally funded NEAR routes can use a separate source wallet: review the quote, then send the specified deposit. Standard ERC-20 orders keep funds in the wallet until settlement; deposits have separate gas and recovery rules. ### Which networks are supported? The app supports batch-auction orders on 14 EVM chains: Ethereum, Arbitrum One, Avalanche, Base, BNB Smart Chain, Gnosis Chain, Ink, Linea, Optimism, Plasma, Polygon, Robinhood Chain, Unichain, and Arc. The standalone app also supports **Bitcoin, Solana, Monad, X Layer, Sui, Tron, Hyperliquid (Hypercore), Starknet and Zcash** as NEAR sources and destinations where assets and quotes permit. External-source swaps require a deposit and refund address. SDK v0.4.3 and hosted MCP chain lists cover the 14 EVM networks, including Arc; they do not build orders on the additional networks. See [Networks & assets](./networks-assets.md). ### Is my trade protected from MEV? Batch auctions use a uniform clearing price per token pair and offchain order submission to mitigate common MEV. Settlement transactions can still be public. Your signed limits are enforced, but this is not a guarantee against every front-running or sandwich scenario. See [Security & audits](./audits.md#mev-protection-by-construction). ### What happens if no solver matches my intent? An unfilled standard ERC-20 order expires after its validity window (30 minutes by default), leaving unsold funds in your wallet. Native-token deposits require an onchain refund; bridge routes follow provider recovery rules. Soft cancellation can race an in-flight settlement. ### Who runs the solvers? On the CoW-hosted chains Ophis surfaces, CoW's established solver network competes. On Optimism, Unichain, Robinhood Chain, and Arc, 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? Batch-auction orders carry Ophis's **1 bp base**. On Optimism, Unichain, Robinhood Chain and Arc the backend adds the capped improvement policy to in-market orders. 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, source-order path to **native Bitcoin** alongside the app's 14 EVM networks. SDK v0.4.3 and hosted MCP mappings cover the same 14 networks, including Arc. Source and asset support for NEAR routes is narrower than the same-chain list. 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 standalone app also supports externally funded Bitcoin and Solana sources. That deposit flow is separate from the EVM-order composition described here and is not a new MCP tool. See [Networks & assets](./networks-assets.md). ## 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 and signs nothing on the Bitcoin side. Source approvals and other wallet transactions can require gas. The EIP-712 limit bounds the **source swap**, not final BTC delivery; the provider quote governs that leg. ## App and agent coverage The Ophis **web app** supports BTC and SOL destination routes when assets and quotes permit. The hosted MCP server has no `swap_to_btc` or `swap_to_sol` tool. An agent integration must implement and validate the two-leg composition itself. The following is conceptual pseudocode, not a runnable 1-Click API example. Verify source/deposit asset, amount, destination, expiry and refund data before signing; do not override a receiver after signing or reuse mismatched quotes: ```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 supported orderbook only after quote/deposit validation. Keep // recovery state and track destination delivery separately from the source fill. ``` 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 an order on the source chain. The solver pays standard ERC-20 settlement gas; approvals, renewed allowances, cancellation or recovery transactions may still require gas. - **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 order.** Can the signer inspect and constrain the sell amount, minimum received and receiver before signing? A limit price constrains one fill, not future token value or cumulative losses. - **MEV mitigation.** How are orders submitted and settled, and what visibility or ordering risks remain? - **Settlement gas.** Who pays settlement gas, and which approvals or other transactions still need native 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 order.** The signed sell amount, minimum buy amount and receiver constrain one fill. They do not bound future token value or cumulative losses from a compromised signer repeatedly authorizing trades. - **MEV mitigation.** Offchain orders and uniform clearing prices per token pair reduce common ordering advantages. External-pool settlement can still be public and exposed to adversarial conditions. - **Solver-paid ERC-20 settlement.** Approvals, renewed exact allowances, wrapping, native-token placement, hard cancellation and refunds can need gas. - **Reach.** The app supports 14 EVM networks; SDK v0.4.3 and hosted MCP mappings cover the same 14, including Arc. NEAR destinations and sources are route-specific. The [BTC cookbook](./agent-btc-cookbook.md) describes a custom composition, not a shipped one-call BTC MCP tool. ## 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: - A receiver-pinned, limit-priced intent can constrain one trade. It is not automatically safer than every transaction: safety also depends on signer policy, approvals, chosen assets, price limits and cumulative spend. - 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 order from Ophis's builder pins the receiver to the owner and fixes the minimum received for one 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 chain-specific 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. Eligible settled Arc orders support referral accrual. 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)**, **Robinhood Chain (4663)**, and **Arc (5042)** run Ophis-deployed settlements, so those four 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 14 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` | | Arc *(sovereign)* | 5042 | `0x78799F98276efba1EdeeD32eae03a3fd8Cdfec3A` | `0x895505F1FE6D762296685fF4a8201782FFdCB8E9` | | 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 14 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. ::: --- # Odos v3 API migration to Ophis — compatibility and endpoints URL: https://docs.ophis.fi/migrating-from-odos # Migrating from Odos Ophis accepts a limited Odos v3 quote-request shape for one ERC-20 input and output, with EOA signing on enabled Ophis-operated chains. It returns a signable intent, not executable router calldata. Start with the [Odos API migration checklist](https://ophis.fi/migrate/odos-api/) to choose between this compatibility surface and the broader Ophis SDK. :::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. SDK v0.4.3 covers those three, Arc (5042), and 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.** 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 sell 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). --- # Networks, bridges & assets URL: https://docs.ophis.fi/networks-assets # Networks, bridges & assets Reviewed October 1, 2026. A listed network or token is not a promise that every pair has liquidity, that every wallet is eligible, or that a route will settle. Check the live quote and the signing request. ## App, parser and SDK coverage | Surface | Current scope | | --- | --- | | Swap app: batch-auction orders | 14 EVM networks, including Arc (5042); see [Getting started](./getting-started.md#supported-networks) | | SDK v0.4.3 and hosted MCP | 14 EVM networks, including Arc (5042), with chain-specific orderbook and signing-domain helpers | | Published vault-order builder | `@ophis/safe-swap` v0.1.7 uses SDK v0.4.4 and covers 14 EVM networks, including Arc, for ERC-20 presigned orders and referral attribution; this does not extend the on-chain policy-module rollout | | Intent parser | 14 chain slugs, including `arc`; the swap app blocks chains disabled in its deployment. Parsing does not verify a token contract or quote | | Standalone swap app: additional NEAR sources and destinations | Bitcoin, Solana, Monad, Hyperliquid (Hypercore), X Layer, Sui, Tron, Starknet and Zcash; source/asset/provider availability still gates each route. These are not additional CoW orderbook or SDK signing-domain deployments | Ophis operates the orderbook and routing lanes on **Optimism, Unichain, Robinhood Chain and Arc**. The other ten app networks use CoW-hosted orderbooks. Do not send Arc orders to a guessed CoW endpoint or use another chain's signing domain. Arc's app configuration is deployment-specific. SDK v0.4.3 resolves its orderbook to `https://arc-mainnet.ophis.fi` and its EIP-712 settlement to `0x78799F98276efba1EdeeD32eae03a3fd8Cdfec3A`. Arc settled trades are ingested by the rebate indexer; use SDK v0.4.4 or later for referral metadata. Arc applies the same capped backend market-order improvement policy as the other operated chains; see the [fee schedule](./fees.md#arc-price-improvement-policy). ## Externally funded swaps through NEAR Intents Use the same **You sell** and **You receive** network/token pickers in the standalone swap app. Choosing a source outside the batch-auction network set opens the externally funded flow automatically; a separate NEAR form is not required. Supported balances can be sent **from or to** the additional networks above, subject to the live asset catalogue and an executable quote. Source network names can remain visible while the catalogue is unavailable; that does not make a token or route available. The direct flow's configured receiving networks also include Ethereum, Base, Arbitrum, Optimism, Polygon, BNB, Avalanche, Gnosis, Plasma and Robinhood Chain. This is a different coverage set from the 14 batch-auction chains. Arc uses the Circle routes below; do not infer direct NEAR support for Arc, Unichain, Ink or Linea. Review the source asset and exact amount, destination asset and minimum receive, destination address, source-chain refund address and deposit deadline. Confirming the quote creates a transfer record before funding instructions appear. Fund the quoted deposit from your source wallet, including any required memo; a compatible connected EVM wallet may offer a send action. External funding is an onchain transfer, not a gasless EIP-712 batch order, and source gas or network fees can apply. Keep the transfer record until delivery or refund is confirmed; the app provides status tracking and transaction-reference recovery. Zcash requires a mainnet transparent **t1/t3 address**; shielded and unified addresses are not supported. Hyperliquid means **Hypercore spot balances**, not Hyperliquid's EVM network. These flows require the NEAR provider to be enabled. Pausing new swaps retains saved-transfer recovery in the standalone app. External-source selection is not currently exposed by the embedded widget, Hooks view, SDK order builder or hosted MCP tools. ## Arc and Circle bridges Arc uses **USDC for gas**. Its native balance uses 18 decimals, while the ERC-20 USDC interface used for token amounts and bridging uses 6. Both interfaces access the same underlying balance; do not add them together. The swap form includes Circle CCTP routes across the configured network set: Arc, Ethereum, Base, Arbitrum, Optimism and Unichain. USDC uses CCTP; expanded asset routes use Circle's token service and the configured per-chain registry. Assets include EURC, cirBTC and WETH where both sides have a supported mapping. This does not make every token in the picker bridgeable. - The direct Circle flow requires an exact-input swap and a personal EOA wallet without smart-account code. It delivers to the connected wallet, not a custom recipient. - **Ethereum WBTC → Arc cirBTC** is a separate two-stage flow: convert WBTC on Ethereum, then bridge cirBTC. It is not a native Bitcoin deposit or an atomic cross-chain swap. - Approvals, conversion placement and bridge transactions can require gas. Review the quoted fees, minimum receive amount and destination gas needs. - Source settlement does not prove destination delivery. Keep the pending transfer record and use the app's recovery flow if an attestation or mint is delayed. Arc's configured Ophis routing lanes include **Uniswap v3, KyberSwap, Aero, Archery, Uniswap v4 and SushiSwap v3**. A lane identifies a liquidity route, not an independent solver operator: Ophis-operated winners are labeled **Ophis**. Available pools, pairs and lanes may change; no venue is promised on every chain or every trade. Across and NEAR Intents remain separate providers for their supported routes. An EVM network in the same-chain selector is not automatically an enabled bridge source. Optimism and Polygon Across source routing are enabled after separate live settlement and delivery tests: Optimism to Base, and Polygon USDC to Robinhood Chain USDG. Live assets and quotes still gate each route. See the [deployment record](https://github.com/ophis-fi/ophis/blob/main/infra/optimism-mainnet/README.md#across-source-dependencies-october-1-2026) for the mainnet dependencies, live test receipts and separate fork-execution proof. ## Tokenized-asset labels The picker carries provider labels for **Ondo, xStocks, Coinbase, bStocks and Reality** when configured-list metadata attributes that chain/address to a provider. The latest additions are **bStocks on BNB** and **Reality/rStock on Arbitrum**. Their attribution comes from the configured CoinGecko lists and naming conventions, not arbitrary wallet names, symbols or user-added list tags. A badge is **display attribution**, not issuer authentication, a legal-rights assessment, an AML decision, trading eligibility or a settlement guarantee. Robinhood canonical-registry and multiplier checks are a separate feature; they must not be inferred from a bStocks or Reality badge. Check the contract, issuer disclosures, jurisdiction restrictions and executable liquidity independently. ## OTC is a separate Ethereum flow The optional OTC desk uses external immutable escrow on **Ethereum**, not the batch-auction orderbook. Public-mode support is restricted to reviewed WETH, USDC and DAI and eligible standard EOAs; contract/delegated wallets and native ETH are not supported. Create, fill, cancel and approval transactions cost gas. Orders are public and all-or-nothing, with **no automatic onchain order expiry**. A pending fill can race cancellation. Writes require both a compatible frontend build and a fresh, matching runtime authorization. A disabled switch prevents new prompts, not already-signed transactions or existing escrow orders. Source support is not proof that live writes are enabled. See the [OTC operating guide](https://github.com/ophis-fi/ophis/blob/main/docs/development/otc-public-trading.md) for admission, shutdown and uncertain-transaction recovery. ## Confidential routing and institutional features With a configured partner key, Ophis's NEAR provider requests `confidentiality: 'basic'` and rejects a quote that does not echo the requested mode. Keyless builds use public NEAR routes. This is provider-specific behavior, **not a user-selectable all-route privacy guarantee**: Across, Circle and same-chain orders do not inherit it, and external-chain transfers remain public. Ophis has no public route-wide confidentiality selector or released AML/SHIELD screening service. Neither a provider response nor a badge proves compliance or anonymity. See NEAR's [confidential-swap integration](https://docs.near-intents.org/integration/distribution-channels/1click-api/quickstart/confidential-swaps) and [SHIELD incident API](https://docs.near-intents.org/security-compliance/shield-incident-api) for the upstream capabilities, not a claim that all of them are integrated into Ophis. --- # Trade rewards URL: https://docs.ophis.fi/trade-rewards # Ophis trade rewards The Ophis trade-reward campaign is active. Eligible wallets can receive one winning ticket while inventory remains. Check the [Rewards page](https://swap.ophis.fi/#/rewards) for current availability and claims. 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 limits that authority: the curator key can trigger swaps, but an onchain policy module checks every order against a fixed rulebook before anything is signed. A compromised curator key cannot redirect proceeds or trade unlisted tokens through this module. Price and turnover protections have the limits described in [Security model](#security-model). The flow settles through CoW Protocol as one atomic, MEV-protected order. The vault Safe is both `order.from` and `order.receiver`: sold tokens leave at settlement and bought tokens return to the Safe. Each order carries the Ophis partner fee. For rebate accounting, call `enrollOphisTrader(vaultSafe, { blocking: true })` immediately before each order submission. Enroll the Safe, not the curator or one of its owners. Registering once during setup is insufficient for a vault that starts trading later. See [Partner enrollment](./partners.md) for retention and recovery. ## 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, minBuyOverride)` call the module re-checks the full order onchain 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 | At presign time, the minimum out must satisfy the configured oracle band (default 50 bps). Stale or invalid rounds fail closed; the oracle is not checked again at fill time | | 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) | | Turnover capacity | One USD leaky bucket limits bursts and refills over 24 hours; it can admit approximately twice its configured capacity in a rolling 24-hour window | | 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 USD turnover bucket.** The turnover capacity is global across every token, not per token, so a compromised curator cannot multiply its reach by rotating through many assets. The bucket refills over 24 hours; its rolling-day allowance can reach approximately twice the configured capacity. - **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` v0.1.7 vault-order builder uses SDK v0.4.4 and supports these 14 chains. Use ERC-20 token addresses on Arc; native-token sentinels are rejected. Arc charges the 1 bp base plus capped backend improvement capture on in-market orders; eligible settled trades count toward volume-tier and referral rebates: - **Ophis self-hosted:** Optimism, Unichain, Robinhood Chain (4663), and Arc (5042). - **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. ## Deployment addresses The repository records the module factories below. Each chain has its own settlement and Chainlink feed configuration; the operator runbook documents the deployment and preflight checks. | 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) to allow for block-timestamp lag while keeping the fill window tight. - The oracle floor band (50 bps) must cover the order's slippage plus the quote fee. Fees, slippage or price movement can put an order outside the band. Check each quote against the configured floor; a larger order is not guaranteed to pass. - 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 restricts a curator to allowlisted tokens, Safe-bound proceeds and policy-valid orders. It is **not a guarantee against loss**. The oracle floor is checked when an order is presigned, not when it fills; price movement during its TTL, fees and repeated policy-valid trades can erode vault value. The turnover limiter is a leaky bucket, not a strict daily spending cap. An initial full bucket plus its refill can admit approximately **2× the configured capacity over rolling 24 hours**. Set capacity no higher than half the intended rolling-day turnover tolerance and size TTL, slippage and asset risk accordingly. Repository reports record a 12-agent adversarial review, 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 the documented deployments. Using these tools is not an audit or endorsement by their authors. During the live rollout the oracle floor rejected mispriced orders in production exactly as designed. `disableModule` blocks new module calls; it **does not revoke existing presignatures or allowances**. The module's `cancel` is curator-only and accepts only orders it recorded. Follow the runbook to cancel or expire outstanding orders and clear allowances before migration. Do not share its token allowances with another enabled module; disabling first also prevents curator cancellation through that disabled module. ## 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-checked at presign, and leaky-bucket-limited turnover. A leaked curator cannot redirect proceeds through this module, but repeated trades and price drift can still cause losses. See the security limits above. **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, minBuyOverride)`. Proceeds return to the Safe. Owners can disable new calls, but must separately resolve existing orders and allowances. 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.