Encoding
The first step to executing a trade on-chain is encoding.
Our Rust crate converts your trades into calldata that the Tycho contracts can execute.
See this Quickstart section for an example of how to encode your trade.
Models
These are the models used as input and output of the encoding crate.
The Solution struct defines your order and how it should be filled. This is the input to the encoding module.
sender
Bytes
Address of the sender of the token in
receiver
Bytes
Address that receives the output token. If set to the TychoRouterV3 address, the output is credited to the sender's vault balance instead of being transferred out.
token_in
Bytes
The input token
amount_in
BigUint
Amount of the input token
token_out
Bytes
The output token
expected_amount_out
BigUint
The output amount your simulation quoted. The router receives it as expectedAmountOut. Encoding fails if it is zero
min_amount_out
BigUint
The smallest output you accept. The router receives it as minAmountOut and reverts below it. Compute it off-chain from your slippage tolerance, e.g. expected_amount_out * 0.9975 for 0.25%
swaps
Vec<Swap>
List of swaps to fulfil the solution
user_transfer_type
UserTransferType
How the input token enters the router — see the UserTransferType tab
Output amounts
The router takes two output guardrails, and the solution supplies both:
expected_amount_out()
expectedAmountOut
min_amount_out()
minAmountOut
Both are absolute amounts, so refreshing a quote means updating both — apply your slippage tolerance to the new quote and set min_amount_out to the result. The router caps minAmountOut at expectedAmountOut (see Slippage bounds), so it rejects a floor above the quote.
Specifies how user funds (the input token) enter the router:
TransferFromPermit2
Use Permit2 for token transfer. You must approve the Permit2 contract and sign the permit externally.
TransferFrom (default)
Use standard ERC-20 approve + transferFrom. You must approve the TychoRouterV3 to spend your tokens.
UseVaultsFunds
No transfer is performed. Uses tokens already deposited in the TychoRouterV3 vault.
A solution consists of one or more swaps. Each swap represents an operation on a single pool.
The Swap struct has the following attributes:
component
ProtocolComponent
Protocol component from tycho-common
token_in
Token
The token you provide to the pool
token_out
Token
The token you expect from the pool
split
f64
Fraction of the input amount to route through this swap, as a decimal between 0 and 1 (e.g. 0.5 = 50%)
user_data
Option<Bytes>
Optional user data to be passed to encoding
protocol_state
Option<Arc<dyn ProtocolSim>>
Optional protocol state used to perform the swap
estimated_amount_in
Option<BigUint>
Optional estimated amount in for this swap. Necessary for RFQ protocols — used to request the quote.
estimated_gas
BigUint
Per-swap gas estimate from simulation
Split Swaps
Solutions can split one or more token hops across multiple pools. The output of one swap is divided into parts, each used as input for subsequent swaps:
By combining splits, you can build complex trade paths.
We validate split swaps. A split swap is valid if:
The output token is reachable from the input token through the swap path
No tokens are unconnected
Each split amount is smaller than 1 (100%) and at least 0 (0%)
For each set of splits, set the split for the last swap to 0. This tells the router to send all tokens not assigned to the previous splits in the set (i.e., the remainder) to this pool.
The sum of all non-remainder splits for each token is smaller than 1 (100%)
Swap Group
Protocols like Uniswap V4 eliminate token transfers between consecutive swaps through flash accounting. If your solution contains sequential (non-split) swaps on such protocols, the encoder compresses them into a single swap group, requiring only one call to the executor.
In the example above, the encoder will compress three consecutive swaps into the following swap group to call the Executor:
A solution contains multiple swap groups when it uses different protocols.
Encoding produces an EncodedSolution with these attributes:
swaps
Vec<u8>
The encoded calldata for the swaps
interacting_with
Bytes
The address of the contract to be called (e.g. the Tycho Router or an Executor)
function_signature
String
Full signature of the router method to call, e.g. splitSwapUsingVault(uint256,address,...). It reflects both the swap strategy and the funding mode
n_tokens
usize
The number of tokens in the trade (relevant for split swaps only)
estimated_gas
BigUint
Estimated gas usage for the encoded solution
Encoder
TychoRouterEncoder prepares calldata for execution via the Tycho Router contract. It supports multi-hop and split swaps.
Builder
Builder options:
swap_encoder_registry— Registry of protocol-specificSwapEncoders used during encoding. Usenew_with_defaultsfor built-in support, or add custom encoders for protocols you've implemented locally.router_address— Router address for execution. Defaults to the deployed address for the given chain ( see Tycho addresses).
Builder Example Usage
Swap Encoders
Each protocol needs its own SwapEncoder to define how the protocol encodes swaps into calldata.
The SwapEncoderRegistry manages these encoders. Use SwapEncoderRegistry::new_with_defaults(chain) to get a registry pre-populated with all built-in encoders. If you need to supply custom executor addresses, use SwapEncoderRegistry::new(chain).add_default_encoders(Some(addresses_json)) instead.
If you need to add custom protocol support, register your own encoder implementation:
Encode
Convert solutions into calldata:
This returns a Vec<EncodedSolution> containing only the encoded swaps. It does not build the full calldata. You must encode the full method call yourself. If you use Permit2, you must handle permit creation and signing yourself using the public Permit2 utility (see Token transfers).
The full method call includes the following parameters, which act as execution guardrails:
amountInandtokenIn— the amount and token you transfer into the TychoRouterV3. For native ETH, use0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE. The router reverts if the token isaddress(0)or the amount is zero.expectedAmountOut— your quoted output amount, taken fromsolution.expected_amount_out(). The router measures positive slippage against it and capsminAmountOutat it (see Slippage bounds). It must be greater than zero.minAmountOutandtokenOut— the smallest output you are willing to accept once fees are deducted, taken fromsolution.min_amount_out(). The same native ETH address rule applies. For maximum security, derive the underlying quote from a third-party source.receiver— who receives the final output. Set this to the TychoRouterV3 address to credit output tokens to the vault.nTokens— (split swaps only) the number of distinct tokens in the split routing graph.clientFeeParams— controls fee-taking and client contribution (see Client Fee Signature). Pass all-zero values if you don't need fees.
The ClientFeeParams struct is defined as:
clientFeeBps
Client fee as a uint32 in fee units, where 100_000_000 = 100% (see Fee units). Set to 0 to take no fee
clientFeeReceiver
Address that identifies the client and receives the client fee (credited to their vault balance). The router resolves negotiated fee rates and positive-slippage exemptions against it, so a signed receiver is worth passing even with clientFeeBps set to 0
maxClientContribution
Maximum amount the client is willing to pay out of pocket if slippage causes the output to fall below minAmountOut. If the shortfall exceeds this value, the transaction reverts. Set to 0 if the client should not subsidize
deadline
Unix timestamp after which the signature is no longer valid
clientSignature
EIP-712 signature over the fee fields and the full swap intent, signed by clientFeeReceiver: a 65-byte ECDSA signature when that address is an EOA, or an ERC-1271 signature of any length when it is a contract
Fee units
clientFeeBps and the router's own fee rates use an 8-decimal fee unit rather than plain basis points, which lets the router charge sub-BPS rates:
100%
100_000_000
1%
1_000_000
1 BPS (0.01%)
10_000
0.1 BPS (0.001%)
1_000
The FeeCalculator exposes both values: MAX_BPS (100_000_000) and MAX_BPS_SQUARED (MAX_BPS², the combined denominator when the router charges a fee on another fee).
The tycho-execution crate provides a ClientFeeParams Rust struct that mirrors this. Callers are responsible for constructing and signing it — the encoder does not use it internally. Call .into_abi_params() to convert it to the ABI-encodable tuple for calldata construction.
These execution guardrails protect against MEV exploits. Setting them correctly gives you full control over swap security.
Refer to the quickstart for an example of converting an EncodedSolution into full calldata. Tailor the example to your use case. See the TychoRouterV3 contract functions for reference.
Slippage bounds
minAmountOut must be non-zero and no greater than expectedAmountOut:
The router reverts with TychoRouter__InvalidMinAmountOut for a zero minAmountOut or one above expectedAmountOut. There is no lower cap on how far below the quote you may set it, so compute a real floor from your slippage tolerance — a minAmountOut set too low exposes the swap to MEV attacks. Pass the amount your simulation returned as expectedAmountOut.
Native Tokens
ETH and WETH are separate tokens in a solution, and the encoder does not convert between them for you. Wherever your route goes from one to the other, add a swap on the native_wrapper protocol. The Tycho stream injects a native_wrapper component on every chain, so you route through it like through any other pool, and a dedicated WETH executor runs the swap.
Your swaps must connect token_in to token_out, so a missing wrap swap is rejected at validation.
Client Fee Signature
Only required when charging a fee or allowing a client contribution. The clientFeeReceiver must sign using EIP-712 — this prevents third parties from spoofing fee configurations. The signature covers the fee parameters and the full swap intent:
swaps is the encoded swap graph — the same bytes you pass to the router. EIP-712 requires dynamic types to be hashed, so pass keccak256(swaps) when you build the struct hash.
The signature covers every field above, which means it only validates for a swap with identical input parameters. Re-encoding the route or changing any amount invalidates it, so sign after you encode rather than before.
To confirm you are signing the right struct, compare your typehash against the router's public CLIENT_FEE_TYPEHASH constant.
The EIP-712 domain is:
with name = "TychoRouter", version = "1", and verifyingContract set to the TychoRouterV3 contract address.
The clientFeeReceiver can be an EOA or a contract. The router first recovers the signature with ECDSA and accepts it when it recovers to clientFeeReceiver. Otherwise it staticcalls isValidSignature(digest, clientSignature) on the receiver and accepts the fee when that call returns the ERC-1271 magic value. A contract receiver therefore decides for itself what counts as a valid signature — a Safe, for example, checks owner signatures — and clientSignature carries no length constraint in that case. ECDSA runs first, so an EOA that carries delegated code (EIP-7702) keeps signing with its own key.
Replay attack risk. The signature contains no nonce. Once a signed ClientFeeParams appears on-chain, anyone who sees it can reuse it for a swap with identical input parameters — same amountIn, tokens, expectedAmountOut, minAmountOut, receiver, and encoded route — until deadline expires. If maxClientContribution > 0, the swap's receiver can repeatedly replay the transaction — each replay debits the client's vault balance by up to maxClientContribution — until the balance is exhausted or the deadline passes.
To minimise exposure:
Set
deadlineas close to the current block timestamp as practical (e.g., a few minutes ahead).Set
maxClientContributionas low as the intended use case allows.
Run as a Binary
The encoding crate ships a tycho-encode CLI that lets you encode swaps without writing Rust. Install it with:
Pass a JSON-serialised Solution via stdin and specify the encoder as a subcommand:
tycho-router— encodes usingTychoRouterEncoder
The CLI accepts the same options as the builder.
Last updated
Was this helpful?

