AI agent integration
Ophis is designed to be agent-friendly. The Intent API 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 before giving an agent signing authority.
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 on the Ophis blog.
MCP server (recommended)
The fastest way to give an MCP-capable agent full Ophis access is the hosted Model Context Protocol server:
https://mcp.ophis.fi/mcp
It speaks streamable-HTTP MCP and exposes fourteen tools:
The current server release is v0.1.1. Its package metadata, runtime handshake, discovery response, and official registry manifest are checked as a single versioned unit in CI.
| Tool | What it does |
|---|---|
parse_intent | Parse a natural-language request into a structured intent. |
resolve_token | Resolve a token symbol to its canonical address from the trusted Ophis/CoW token list; fails closed (anti-spoof). Call this before quoting or building so you never trade against a spoofed address. |
get_quote | Fetch an executable quote for a parsed intent. |
build_order | Build a bounded, ready-to-sign order (receiver unconditionally pinned to the owner). |
submit_order | Submit a signed order to the correct per-chain orderbook. |
validate_order | Offline preflight for an order you built outside build_order: catches the silent-failure modes (wrong appCode, wrong orderbook host, wrong EIP-712 domain, appData-hash mismatch, unpinned receiver) before you sign. |
lookup_tier | Look up a wallet's 30-day volume tier / rebate status. |
get_integrator_earnings | Look up what an integrator's own-fee / referral routing earned, by appCode: routed volume, the Ophis base fee, your stacked fee, and rebate paid-to-date with payout tx links. |
list_chains | Resolve supported chains and their settlement / orderbook hosts. |
get_balances | Read a wallet's native and ERC-20 balances on one chain via a public RPC. |
get_portfolio | Read a wallet's token balances across multiple chains. |
get_gas | Fetch the current gas price for a chain. |
get_token_chart | Fetch a token's OHLCV price chart. |
expected_surplus | Estimate how much better an Ophis sell-quote beats the open market (beatBps). |
Point any MCP client (Claude, Cursor, or a custom agent) at that URL. Ophis
never holds keys: build_order returns a bounded order the agent signs
locally; the signature is the trust boundary (see the warning below). A bare
request without an Accept: text/event-stream header returns HTTP 406; that is
the transport negotiating, not an outage.
Connect your MCP client
The server is public and keyless, so there is nothing to sign up for. Copy the block for your client.
Claude Code (one command):
claude mcp add --transport http ophis https://mcp.ophis.fi/mcp
Claude Desktop (claude_desktop_config.json, via the mcp-remote bridge):
{
"mcpServers": {
"ophis": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.ophis.fi/mcp"]
}
}
}
Cursor (~/.cursor/mcp.json, or the project .cursor/mcp.json):
{
"mcpServers": {
"ophis": {
"url": "https://mcp.ophis.fi/mcp"
}
}
}
VS Code (.vscode/mcp.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 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 directly, as shown next.
The integration flow
- Parse.
POSTthe user's request (or your agent-generated trade idea) tohttps://ophis.fi/api/intent. - Read. Receive a
ParsedIntentwith normalizedsellToken,buyToken,amount, andchainentities. - Build a deep link. Map the chain slug to its chain ID and
construct
https://swap.ophis.fi/#/<chainId>/swap/<sellToken>/<buyToken>. - Hand off. Open the link for the user to review and sign. Ophis never auto-signs, every order requires explicit wallet approval.
Ophis intentionally does not implement x402 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)
curl -sS https://ophis.fi/api/intent \
-H 'content-type: application/json' \
-d '{"text":"swap 100 USDC for ETH on Base"}'
{
"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
import requests
INTENT_API = "https://ophis.fi/api/intent"
SWAP_APP = "https://swap.ophis.fi"
# The 13 EVM chains the Intent API can return, mapped to their chain IDs.
# Keep in sync with the API's supported-network list; build_deeplink()
# raises on any future slug not listed here rather than misrouting it.
CHAIN_SLUG_TO_ID = {
"ethereum": 1,
"optimism": 10,
"bnb": 56,
"gnosis": 100,
"polygon": 137,
"base": 8453,
"ink": 57073,
"linea": 59144,
"arbitrum": 42161,
"avalanche": 43114,
"plasma": 9745,
"unichain": 130,
"robinhood": 4663,
}
def parse_intent(text: str) -> dict:
"""Call the Ophis Intent API and return the ParsedIntent payload."""
resp = requests.post(INTENT_API, json={"text": text}, timeout=10)
resp.raise_for_status()
body = resp.json()
if not body["ok"]:
raise RuntimeError(f'{body["error"]["code"]}: {body["error"]["message"]}')
return body["data"]
def build_deeplink(parsed: dict) -> str:
"""Turn a ParsedIntent into a swap deep link for the user to sign."""
by_type = {e["type"]: e["value"] for e in parsed["entities"]}
sell = by_type.get("sellToken", "_")
buy = by_type.get("buyToken", "_")
chain_slug = by_type.get("chain")
if chain_slug is None:
chain_id = 1 # no chain in the request -> default to Ethereum
elif chain_slug in CHAIN_SLUG_TO_ID:
chain_id = CHAIN_SLUG_TO_ID[chain_slug]
else:
# The parser may return a chain this map doesn't cover yet. Fail
# loud instead of silently routing the user to the wrong chain.
raise ValueError(f"unmapped chain slug {chain_slug!r}; update CHAIN_SLUG_TO_ID")
# The user sets/confirms the amount and signs in the app.
return f"{SWAP_APP}/#/{chain_id}/swap/{sell}/{buy}"
intent = parse_intent("swap 100 USDC for ETH on Base")
print(build_deeplink(intent)) # https://swap.ophis.fi/#/8453/swap/USDC/ETH
LangChain tool
Wrap the API as a LangChain tool your agent can call when a user wants to trade:
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:
{
"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 POSTing { "text": <text> } to
https://ophis.fi/api/intent (see the Python helper above), then surface
the resulting deep link to the user.
Drop-in framework adapters
Everything above keeps a human in the signing loop. If instead you are building an agent that executes swaps itself and you are on a common framework, you do not have to hand-roll the order flow in the next section. Four published npm packages wrap quote, EIP-712 sign, relayer approval, and submit into one call, and each stamps your referral code into every order when one is supplied, so the rebate accrues:
| Package | Version | For | Registers |
|---|---|---|---|
@ophis/agentkit-ophis | v0.3.2 | Coinbase AgentKit | an OphisActionProvider_swap action |
@ophis/plugin-goat | v0.3.2 | GOAT SDK | an ophis_swap tool |
@ophis/plugin-elizaos | v0.3.2 | elizaOS | a swap action |
@ophis/agent-swap | v0.3.2 | any custom EOA framework | the executeOphisSwap() core |
The v0.3.2 adapter family is built and published against @ophis/sdk v0.4.2,
so its fee policy, chain list, orderbook hosts, settlement contracts, and vault
relayers match the current SDK.
Coinbase AgentKit, in one line:
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:
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):
import { ophisPlugin } from '@ophis/plugin-elizaos';
export const character = {
name: 'Trader',
plugins: [ophisPlugin], // registers a natural-language `swap` action
};
The AgentKit and GOAT tools take sellToken, buyToken, sellAmount (whole
units, e.g. "1.5"), and an optional slippageBps (default 50 = 0.5%); the
elizaOS action reads the tokens and amount from the user's message and uses the
default 0.5% slippage. Each quotes against the Ophis orderbook, signs the order
EIP-712 with the agent's own wallet, approves the CoW vault relayer once, submits,
and returns the order UID plus an explorer URL. ERC-20 to ERC-20 only (native-ETH
sells need CoW eth-flow, a separate path, so wrap to WETH first). The agent's
wallet is the order owner and receiver, so funds only ever move through the
audited CoW settlement contract, back to the same wallet.
The 1 bp base fee applies to every supported pair. Every drop-in adapter (AgentKit, GOAT, elizaOS) and the platform integrations below detect stable pairs from a verified stablecoin list so the reduced price-improvement policy is selected automatically: 50% capped at 20 bps for stable pairs, versus 80% capped at 99 bps for volatile pairs.
The referralCode is optional: omit it and swaps still work and settle, you just
forgo the rebate. Mint one below, then ship, no redeploy of the swap path needed
to start earning.
More platform integrations
Beyond the npm packages above, Ophis maintains swap integrations for more agent
platforms, each built on the same audited Ophis order flow (the TypeScript ones
reuse @ophis/agent-swap; the Python ones mirror the same order construction and
fund-safety guards):
elizaOS (published, in the
table above), plus HeyAnon, Swarms, the MetaMask Agent Wallet, and
Bankr. Their source lives under
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 - Umbrella skill:
https://ophis.fi/.well-known/agent-skills/ophis/SKILL.md - Sub-skills:
ophis-quote,ophis-swap,ophis-order-status,ophis-cancel,ophis-surplus-report.
The umbrella's frontmatter carries a machine-readable policy block: the
pinned per-chain settlement and vault-relayer contracts (the only allowed
approve spenders), the EIP-712 signing domains, the orderbook hosts, and
slippage latches. Policy-enforcing runtimes can apply it mechanically; CI in
the Ophis repo pins the block against the deployed addresses so the published
skills cannot drift. The skills cover all three Ophis-operated chains
(Optimism, Unichain, and Robinhood Chain); for other chains use the MCP server
above, which resolves per-chain contracts via list_chains.
The same canonical family is published as
@ophis/agent-skills
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 on its volume, currently 8 to 12 percent, paid on-chain. The code rides in the order's appData, so there is nothing for the end user to sign or opt into.
- Open the Rewards page and connect a wallet.
- Mint a code (about 30 seconds). It is yours permanently.
- Pass it to any adapter as
referralCode, or exportOPHIS_REFERRAL_CODEand the adapters pick it up automatically.
The code is optional: without one your agent still swaps normally, it just earns no rebate. You can ship first and add the code later.
Submitting orders programmatically
The Intent API only normalizes language, it does not place orders. To submit orders programmatically, build and sign a CoW Protocol order 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 above already get all four right, hand-roll
this only if you are on neither. The @ophis/sdk helpers below are also what
those adapters call under the hood.
The helpers below live in @ophis/sdk, published on npm (v0.4.2, public).
Install it with npm install @ophis/sdk, or copy the values from the call-outs
if you prefer to vendor them.
1. Resolve the orderbook host from the chain ID
Optimism, Unichain, and Robinhood Chain break the api.cow.fi/<slug> pattern. Ophis self-hosts
their orderbooks at optimism-mainnet.ophis.fi, unichain-mainnet.ophis.fi,
and robinhood-mainnet.ophis.fi.
Posting one of their orders to api.cow.fi/<slug> (a host that does not serve
Ophis) silently bypasses the Ophis solver and zeroes the partner fee. Resolve
hosts via @ophis/sdk getOphisOrderbookUrl per chain rather than hardcoding.
import { getOphisOrderbookUrl } from '@ophis/sdk';
const orderbookUrl = getOphisOrderbookUrl(10); // -> https://optimism-mainnet.ophis.fi
// Throws on an invalid or unsupported chainId rather than guessing a host.
2. Build the partner-fee appData correctly
The appData base is 1 bp on every supported chain and pair. The keyless MCP
build_order and high-level SDK builders select it automatically. Use the
CIP-75 volume shape, not the
price-improvement shape { priceImprovementBps, maxVolumeBps, recipient }:
the two shapes use different denominators, so slotting a value into the wrong
field is a silent magnitude error. Hash the appData with cow-sdk's deterministic
serializer, never keccak256(JSON.stringify(doc)). JSON key order isn't
stable, so the hash won't match what solvers expect.
For a manual builder, call
ophisVolumeBpsForChainAndPair(chainId, isStablePair). This keeps manual
builders aligned with the canonical policy.
The drop-in adapters above derive stable-pair status from a verified stablecoin
list.
import { MetadataApi, stringifyDeterministic } from '@cowprotocol/cow-sdk';
import { keccak256, toUtf8Bytes } from 'ethers';
import { buildOphisAppDataPartnerFee } from '@ophis/sdk';
// buildOphisAppDataPartnerFee(chainId) REQUIRES a chainId and THROWS on a
// missing/invalid one (a forgotten arg fails loud, not as a silent `undefined`).
// It returns the metadata.partnerFee value on every chain in the SDK's
// OPHIS_FEE_CHAIN_IDS (the Ophis-operated chains plus the CoW-hosted chains the
// fork serves), or `undefined` on any other chain.
//
// On Optimism, Unichain, and Robinhood Chain this returns the required 1 bp
// base. Their backends enforce the same 1 bp anti-bypass floor and separately
// apply capped price-improvement capture.
const partnerFee = buildOphisAppDataPartnerFee(10);
// -> { volumeBps: 1, recipient }
const metadataApi = new MetadataApi();
const doc = await metadataApi.generateAppDataDoc({
appCode: 'ophis',
metadata: {
partnerFee,
hooks: {}, // pin empty, appData hooks are arbitrary on-chain calls
},
});
const fullAppData = await stringifyDeterministic(doc);
const appDataHash = keccak256(toUtf8Bytes(fullAppData)); // bytes32 -> order.appData
3. Sign with the correct EIP-712 domain
CoW orders are signed with EIP-712 typed data (signTypedData), never
signMessage. The verifyingContract is chain-specific, and the Ophis-operated
chains do not use CoW's canonical settlement.
On Optimism, Ophis's GPv2Settlement is 0x310784c7…B859, on Unichain it is
0x108A678716e5E1776036eF044CAB7064226F714E, and on Robinhood Chain it is
0x886d9fd312F442C4E1f3cdeAE7b4AB73493e57cD, not the canonical
0x9008D19f…ab41. cow-sdk defaults to the canonical address, so signing an OP
order with the SDK default yields a domain separator the deployed contract
rejects, every order fails. Build the domain from the chain ID instead.
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.
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:
- 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.
- 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;
receiverpinned to the account;appDatapinned 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; avoidpresign.
- 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.
- Defense in depth: enforce the policy in two places, the EIP-1271 validator/signer and server-side at orderbook ingestion.
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.