Method
How does Sato Swap decide, and what does it hand back?
Sato Swap checks four targets, applies the operator's policy, ranks venues inside one lane and returns an unsigned object — never a key, never custody, and never a route on a reading it could not make, as of 2026-09-15.
This page is the definition. Sato Swap is the surface, and POST /api/swap/quote plus the MCP tool onchain_agent_swap are the same code answering over two transports. Nothing below is a security review, and no venue named here is endorsed by being named.
How an agent gets a swap it is allowed to make
One request, seven steps, in the order the code runs them. Step six is the one that defines the product: what comes back is an unsigned object. The wallet that owns the funds signs it, or throws it away.
Step 1
Post the intent
Send the trade you want as JSON — chain in, token in, token out, amount in base units, the taker address, slippage, an optional deadline and a mode — to POST /api/swap/quote, or to the MCP tool onchain_agent_swap. The same module answers both, so the two transports cannot disagree.
Step 2
Read the four verdicts
Both tokens, the venue endpoint and the recipient are checked separately, each returning go, caution, no or unknown with the rule that produced it and the date it was read. Unknown means no record was held or a lane could not be read — never zero, and never a finding against the target.
Step 3
Let the policy decide
The operator's policy is evaluated server-side, after the venue is chosen and before anything is built: caps per trade and per period, allowed chains, tokens, venues, recipients and modes, slippage and deadline ceilings, and what a caution or an unknown does. Unknown refuses by default. Where no stored policy applies, the stated default applies and the response says so.
Step 4
Simulate the route
The built transaction is run against a node before it is handed back. A route that reverts is withheld under rule E4 with the decoded revert reason; where no lane could run at all, the reason is stated rather than the absence being passed off as a clean result.
Step 5
Act on any precondition
A revert caused by the requesting wallet's own missing allowance or balance is not treated as a fault in the route. It is re-run against a state where the approval exists; where that is impossible, the transaction is returned with the precondition named — which approval, to which spender, for how much — and with the route reported as not simulated.
Step 6
Sign the unsigned transaction, or do not
In build-tx mode the response carries EVM calldata, or the venue's unsigned base64 transaction on Solana, together with that venue's own fee sentence quoted verbatim. Sato Hub holds no key, signs nothing and broadcasts nothing: the wallet that owns the funds signs it, or discards it. In recommend mode no transaction is returned at all, whatever the gate said.
Step 7
Keep the receipt
The decision is recorded as an Ed25519-signed receipt over the quote, the mode, the policy version and digests of what was checked, verifiable against the published key at /.well-known/jwks.json. It records what was returned and checked — it is not proof that a trade settled or that a fee was received.
1. What is checked
Four targets, each with its own verdict — go, caution, no or unknown. They are reported separately and never blended into a single score, because the thing you need to know is which one did not clear.
| Target | Lane | What it reads |
|---|---|---|
| token_in | Token table (TOK0), then the Preflight token lane (T1–T6) | TOK0 first: a chain's wrapped native asset, a recognised stablecoin or a native placeholder is matched against Sato Hub's own token table and answers go, naming the date that entry was last reconciled. TOK0 records which address carries which symbol on which chain — it is not an audit, an endorsement or a statement about the token. Everything else goes to the keyless chain reads, where a non-EVM chain or an unresolvable symbol answers unknown with the reason attached — never a guess. |
| token_out | Preflight token lane (T1–T6) | The same two steps, run separately on the output token. The two verdicts are reported separately and are never merged into one. |
| venue | Preflight endpoint lane (R1–R6) | The directory record where Sato Hub lists the venue; a live handshake against the venue's https endpoint where it does not. |
| recipient | The gate's own rules (RCPT1–RCPT4) | RCPT1 the recipient is the taker. RCPT2 the recipient is named in the policy. RCPT3 a listed Passport holds this wallet — a record of who registered, which raises to caution and never to go. RCPT4 no record. |
Unknown is never zero. It means we hold no record, or a lane could not be read. It is not a finding against the target — and under the default policy it refuses, which is the product: an agent should not trade on a reading nobody made.
2. The policy model
A policy is an operator’s standing answer to what a given caller may swap, how much, and what happens when we hold no record. It is pure — no clock but the one passed in, no network — so the HTTP lane and the MCP lane cannot drift apart. Policies are managed at /api/swap/policies.
- Strict on unknown, permissive on empty. An empty allowlist means “any, subject to the verdict” — an operator who has not listed tokens has not thereby refused every token. But unknown_verdict defaults to refuse. That asymmetry is deliberate.
- Caution allows by default. A caution is the normal state of most of the long tail; refusing it out of the box would refuse nearly every real trade while telling the operator nothing new. Set caution_verdict to refuse for the stricter posture, and the refusal then says it was your rule.
- It never prices a token. Caps are in base units of a named token. A USD cap bites only when the caller hands us a price it already holds; with no price the notional is unknown and is resolved by unknown_verdict, never silently as zero.
- It never refuses on a missing Passport alone. require_passport defaults to false. No record is no record.
- Every refusal names its rule. The rule id, the limit and the observed value travel with it, so a refusal can be argued with rather than only obeyed.
3. Execution modes
| Mode | Returns | Withheld |
|---|---|---|
| recommend | A chosen venue, the ladder it was chosen on, the fee sentence verbatim from that venue's adapter, the four verdicts and the policy in force. | Calldata, always — whatever the gate said. This is the mode that sells the refusal: you can ask what to do without being handed something signable by accident. |
| build-tx | Everything above, plus an UNSIGNED transaction: EVM calldata, or the venue's unsigned base64 transaction on Solana. | The transaction, whenever the gate did not allow it or a simulation reverted. The reason is always returned with the withholding, under its rule id. |
| intent-post | Does not exist. | Deliberately not built. It is the one mode that would move Sato Hub from handing back an unsigned object to relaying a signed one, and that is a different product with a different set of obligations. |
4. Simulation, and what “not simulated” means
Before build-tx hands back a transaction, a node is asked what would happen if it were sent — without sending it. Which lane ran is always reported.
| Lane | What it does | Expected output |
|---|---|---|
| evm_rpc | Keyless: eth_call at latest state from the taker, then eth_estimateGas, each inside a 6s deadline. A revert is decoded where the venue returned a standard Error(string) or Panic(uint256) payload. | Not decoded. A router's return data is usually not a plain output amount, and we would rather print “output not simulated” than a number decoded by guessing at an ABI. |
| alchemy | alchemy_simulateExecution, only when ALCHEMY_API_KEY is set and the chain has a published Alchemy host. Inert and unmentioned without the key. | Asset changes give a real expected output. |
| solana_rpc | simulateTransaction over a public RPC, for the unsigned base64 transaction the venue returned. | Not available. Solana simulation returns logs and units consumed, not an output amount, and the result says so. |
| none | No simulation could be run. Always carries the reason it could not. | Absent. “We did not check” and “we checked and it did not revert” must never look the same from outside, so an absent simulation is a first-class result rather than silence. |
A simulation that did not revert says one thing only: at that block, from that address, this call did not revert. It is not a prediction of the fill, the price or the outcome.
5. The fee, per venue
3 bps when both legs are stablecoins Sato Hub recognises, 15 bps when either leg is volatile. The fee is a parameter on the venue’s own quote — taken inside the swap transaction by that venue’s router, to the Sato fee address 0xcEE53Eb001d4d1743EF9df333Dcf45bC38622bE9. It is stated on every quote before anything is signed. A failed, reverted or unsigned trade pays nothing.
On several venues the flat sentence is not the whole truth: the venue keeps a share, charges a different leg, or pays later and only conditionally. Each qualifier below is quoted from that venue’s own documentation, and is sent with the quote rather than kept here. None of these numbers is observed by us — what actually lands is a separate on-chain reading, and a receivable is never recorded as received.
| Venue | Qualifier |
|---|---|
| 0x-protocol | No qualifier — the flat sentence is true here: 3 bps stable-to-stable, 15 bps otherwise, taken on the input leg by the venue’s own router. |
| 1inch | No qualifier — the flat sentence is true here: 3 bps stable-to-stable, 15 bps otherwise, taken on the input leg by the venue’s own router. |
| jupiter-aggregator | No qualifier — the flat sentence is true here: 3 bps stable-to-stable, 15 bps otherwise, taken on the input leg by the venue’s own router. |
| odos | On Odos the fee is deducted from the OUTPUT tokens of the swap, not the input leg, and Odos forwards 80% of it to the recipient and retains 20% (docs.odos.xyz/home/api-monetization). The rate above is the rate sent, not the rate received. |
| kyberswap | No qualifier — the flat sentence is true here: 3 bps stable-to-stable, 15 bps otherwise, taken on the input leg by the venue’s own router. |
| relay | On Relay an app fee does not move on-chain to the recipient: it accrues as an off-chain USDC balance that must be claimed (docs.relay.link/features/app-fees). Until it is claimed it is receivable, not received, and an empty paidAppFees on the executed trade means it was not collected at all. |
| debridge | On deBridge DLN the affiliate fee is paid only when the order is unlocked on the source chain, after a fill and only if the order is fulfilled (docs.debridge.finance). Until then it is receivable, not received. |
| lifi | On LI.FI the fee is collected at execution and forwarded to the fee recipient minus LI.FI's own 0.25% and a further volume-dependent share LI.FI does not publish (docs.li.fi). The net share reaching the recipient is unknown, not the rate above. |
| cow | On CoW the partner fee is taken in the surplus token and capped at 100 bps; CoW DAO retains 25% of it and pays the rest weekly in WETH, with amounts under 0.001 WETH forfeited (docs.cow.fi/governance/fees/partner-fee). The rate above is the rate sent. |
6. Venues and lanes
Derived from the adapter set when this page was rendered — each adapter’s declared lanes and its own supports() answer. A same-chain quote and a cross-chain quote are different products and are never ranked against each other: different settlement, different finality, different failure mode. The other lane comes back as a separate offer.
| Venue | Lanes | Chains it will attempt |
|---|---|---|
| 0x-protocol | same-chain | Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain, Avalanche, Gnosis, Sei, Berachain, Hyperliquid, zkSync, Robinhood Chain |
| 1inch | same-chain | Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain, Avalanche, Gnosis, Sei, Berachain, Hyperliquid, zkSync, Robinhood Chain |
| jupiter-aggregator | same-chain | Solana |
| odos | same-chain | Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain, Avalanche, Gnosis, Sei, Berachain, Hyperliquid, zkSync, Robinhood Chain |
| kyberswap | same-chain | Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain, Avalanche, Berachain, zkSync |
| relay | same-chain, cross-chain | Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain, Avalanche, Gnosis, Sei, Berachain, Hyperliquid, zkSync, Robinhood Chain |
| debridge | cross-chain | none declared |
| lifi | same-chain, cross-chain | Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain, Avalanche, Gnosis, Sei, Berachain, Hyperliquid, zkSync, Robinhood Chain |
| cow | same-chain | Ethereum, Base, Arbitrum, Polygon, Avalanche, Gnosis |
A chain listed here is one the adapter will attempt. The venue’s own error is better evidence of its coverage than our guess, so an unconfirmed chain is asked rather than pre-refused — and whatever the API says is reported as the reading it is.
7. The ladder
Inside a lane, venues are ranked by: retired and dormant excluded outright; then whether the venue answered with a quote at all; then liveness; then the observed success rate of Sato Hub’s own daily checks over at least fourteen days; then verification state; then quoted output; then name, so the same data always yields the same route. Price ranks below liveness on purpose — a venue that quotes a better number and then does not answer is worth less than one that answers.
The observed success rate is the share of our checks that succeeded. It is never called uptime: a failure can be on our side. The whole ladder travels back in chosen_by, each signal with the field it was read from, so a route can be re-derived instead of trusted.
8. What a receipt binds
Version 2 of the quote binding (Sato Swap, 2026-09-15) adds what was checked to what was returned. The version travels in the row and inside the signed message, so a holder rebuilds exactly one construction and checks it against /.well-known/jwks.json. Rows minted before the change keep verifying under version 1 forever.
| Signed field | What it records |
|---|---|
| route_id · kind · venue · chain | Which quote this is, on which rail, and where it was to be executed. |
| token_in · token_out · amount_in · amount_out | The pair and the amounts, base units as strings. Never a USD figure we did not observe. |
| fee_bps · fee_recipient | The fee parameters that were SENT. Not a statement that a fee was received. |
| source | Which lane asked — the HTTP API or MCP. |
| binding_version · mode | Which construction was signed, and whether a recommendation or an unsigned transaction was asked for. |
| policy_id · policy_version | The policy that decided. A receipt that says “decided under policy vN” is worth nothing if that sentence sits outside the signature. |
| verdicts_digest · simulation_digest · simulation_lane | Digests of what was checked, not the objects: a digest is stable and cannot be quietly widened later. |
| lane · counterparty · sato_agent_id | Same-chain or cross-chain, what the counterparty lookup found, and a proven Passport id where there was one. |
Everything that can change after the quote — status, tx hash, props — is deliberately outside the signature. A signature over a mutable row would fail on the first honest update and teach people to ignore it. A receipt is evidence of what we said and what we checked; it is not evidence that a fee landed, a trade settled, or that anyone authorised a transfer.
9. Non-custodial, by construction
Every response is an unsigned object. No module in the path holds a key, signs a transaction or broadcasts one. The Ed25519 signature on a response proves who said it — never that anyone authorised a transfer.
10. What Sato Swap does not do
No intent posting
Nothing signed is relayed, posted to a solver network or submitted on anyone's behalf. Every response is an unsigned object.
No custody
No key is generated, requested or stored. Funds are never held, netted or forwarded. The fee is a parameter on the venue's own quote, taken by the venue's router inside the transaction you sign.
No trusted list
There is no allowlist of venues Sato Hub vouches for. A venue is ranked on dated readings anyone can re-derive, and a listing is a record, not an endorsement.
No counterparty ranking
A counterparty with a Passport is a record of who registered, not a score of how they behave. A counterparty with no Passport is no record — never a refusal on that ground alone.
No security review
A verdict describes readings on a date. It is not an audit, a price opinion, or a statement about returns, and nothing in the path may be described as one.
No price claim
Quoted output is the sixth signal in the ladder, not the first, and a quote is a quote rather than a fill. No “cheapest” or “optimal” claim is made anywhere.
11. Licence notes — venues not offered
A venue is absent for a reason, and the reason is written down. In every case below the blocker is permission or attribution, not an opinion about the venue.
| Venue | Why |
|---|---|
| Across | Not adapted. Its swap API requires a bearer key and a registered two-byte integratorId, and the app fee is paid on the DESTINATION chain — so the fee address would have to exist there too. No adapter is better than an adapter whose disclosure we could not make true. |
| AgentSwap | Not adapted. It publishes no terms of service and documents no integrator share, so there is no fee we could disclose and no published permission to integrate against. Recorded as a venue, not routed to. |
| 0x · 1inch | Adapters exist and are unchanged. Both need an API key; without one they answer “unavailable” with the reason, which is a reading rather than a failure hidden. |
| LI.FI · CoW | Env-gated on an integrator string (LIFI_INTEGRATOR / COW_APP_CODE). A fee sent without one is attributed to nobody, so without the string these adapters do not quote. |
| Uniswap v4 | Not quoted directly. The singleton PoolManager publishes no per-pair pool id, so a v4 reading would be null with the reason rather than a number. |
Reading it from an agent
- POST /api/swap/quote — mode recommend or build-tx, signed response, shape published at /schemas.
- onchain_agent_swap over the MCP server — the same code, write-labelled because build-tx produces something signable.
- GET /api/swap/receipts/<id> — the public receipt, and /swap renders it.
- /api/openapi.json — the machine contract for the public operations.
Questions about Sato Swap
- How does Sato Swap choose a venue?
- Sato Swap ranks venues inside one lane on liveness, the observed record of Sato Hub's own daily checks, verification state and only then quoted output; the whole ladder travels back in chosen_by, as of 2026-09-15.
- What does a Sato Swap verdict mean?
- A Sato Swap verdict is go, caution, no or unknown, returned separately for each of four targets — token in, token out, venue and recipient — each naming the rule that fired and when it was read, as of 2026-09-15.
- What fee does Sato Swap charge, and who receives it?
- Sato Swap takes 3 bps when both legs are stablecoins it recognises and 15 bps otherwise, to the Sato fee address, inside the venue's own router. Several venues keep a share; each says so, as of 2026-09-15.
- What does a Sato Swap receipt prove?
- A Sato Swap receipt is an Ed25519-signed record of what we returned and what was checked — quote, mode, policy version, gate digest, simulation digest. It is not proof a fee landed or a trade settled, as of 2026-09-15.
- Does Sato Swap hold my funds or keys?
- No. Sato Swap returns unsigned objects — a recommendation, or an unsigned transaction you sign with your own wallet. It holds no key, signs nothing, broadcasts nothing and never takes custody, as of 2026-09-15.
- Why did Sato Swap refuse my trade?
- Unknown refuses by default, and every refusal names its rule and the two values it compared — E1 gate refused, E3 unknown verdict, E4 reverted in simulation, E7 no simulation, plus the policy rules, as of 2026-09-15.
- Which chains and venues does Sato Swap route?
- Sato Swap asks 9 venue adapters across 14 chains (13 EVM plus Solana): 8 quote same-chain and 3 quote cross-chain. The two lanes are never ranked against each other, as of 2026-09-15.
- Is there an MCP tool for swaps?
- Yes — onchain_agent_swap over POST /api/mcp, answered by the same code as POST /api/swap/quote so the two lanes cannot disagree. It is write-labelled and returns unsigned objects only, as of 2026-09-15.
- How can an AI agent swap tokens?
- By asking for an unsigned transaction rather than delegating a key: the agent posts an intent, four targets are read, the owner's policy decides, the route is simulated, and the agent signs, as of 2026-09-15.
- What stops an agent from making a swap it should not?
- A policy the owner writes, enforced server-side after routing and before anything is built: caps, allowlists, slippage and deadline ceilings, and what a non-go verdict does, as of 2026-09-15.
- How does Sato Swap handle slippage and deadlines?
- It is passed to the venue's own router as that venue's parameter, defaulted by the request schema, and a policy may cap it — over the cap the request is refused with both numbers, as of 2026-09-15.
- Can an agent swap across chains with Sato Swap?
- Yes — a chain_out different from chain_in is the cross-chain lane, quoted by 3 of the 9 adapters. The two lanes are ranked separately and a bridge quote is never compared against a same-chain one, as of 2026-09-15.
- How do I call Sato Swap from code?
- POST /api/swap/quote with a JSON intent, or call the MCP tool onchain_agent_swap — the same module answers both. No key is needed, and a refusal arrives as a 200 carrying its reason, as of 2026-09-15.
- What happens when Sato Swap simulates a transaction?
- It runs the transaction against a node first: a route that reverts is withheld with the decoded reason, while a revert on your own missing approval is reported as a precondition instead, as of 2026-09-15.
- What does Sato Swap not do?
- It never holds keys, signs, broadcasts, prices a token, models depth or reviews a venue. A verdict is a dated reading and a receipt records a decision, not a settlement, as of 2026-09-15.
- How is Sato Swap different from routing directly through an aggregator?
- It does not replace an aggregator: it asks several and adds four dated verdicts, a server-side policy, a simulation and a signed record. The cost is our fee and one more hop, as of 2026-09-15.
- What does Sato Swap cost?
- The API needs no key and no account. The trade carries 3 bps stable-to-stable and 15 bps otherwise on the venue's own quote, plus that venue's fee and gas, as of 2026-09-15.
- Who can set a Sato Swap policy?
- The operator of the key, agent or wallet it binds to — exactly one of the three, managed at /api/swap/policies and versioned into every receipt. The default refuses on unknown, as of 2026-09-15.
Sources — the venue documentation every fee sentence is read from
- Jupiter — adding fees to a swap ↗
- Odos — API monetization ↗
- LI.FI — quote API reference ↗
- Relay — app fees ↗
- deBridge DLN — affiliate fees ↗
- CoW Protocol — partner fee ↗
- 0x — swap quote API ↗
- 1inch — classic swap API ↗
- Across — swap API (not adapted; see the licence notes) ↗
- Model Context Protocol — specification ↗
- RFC 8032 — Ed25519 signatures ↗
Corrections land on the transparency report. Every answer above is also published one question per page under /answers.