To enable simulations for a newly added protocol, it must first be integrated into the Tycho Simulation repository. Please submit a pull request to the repository to include it.
In order to add a new native protocol, you will need to complete the following high-level steps:
Create a protocol state struct that contains the state of the protocol, and implements the ProtocolSim trait (see ).
Create a tycho decoder for the protocol state: i.e. implement TryFromWithBlock for ComponentWithState to your new protocol state.
Each native protocol should have its own module under tycho-simulation/src/evm/protocol.
To create a VM integration, provide a manifest file and an implementation of the corresponding adapter interface. is a library to integrate DEXs and other onchain liquidity protocols into Tycho.
The following exchanges are integrated with the VM approach:
Balancer V2 (see code )
Install , start by downloading and installing the Foundry installer:
then start a new terminal session and run
Clone the Tycho Protocol SDK:
Install dependencies:
Read the documentation of the interface. It describes the functions that need to be implemented and the manifest file.
Additionally, read through the docstring of the interface and the interface, which defines the data types and errors the adapter interface uses. You can also generate the documentation locally and look at the generated documentation in the ./docs folder:
Your integration should be in a separate directory in the evm/src folder. Start by cloning the template directory:
Implement the ISwapAdapter interface in the ./evm/src/<your-adapter-name>.sol file. See Balancer V2 implementation for reference.
Set up test files:
Copy evm/test/TemplateSwapAdapter.t.sol
Rename to <your-adapter-name>.t.sol
Once you have the swap adapter implemented for the new protocol, you will need to:
Generate the adapter runtime file by running the script in our SDK repository with the proper input parameters.
For example, in order to build the Balancer V2 runtime, the following command can be run:\
Add the associated adapter runtime file to tycho-simulations/src/protocol/vm/assets. Make sure to name the file according to the protocol name used by Tycho Indexer in the following format: <Protocol><Version>Adapter.evm.runtime. For example: vm:balancer_v2 will be BalancerV2Adapter.evm.runtime. Following this naming format is important as we use an automated name resolution for these files.
If your implementation does not support all pools indexed for a protocol, you can create a filter function to handle this. This filter can then be used when registering an exchange in the ProtocolStreamBuilder. See for example implementations.
Test all implemented functions.
Use fuzz testing (see Foundry test guide, especially the chapter for Fuzz testing)
Reference existing test files: BalancerV2SwapAdapter.t.sol
Configure fork testing (run a local mainnet fork against actual contracts and data):
Set ETH_RPC_URL environment variable
Use your own Ethereum node or services like Infura
Run the tests with
cd ./evm
forge testcurl -L https://foundry.paradigm.xyz | bashfoundryupgit clone https://github.com/propeller-heads/tycho-protocol-libcd ./tycho-protocol-lib/evm/
forge installcd ./evm/
forge doccp ./evm/src/template ./evm/src/<your-adapter-name>>>> cd evm
>>> ./scripts/buildRuntime.sh -c “BalancerV2SwapAdapter” -s “constructor(address)” -a “0xBA12222222228d8Ba445958a75a0704d566BF2C8”To integrate an EVM exchange protocol:
Implement the interface.
Create a manifest file summarizing the protocol's metadata.
The manifest file contains author information and additional static details about the protocol and its testing. Here's a list of all valid keys:
Calculates marginal prices for specified amounts.
Return marginal prices in buyToken/sellToken units.
Include all protocol fees (use minimum fee for dynamic fees).
If you don't implement this function, flag it accordingly in capabilities and make it revert using the NotImplemented error.
While optional, we highly recommend implementing this function. If unavailable, we'll numerically estimate the price function from the swap function.
Simulates token swapping on a given pool.
Execute the swap and change the VM state accordingly.
Include a gas usage estimate for each amount (use gasleft() function).
Return a Trade struct with a price attribute containing price(specifiedAmount).
Retrieves token trading limits.
Return the maximum tradeable amount for each token.
The limit is reached when the change in received amounts is zero or close to zero.
Overestimate the limit if in doubt.
Ensure the swap function doesn't error with LimitExceeded for amounts below the limit.
Retrieves pool capabilities.
Retrieves tokens for a given pool.
We mainly use this for testing, as it's redundant with the required substreams implementation.
Retrieves a range of pool IDs.
We mainly use this for testing. It's okay not to return all available pools here.
This function helps us test against the substreams implementation.
If you implement it, it saves us time writing custom tests.
If the price function isn't supported, return Fraction(0, 1) for the price (we'll estimate it numerically).
yamlCopy# Author information helps us reach out in case of issues
author:
name: Propellerheads.xyz
email: [email protected]
# Protocol Constants
constants:
# Minimum gas usage for a swap, excluding token transfers
protocol_gas: 30000
# Minimum expected capabilities (individual pools may extend these)
# To learn about Capabilities, see ISwapAdapter.sol)
capabilities:
- SellSide
- BuySide
- PriceFunction
# Adapter contract (byte)code files
contract:
# Contract runtime (deployed) bytecode (required if no source is provided)
runtime: UniswapV2SwapAdapter.bin
# Source code (our CI can generate bytecode if you submit this)
source: UniswapV2SwapAdapter.sol
# Deployment instances for chain-specific bytecode
# Used by the runtime bytecode build script
instances:
- chain:
name: mainnet
id: 1
# Constructor arguments for building the contract
arguments:
- "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"
# Automatic test cases (useful if getPoolIds and getTokens aren't implemented)
tests:
instances:
- pool_id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
sell_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
buy_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
block: 17000000
chain:
name: mainnet
id: 1function price(
bytes32 poolId,
IERC20 sellToken,
IERC20 buyToken,
uint256[] memory sellAmounts
) external returns (Fraction[] memory prices);function swap(
bytes32 poolId,
IERC20 sellToken,
IERC20 buyToken,
OrderSide side,
uint256 specifiedAmount
) external returns (Trade memory trade);function getLimits(bytes32 poolId, address sellToken, address buyToken)
external
returns (uint256[] memory limits);function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken)
external
returns (Capability[] memory);function getTokens(bytes32 poolId)
external
returns (IERC20[] memory tokens);function getPoolIds(uint256 offset, uint256 limit)
external
returns (bytes32[] memory ids);