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