Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Commonly used entities and concepts within Tycho.
This outlines the core entities and components that form the foundation of the Tycho system. Understanding these concepts is essential for working with or on the application effectively.
With ProtocolSystems we usually refer to a DeFi protocol. A group of smart contracts that work collectively provide financial services to users. Each protocol typically contains:
A single Extractor (see below)
One or more ProtocolComponents
We model major versions of protocols as distinct entities. For example, Uniswap V2 and Uniswap V3 are separate ProtocolSystems.
Attributes:
name: The protocols' identifier
protocol_type: The category of protocol being indexed, currently pure organisational use.
name: The identifier of the protocol type
financial_type: The specific financial service provided:
Swap
PSM
Debt
Leverage
attribute_schema: Currently unused; initially intended to validate static and hybrid attributes.
implementation_type: Either VM or Custom (native - see below)
Tokens represent fungible tradeable assets on a blockchain. Users interact with protocols primarily to buy, sell, or provide liquidity for tokens. While ERC20 is the most common standard, Tycho supports other token types as well.
Tycho automatically detects and ingests new tokens when a ProtocolComponent using that token is ingested in the DB. Upon detection, we run test transactions to determine the token's behavior.
Attributes:
Address: The blockchain address that uniquely identifies the token
Decimals: Number of decimal places used to represent token values
Symbol: Short human-readable identifier (e.g., ETH, USDC)
Tax: Token transfer tax in basis points, averaged across simulated transfers
Gas: Cost to transfer the token in the blockchain's native compute units
Chain: The blockchain where the token is deployed
Quality: Score from 0-100 indicating token reliability:
100: Standard token with normal behavior
75: Rebase token (supply adjusts automatically)
50: Fee token (charges fees on transfers)
10: Failed initial token analysis
9-5: Failed subsequent analysis after creation
0: Could not extract decimals from on-chain data
ProtocolComponents represent specific operations that can be executed on token sets within a ProtocolSystem. Examples include liquidity pools in DEXes or lending markets in lending protocols.
A new ProtocolComponent is created whenever a new operation becomes available for a set of tokens such as when a new trading pair is deployed on a DEX.
Attributes:
id: A unique identifier for the component
protocol_system: The parent protocol system
protocol_type_name: Subtype classification for filtering components
chain: Blockchain where the component operates
tokens: Addresses of tokens this component works with
contract_addresses: Smart contracts involved in executing operations (may be empty for native implementations)
static_attributes: Constant properties known at creation time, including:
Attributes used to filter components (e.g. RPC and/or DB queries)
Parameters needed to execute operations (fees, factory addresses, pool keys)
creation_tx: Transaction hash that created this component
created_at: Timestamp of component creation
Each component also has dynamic attributes that change over time and contain state required to simulate operations.
The indexer subsystem processes blockchain data, maintains an up-to-date representation of entities and provides RPC and Websocket endpoints exposing those entities to clients.
An Extractor processes incoming blockchain data, either at the block level or at shorter intervals (e.g. mempool data or partial blocks from builders).
The Extractor:
Pushes finalized state changes to permanent storage
Stores unfinalized data in system buffers (see ReorgBuffers below)
Performs basic validation, such as checking for the existence of related entities and verifying the connectedness of incoming data
Aggregates processed changes and broadcasts them to connected clients
Handles chain reorganizations by reverting changes in buffers and sending correction messages to clients
Tycho's persistence layer tracks state changes at the transaction level. This granular versioning enables future use cases such as:
Replay changes transaction by transaction for backtesting
Historical analysis of protocol behavior
The default storage backend (PostgreSQL) maintains versioned data up to a configurable time horizon. Older changes are pruned to conserve storage space and maintain query performance.
While the system supports versioning, alternative persistence implementations aren't required to implement this feature.
ReorgBuffers store unfinalized blockchain state changes that haven't yet reached sufficient confirmation depth.
This approach allows Tycho to:
Respond to queries with the latest state by merging buffer data with permanent storage
Handle chain reorganizations by rolling back unconfirmed changes
Send precise correction messages to clients when previously reported states are invalidated
When a reorganization occurs, the system uses these buffers to calculate exactly what data needs correction, minimizing disruption to connected applications.
The simulation library allows clients to locally compute the outcome of potential operations without executing them on-chain, enabling efficient price discovery and impact analysis.
Tycho offers two approaches for simulating protocol operations:
Virtual Machine (VM) Integration
Uses the blockchain's VM to execute operations
Requires a contract that adapts the protocol's interface to Tycho's interface
Creates a minimal local blockchain view with only the necessary contract state
Advantages:
Faster integration of new protocols
No need to reimplement complex protocol math
Disadvantages:
Significantly slower simulation compared to native implementations
Native Implementation
Reimplements protocol operations directly in Rust code
Compiles to optimized machine code for the target architecture
May still access the VM if required, e.g. to simulate Uniswap V4 hooks
Advantages:
Much faster simulation performance
More efficient for high-volume protocols
Disadvantages:
Longer integration time
Requires comprehensive understanding of protocol mathematics
Must identify and index all relevant state variables
The Solution represents a complete pathway for moving tokens through one or more protocols to fulfil a trade. It bridges the gap between finding the best trade route and actually executing it on-chain.
The flexible nature of Solutions allows them to represent simple single-hop swaps, complex multi-hop trades, or split routes where a token amount is distributed across multiple pools simultaneously. You can see more about Solutions here.
A Transaction turns a Solution into actual blockchain instructions. It contains the specific data needed to execute your trade: which contract to call, what function to use, what parameters to pass, and how much native token to send.
This is the final product that gets submitted to the blockchain. It handles all the technical details like approvals, native token wrapping/unwrapping, and proper contract interactions so you don't have to. For more about Transactions see here.
Strategies define how Solutions are translated into Transactions, offering different tradeoffs between complexity, gas efficiency, and security. They encapsulate the logic for how trades should be executed on-chain. To know which Strategies we currently support, see here.
A quickstart to swap onchain with Tycho. By the end of this quickstart, you will:
Fetch real-time market data from Tycho Indexer.
Simulate swaps between token pairs to calculate spot prices and output amounts using Tycho Simulation.
Encode the best trade, for a certain token pair, and simulate or execute it using Tycho Execution.
Want to chat with our docs? Download an LLM-friendly text file of the full Tycho docs.
Clone the Tycho Simulation repository; you'll find the quickstart code there.
To run the quickstart, run the following commands:
If you don't have an RPC URL, you can explore a list of public ones for Ethereum Mainnet and Base.
The quickstart fetches all protocol states and returns you the best amount out (best price) for a given token pair (by default, 1 WETH to USDC).
Additionally, it returns calldata to execute the swap on this pool with the Tycho Router.
You should see an output like the following:
If you would like to see the results for a different token, amount, or chain, you can set additional flags:
This example would look for the best swap for 10 USDC -> WETH on Base.
If you would like to see all the Tycho Indexer and Simulation logs run with RUST_LOG=info
:
The quickstart is a minimal example of how to:
Setup and load necessary data (such as available tokens).
Connect to the Tycho Indexer to fetch on-chain protocol data (e.g., Uniswap V2, Balancer V2) and build a Protocol Stream that streams updates (like new pools, states) in real-time.
Simulate swaps on all available pools for a specified pair (e.g. USDC, WETH), and print out the most USDC you can get for 1 WETH.
Encode a swap to swap 1 WETH against the best pool.
Execute the swap against the Tycho Router.
To run Tycho Indexer, set up the following environment variables:
URL (by default "tycho-beta.propellerheads.xyz"
)
API key (by default, the test key is sampletoken
)
TVL threshold: A filter to only get snapshot data for pools with TVL greater than the specified threshold (in ETH). Here, the default is 10,000 ETH to limit the data you pull.
The Indexer stream or the Simulation does not manage tokens; you must manage them.
To simplify this, load_all_tokens
gets all current token information from Tycho Indexer RPC for you.
The protocol stream connects to Tycho Indexer to fetch the real-time state of protocols.
Here, you subscribe to Uniswap V2 and Balancer V2 only. To include additional protocols like Uniswap V3, simply add:
For a full list of supported protocols and which simulation state (like UniswapV3State
) to use for them, see Supported Protocols.
Note: The protocol stream will supply all protocol states in the first BlockUpdate
object. All subsequent BlockUpdates
contain only new and changed protocol states (i.e., deltas).
get_best_swap
uses Tycho Simulation to simulate swaps and calculate buy amounts. We inspect all protocols updated in the current block (i.e. those with balance changes).
result
is a GetAmountOutResult
that has information on the amount out, gas cost, and the new state of the protocol. For example, if you want to do another swap after this one, you could do
By inspecting each of the amount outs, we then choose the protocol component that gives us the highest amount out.
After choosing the best swap, we can proceed to use Tycho Execution to encode it.
a. Create encoder
Knowing the best protocol component (i.e., pool), we can now put the swap into the expected input format for our encoder. For more info about the Swap
and Solution
models, please have a look here.
This step allows you to test or perform real transactions based on the best available swap options. To be able to do this step, you need to pass your wallet's private key in the run command. Handle it securely and never expose it publicly.
You'll encounter the following prompt:
You have three options:
Simulate the swap: Tests the swap without executing it on-chain. It will simulate an approval (for permit2) and a swap transaction on the node. You will see something similar to this:
If status is false
, the simulation has failed. You can print the full simulation output for detailed failure information.
execute
: Performs the swap on-chain using your real funds. The process involves performing an approval (for permit2) and a swap transaction. You'll receive transaction hashes and statuses like:
If status is false
, the execution failed. Use the transaction hash on platforms like Etherscan or Tenderly to investigate the failure.
skip
: Ignores this swap and the program resumes listening for blocks.
Market conditions can change rapidly. Delays in making your decision might lead to transaction reverts, especially if you've set parameters like minimum amount out or slippage. Always ensure you're comfortable with the potential risks before executing swaps.
If you want to run this quickstart outside of the Tycho Simulation repository, please add to your project's Cargo.toml
the following requirements:
In this quickstart, you explored how to use Tycho to:
Connect to the Tycho Indexer: Retrieve real-time protocol data filtered by TVL.
Fetch Token and Pool Data: Load all token details and process protocol updates.
Simulate Token Swaps: Compute the output amount, gas cost, and updated protocol state for a swap.
Encode a Swap: Create a solution from the best pool state and retrieve calldata to execute against a Tycho router.
Execute a Swap: Execute the best trade using the Tycho Router.
To dive deeper:
Integrate with your Solver: Add Tycho pool liquidity to your solver, starting from this guide.
Learn more about Tycho Execution: Read more about the datatypes necessary to encode an execution against a Tycho router or executor.
Learn more about Tycho Simulation: Explore advanced features like custom filters, protocol-specific simulations, and state transitions.
Explore Tycho Indexer: To add or modify the data Tycho indexes.
Important Note
In some cases, the community sponsors a bounty.
Current open bounties: Bounty Tracker
Specifically for:
DEX Integrations: Some DEXs can't integrate themselves - and instead sponsor a bounty for the community.
Orderflow integrations: Tools that want to use Tycho in their router.
Tycho X Projects: Teams can also sponsor bounties for Tycho X projects.
Cumulative Bounties: Several parties can sponsor and cumulate a bounty for the same issue.
Single winner: Bounties are, unless specified otherwise, awarded in full to the first team that satisfies the requirements.
Core Maintainer Support: Tycho maintainers will support every team working on a bounty. Incl. guidance, PR reviews, and final assessment.
Award of a Bounty: Each bounty has a board of three assessors, usually the one who specified the bounty, the sponsor of the bounty, and one dev from the Tycho team.
Discover a bounty: Find current open bounties in the – Bounty Tracker.
Reach out: Reach out to Tycho maintainers at tycho.build or dm Tanay if you plan to work on a bounty.
Submit: Submit your work in a PR and notify maintainers.
Review & Award: After a successful review by the three assessors, maintainers will merge the PR and payout the bounty. (any merged PR automatically qualifies for the bounty.)
Find the list of open bounties here: Bounty Tracker
Tycho is a community project that helps DEXs and Solvers coordinate.
A quick overview, there are three ways to contribute to Tycho:
Pick an issue: Find issues to contribute to in the Tycho issue tracker – or propose an issue for a new feature.
Build an app: Build an app using Tycho. Use the specifications in Tycho X as an inspiration.
Win a bounty: Some issues, integrations, or Tycho X projects have bounties sponsored by the community; you can find all bounties here: Bounty Tracker.
Whichever way you choose to contribute – Tycho maintainers and the community are here to help you. Before you get started:
Join tycho.build – our telegram group for Tycho builders.
Reach out to Tanay - so that he can support you and ensure someone else isn't already working on the same project.
A python package is available to ease integration into python-based projects. To install locally:
Git
Rust 1.84.0 or later
Python 3.9 or above
The Python client is a Python wrapper around our Rust Client that enables interaction with the Tycho Indexer. It provides two main functionalities:
Streaming Client: Python wrapper around Rust Client for real-time data streaming
RPC Client: Pure Python implementation for querying Tycho RPC data
The TychoStream
class:
Locates the Rust binary (tycho-client-cli
)
Spawns the binary as a subprocess
Configures it with parameters like URL, authentication, exchanges, and filters
Implements an async iterator pattern that:
Reads JSON output from the binary's stdout
Parses messages into Pydantic models
Handles errors and process termination
Here's one example on how to use it:
The TychoRPCClient
class:
Makes HTTP requests to the Tycho RPC server
Serializes Python objects to JSON
Deserializes JSON responses to typed Pydantic models
Handles blockchain-specific data types like HexBytes
Here's one example on how to use it to fetch tokens information (available at POST /v1/tokens endpoint):
Tycho indexes on-chain liquidity, with a current focus on token swaps. Future development can include other liquidity provisioning, lending, and derivatives.
The rapid innovation in DeFi protocols has created a fragmented ecosystem without standardized interfaces for fundamental operations like swaps, liquidity provisioning, etc.
Tycho aims to provide a standardized interface across those operations.
With a focus on fast local simulations on top of the latest known state of the chain and settlements through tycho-execution.
Before Tycho, you might face the following issues if you want to settle on onchain protocols:
Rewrite protocol-specific mathematics in your application programming language to simulate fast locally.
Develop protocol-specific indexing to supply data for local simulations.
Watch and filter out user-created token pairs with unusual or malicious behavior.
Navigate an enormous search space of liquidity sources with effective filtering heuristics.
Chain reorganizations ("reorgs") that alter transaction history must be handled with care.
Block propagation delays caused by peer-to-peer network topology and geographic distribution.
Continuous maintenance of node infrastructure, such as updating client versions (especially during hard forks), updating storage space, etc.
Traditional indexers rely on node client RPC interfaces, which have significant limitations:
Data must be requested from nodes, introducing latency and potential for error.
Multiple requests are often needed to assemble a complete view of the data.
Complex query contracts may be required for comprehensive data extraction (e.g., to get all Uniswap V3 ticks) whose execution adds additional latency to data retrieval.
Load-balanced RPC endpoints can expose inconsistent state views during reorgs, making it hard to scale across many node clients.
May involve maintaining and running multiple instances of modified node clients.
Tycho adopts a fundamentally different approach:
Data is pushed/streamed as a block is processed by the node client.
Current implementation leverages Substreams as the primary data source.
Alternative data sources can be integrated if they provide comparable richness.
State changes are communicated to clients through streaming interfaces.
Non-blockchain-native users shouldn't need to understand chain-specific concepts.
Reorgs and optimistic state changes remain invisible to users by default.
Users perceive only that state has changed, regardless of underlying mechanism.
Advanced users can access detailed information about state changes when needed.
Granular visibility allows inspection of upcoming state changes.
Applications can track specific liquidity pair changes for specialized use cases.
Tycho Client CLI installation documentation
The binary client is recommended for 2 situations:
For a quick setup, to consume data from Tycho Indexer direct on a terminal
To consume data from Tycho Indexer on apps developed in languages where there isn't a native tycho client available (e.g: any languages apart from Rust and Python). For the supported languages, please check the Rust Clientor Python Clientdocs.
This guide provides two methods to install Tycho Client:
Download pre-built binaries from GitHub Releases (recommended for most users)
Build from source (for developers or customized installations)
Step 1: Download the pre-built binary
For a simple, setup-free start, download the latest tycho-client
binary release that matches your OS/architecture on GitHub.
💡 Tip: Choose the latest release unless you need a specific version.
Step 2: Extract the binary from the tar.gz
Open a terminal and navigate to the directory where the file was downloaded. Run the following command to extract the contents:
Step 3: Link the binary to a directory in your system's PATH (recommended):
Step 4: Verify Installation
You should see the Tycho Client version displayed. If you need more guidance, contact us via Telegram
Git
Rust 1.84.0 or later
Step 3: Link the binary to a directory in your system's PATH (recommended):
Step 4: Verify Installation
You should see the Tycho Client version displayed. If you need more guidance, contact us via Telegram
Permission denied
chmod +x tycho-client
to make the binary executable
Command not found
Ensure the installation location is in your PATH
Other issues
If you're connecting to our hosted service, please follow our Authentication to get an API Key. Once you have a key, export it using an environment variable
or use the command line flag
Now, you're all set up!
Before consuming the data, you first need to choose which protocols you want to track. You can find a list of#available-protocols here. For example, to track the Uniswap V2 and V3 pools on Mainnet, with a minimum value locked of 100 ETH, run:
Or skip secure connections entirely with --no-tls
for local setups [coming soon].
Since all messages are sent directly to stdout in a single line, logs are saved to a file: ./logs/dev_logs.log
. You can configure the directory with the --log-dir
option.
For more details on using the CLI and its parameters, run:
For extended explanation on how each parameter works, check our Usageguide.
Overview of Tycho, its components and how to get started.
Tycho is an open-source interface to on-chain liquidity. Tycho
Indexes DEX protocol state for you with low latency,
Simulates swaps extremely fast with one interface for all DEXs, and
Executes swaps on-chain
Integrations are the largest point of friction for both DEXs and Solvers:
Solvers can't scale integrations. So, Solvers spend much of their time on integrations, and new solvers can't catch up and compete.
DEXs need to convince solvers to integrate them to get orderflow and win LPs. But Solvers prioritize DEXs with liquidity. This makes it hard for new DEXs to get the flow their design deserves.
In the end, every solver separately integrates every DEX – leading to massive wasted effort from which no one benefits.
Tycho fixes this:
DEXs can integrate themselves and don't need to wait for solvers, and
Solvers can use new DEXs without any additional effort.
Tycho lowers barriers to entry so that both innovative DEXs and Solvers have a chance to win flow.
Tycho makes it easy to simulate and execute over on-chain liquidity sources – without needing to understand protocol internals, run nodes, or do RPC calls.
To set up, go to the Tycho Indexer quickstart and start your liquidity stream.
To integrate your DEX, submit a PR to Tycho Protocol Integrations on GitHub.
To get started, check the Protocol SDK docs.
Or contact our team so we can help you integrate.
Tycho has three components for solvers:
Tycho Indexer: Infrastructure to parse, store and stream protocol state deltas. It also comes with clients in Python and Rust and a hosted webstream if you don't want to run your version of the Indexer. -> Indexer docs.
Tycho Protocol Simulation: A simulation library with a unified interface to query swaps and prices. Optimized for speed, running on compiled contracts in REVM with in-memory storage. -> Protocol Simulation docs.
Tycho Execution: Audited and gas-efficient router and DEX executor contracts for safe, simple, and competitive execution of swaps.
And one integration SDK for DEXs:
Tycho Protocol Integration: An SDK for any DEX (or Stable Coin, LRT, etc.) to integrate their liquidity and receive flow from solvers.
Tycho exposes data through two mechanisms, the RPC and the stream. The RPC provides you access to static data, like the state of a component at a given block or extended information about the tokens it has found. For streaming data, we recommend using the Tycho Client. This guide documents the RPC interfaces.
Tycho stream provides only the token addresses that Protocol Components use. If you require more token information, you can request using Tycho RPC's POST /v1/tokensendpoint. This service allows filtering by both quality and activity.
The quality rating system helps you quickly assess token's specific properties:
100: Normal ERC-20 Token behavior
75: Rebasing token
50: Fee-on-transfer token
10: Token analysis failed at first detection
5: Token analysis failed multiple times (after creation)
0: Failed to extract attributes, like Decimal or Symbol
The Token Quality Analysis was developed to aid Tycho Simulation in filtering out tokens that behave differently from standard ERC-20 Tokens. The analysis is under constant improvement and can provide wrong information.
This section documents Tycho's RPC API. Full swagger docs are available at: https://tycho-beta.propellerheads.xyz/docs/
Stream real-time onchain liquidity data
Tycho Indexer gives you a low-latency, reorg-aware stream of all attributes you need to simulate swaps over DEX and other onchain liquidity.
Tycho can track protocols in two ways:
For Native Simulation: Tycho gives structured data that mirrors on-chain states, so you can simulate protocol logic outside the VM (e.g. in your own Rust rewrite of Uni v2 swap function). Useful for example if you solve analytically over the trading curves.
Virtual Machine (VM) Compatibility: Tycho tracks the state of all protocol contracts so you can simulate calls over it with no network overhead (locally on revm). Used by Protocol Simulation to simulate key protocol functions (swap, price, derivatives etc.).
Native integrations are more effort, but run faster (~1-5 microseconds or less per simulation), VM integrations are easier to do but run slower (~100–1000 microseconds per simulation).
Complete Protocol Systems: Tycho doesn’t just track standalone data; it indexes whole systems, like Uniswap pools or Balancer components, even detecting new elements as they’re created.
Detailed Component Data: For each tracked protocol component, Tycho records not just static values (like fees or token pairs) but also dynamic state changes, ensuring you have all you need to replicate the onchain state.
Tycho Indexer leverages Substreams, a robust and scalable indexing framework by StreamingFast.
While Tycho currently uses Substreams to deliver high-performance indexing, our architecture is designed to be flexible, supporting future integrations with other data sources.
Setting up using Tycho is simple with the tycho client.
Available as a CLI binary, rust crate, or python package.
Execute swaps through any protocol.
Tycho Execution provides tools for encoding and executing swaps against Tycho routers and protocol executors. It is divided into two main components:
Encoding: A Rust crate that encodes swaps and generates calldata for execution.
Executing: Solidity contracts for executing trades on-chain.
The source code for Tycho Execution is available here. For a practical example of its usage, please refer to our Quickstart.
Tycho Execution leverages Permit2 for token approvals, simplifying the approval process while improving efficiency and security.
Before executing a swap via our router, you must approve the Permit2 contract for the specified token. You only need to do this once per token. This ensures the router has the necessary permissions to execute trades on your behalf.
When encoding a transaction, we provide functionality to build the Permit
struct. However, generating a valid signature requires access to your private key to sign the permit (that is why providing a private key is a part of the setup for encoding).
For more details on Permit2 and how to use it, see the Permit2 official documentation.
Alternatively, you have the option to transfer input tokens into the router before executing the swap (in the same transaction). This way, no approvals are necessary and a permit2 signature is not needed.
In this version of our Tycho Router, tokens are always transferred through the router before reaching the destination/next pool. This means:
The input token is transferred to the router.
The router forwards the input token to the first pool.
The output token is transferred from the pool back to the router.
The router sends the output token to the user.
For multi-hop swaps, the router handles each intermediate token transfer in the same manner.
How to integrate Tycho in different execution venues.
First, initialize the encoder with the tycho_router
shortcut:
This means that the TychoRouter
will assume that the token in was already transferred in.
The solution structure requires:
A token transfer to the TychoRouter
as the first interaction
The calldata returned from encoder.encode_router_calldata(...)
as the next interaction
Make sure to encode the initial token transfer operation yourself, as this isn't handled by the Tycho encoder.
For other venues, like UniswapX or 1inch Fusion, please contact us.
In this guide, you'll learn more about the Tycho Client and the streamed data models.
Real-Time Streaming: Get low-latency updates to stay in sync with the latest protocol changes. Discover new pools as they’re created.
TVL Filtering: Receive updates only for pools exceeding a specified TVL threshold (denominated in the Chain's Native Token).
Support for multiple protocols and chains
The client is written in Rust and available as:
Follow one of the guides above to learn how to set up the client appropriate for you.
Currently, interacting with the hosted Tycho Indexer doesn't require a personalized API Key; you can use the key sampletoken
. For broader rate-limiting, priority support, and access to new products, please contact @tanay_j
on Telegram.
Tycho Client provides a stream of protocol components, snapshots, their state changes, and associated tokens. For simplicity, we will use Tycho Client Binary as a reference, but the parameters described below are also available for our Rust and Python versions.
Note: While Tycho takes chain as a parameter, it is designed to support streaming from a single chain. If you want to consume data from multiple chains you will need to use more than one client connection.
You can request individual pools or use a minimum TVL threshold to filter the components. If you choose minimum TVL tracking, Tycho-client will automatically add snapshots for any components that exceed the TVL threshold, e.g., because more liquidity was provided. It will also notify you and remove any components that fall below the TVL threshold. Note that the TVL values are estimates intended solely for filtering the most relevant components.
TVL Filtering:
Tycho indexes all the components in a Protocol. TVL filtering is highly encouraged to speed up data transfer and processing times by reducing the number of returned components.
TVL is measured in the chain's native currency (e.g., 1 00 ETH on Ethereum Mainnet).
You can filter by TVL in 2 ways:
Set an exact TVL boundary:
This will stream updates for all components whose TVL exceeds the minimum threshold set. Note: if a pool fluctuates in TVL close to this boundary, the client will emit a message to add/remove that pool every time it crosses that boundary. To mitigate this, please use the ranged tv boundary described below.
Set a ranged TVL boundary (recommended):
This will stream state updates for all components whose TVL exceeds the add-tvl-threshold
. It will continue to track already added components if they drop below the add-tvl-threshold
, only emitting a message to remove them if they drop below remove-tvl-threshold
.
Tycho emits data in an easy-to-read JSON format. Get granular updates on each block:
Snapshots for complete component (or pool) states,
Deltas for specific updates, and
Removal notices for components that no longer match your filtration criteria.
Extractor status for keeping track of the sync status of each extractor.
Each message includes block details to help you stay on track with the latest block data.
FeedMessage
The main outer message type. It contains both the individual SynchronizerState (one per extractor) and the StateSyncMessage (also one per extractor). Each extractor is supposed to emit one message per block (even if no changes happened in that block) and metadata about the extractor's block synchronization state. The latter allows consumers to handle delayed extractors gracefully.
SynchronizerState (sync_states
)
This struct contains metadata about the extractor's block synchronization state. It allows consumers to handle delayed extractors gracefully. Extractors can have any of the following states:
Ready
: the extractor is in sync with the expected block
Advanced
: the extractor is ahead of the expected block
Delayed
: the extractor has fallen behind on recent blocks but is still active and trying to catch up
Stale
: the extractor has made no progress for a significant amount of time and is flagged to be deactivated
Ended
: the synchronizer has ended, usually due to a termination or an error
StateSyncMessage (state_msgs
)
This struct, as the name states, serves to synchronize the state of any consumer to be up-to-date with the blockchain.
The attributes of this struct include the header (block information), snapshots, deltas, and removed components.
Snapshots are provided for any components that have NOT been observed yet by the client. A snapshot contains the entire state at the header.
Deltas contain state updates observed after or at the snapshot. Any components mentioned in the snapshots and deltas within the same StateSynchronization message must have the deltas applied to their snapshot to arrive at a correct state for the current header.
Removed components is a map of components that should be removed by consumers. Any components mentioned here will not appear in any further messages/updates.
Snapshots
Snapshots are only emitted once per protocol, upon the client's startup. All the state is updated later via deltas from the next block onwards.
ComponentWithState
Tycho differentiates between component and component state.
The component itself is static: it describes, for example, which tokens are involved or how much fees are charged (if this value is static).
The component state is dynamic: it contains attributes that can change at any block, such as reserves, balances, etc.
ResponseAccount
This contains all contract data needed to perform simulations. This includes the contract address, code, storage slots, native balance, account balances, etc.
Deltas
Deltas contain only targeted changes to the component state. They are designed to be lightweight and always contain absolute new values. They will never contain delta values so that clients have an easy time updating their internal state.
Deltas include the following few special attributes:
account_updates
: Includes contract storage changes given as a contract storage key-value mapping for each involved contract address. Here, both keys and values are bytes.
new_protocol_components
: Components that were created on this block. Must not necessarily pass the tvl filter to appear here.
deleted_protocol_components
: Any components mentioned here have been removed from the protocol and are not available anymore.
new_tokens
: Token metadata of all newly created components.
component_balances
: Balances changes are emitted for every tracked protocol component.
component_tvl
: If there was a balance change in a tracked component, the new tvl for the component is emitted.
account_balances
: For protocols that need the balance (both native and ERC-20) of accounts tracked for the simulation package (like BalancerV3 which needs the Vault balances), the updated balances are emitted.
Note: exact byte encoding might differ depending on the protocol, but as a general guideline integers are big-endian encoded.
Simulate interactions with any protocol.
Tycho Simulation is a Rust crate that provides powerful tools for interacting with protocol states, calculating spot prices, and simulating token swaps.
To use the simulation tools with Ethereum Virtual Machine (EVM) chains, add the optional evm
feature flag to your dependency configuration:
Add this to your project's Cargo.toml
file.
spot_price
returns the pool's current marginal price.
get_amount_out
simulates token swaps.
You receive a GetAmountOutResult
, which is defined as follows:
new state
allows you to, for example, simulate consecutive swaps in the same protocol.
Please refer to the in-code documentation of the ProtocolSim
trait and its methods for more in-depth information.
fee
returns the fee of the protocol as a ratio.
For example if the fee is 1%, the value returned would be 0.01.
If the fee is dynamic, it returns the minimal fee.
get_limits
returns a tuple containing the maximum amount in and out that can be traded between two tokens.
If there are no hard limits to the swap (for example for Uniswap V2), the returned amount will be a "soft" limit, meaning that the actual amount traded could be higher but it's advised to not exceed it.
To maintain up-to-date states of the protocols you wish to simulate over, you can use a Tycho Indexer stream. Such a stream can be set up in 2 easy steps:
It is necessary to collect all tokens you are willing to support/swap over as this must be set on the stream builder in step 2. You can either set up custom logic to define this, or use the Tycho Indexer RPC to fetch and filter tokens for you. To simplify this, a util function called load_all_tokens
is supplied and can be used as follows:
The stream created emits BlockUpdate messages which consist of:
block number
- the block this update message refers to
new_pairs
- new components witnessed (either recently created or newly meeting filter criteria)
removed_pairs
- components no longer tracked (either deleted due to a reorg or no longer meeting filter criteria)
states
- the updated ProtocolSim
states for all components modified in this block
The first message received will contain states for all protocol components registered to. Thereafter, further block updates will only contain data for updated or new components.
Note: For efficiency,
ProtocolSim
states contain simulation-critical data only. Reference data such as protocol names and token information is provided in theProtocolComponent
objects within thenew_pairs
field. Consider maintaining a store of these components if you need this metadata.
Clone the repo, then run:
You will see a UI where you can select any pool, press enter, and simulate different trade amounts on the pool.
The program prints logs automatically to a file in the logs
directory in the repo.
The rust crate provides a flexible library for developers to integrate Tycho’s real-time data into any Rust application.
To use Tycho Client in Rust, add the following crates to your Cargo.toml
:
Currently, our creates are not available on crates.io - please use our git reference
Step 2: Use Tycho-client
From there it is easy to add a Tycho stream to your rust program like so:
Via the Tycho Router – Execute trades through our audited router for seamless execution.
Directly to the Executor – Bypass the Tycho Router and execute the trade using your own router.
This provides greater control on the token transfers and approvals. But also gives you greater responsibility to make sure that the swap was executed correctly. You are responsible for token approvals, token transfers and error handling in your execution flow.
Steps to integrate Tycho Executors into your own router:
Implement something similar to Dispatcher that routes calldata to the correct Executor
contract for swap and in case of callbacks.
Ensure that your router contract correctly manages token approvals and transfers.
Append the calldata for the swap to your overall execution flow.
⚠️ Security Considerations
Tycho's Router has been audited, and its entire execution flow has been verified. However, when using direct execution, Tycho is not responsible for security checks, validation, or execution guarantees. You assume full responsibility for managing token approvals, transfers, and error handling. Ensure that your router contract implements the necessary security measures to prevent reentrancy, slippage manipulation, or loss of funds.
Currently, Tycho supports the following protocols:
Live tracker & Upcoming protocols
There are two types of implementations:
Native protocols have been implemented using an analytical approach and are ported to Rust - faster simulation.
VM protocols execute the VM bytecode locally - this is easier to integrate the more complex protocols, however has slower simulation times than a native implementation.
Integrating with Tycho requires three components:
Indexing: Provide the protocol state/data needed for simulation and execution
Simulation: Implement the protocol's logic for simulations
Execution: Define how to encode and execute swaps against your protocol
Important: Simulation happens entirely off-chain. This means everything needed during simulation must be explicitly indexed.
Tycho offers two integration modes:
Native Rust Integration: Implement a Rust trait that defines the protocol logic. Values used in this logic must be indexed as state attributes.
To enable swap execution, implement:
SwapEncoder: A Rust struct that formats input/output tokens, pool addresses, and other parameters correctly for the Executor
contract.
Executor: A Solidity contract that handles the execution of swaps over your protocol's liquidity pools.
Tycho supports many protocol designs, however certain architectures present indexing challenges.
Before integrating, consider these limitations:
Soon to be supported:
Protocols that interface with external contracts during operations Tycho should support (i.e swap, price and limit calculations etc.). External contracts are those not deployed by the protocol's factories. ERC20 token contracts are exempt from this restriction.
Not supported:
Protocols where any operation that Tycho should support requires off-chain data, such as signed prices.
Our indexing integrations require a Substreams SPKG to transform raw blockchain data into structured data streams. These packages enable our indexing integrations to track protocol state changes with low latency.
Substreams is a new indexing technology that uses Rust modules to process blockchain data. These modules, along with protobuf definitions and a manifest, are packaged into an SPKG file which can be run on the Substreams server.
Learn more:
VM integrations primarily track contract storage associated with the protocol’s behavior. A key limitation in Substreams to keep in mind is that you must witness a contract’s creation to access its full storage. Most integrations will likely use the VM method due to its relative simplicity, so this guide focuses on VM-based integrations.
It's important to keep in mind that simulations are run in an empty VM loaded only with the indexed contracts and storage. If your protocol calls external contracts during any simulation (swaps, price calculations etc), those contracts must also be indexed.
Native integrations follow a similar approach with one main difference: instead of emitting changes in contract storage slots, they should emit values for all created and updated attributes relevant to the protocol’s behavior.
The Tycho Indexer ingests all data versioned by block and transaction. This approach helps maintain a low-latency feed and correctly handles chains that may undergo reorgs. The key requirements for the data emitted are:
Each state change must include the transaction that caused it
Each transaction must be paired with its corresponding block
All changes must be absolute values (final state), not deltas
Changes must be aggregated at the transaction level; it is considered an error to emit BlockChanges
with duplicate transactions in the changes
attributes.
To ensure compatibility across blockchains, many of the data types used are encoded as variable-length bytes. This flexible approach requires an informal interface so that consuming applications can interpret these bytes consistently:
Integers: When encoding integers, particularly those representing balances, always use unsigned big-endian format. Balances are referenced at multiple points within the system and need to be consistently decoded along their entire journey.
Strings: Use UTF-8 encoding for any string data stored as bytes.
Attributes: Attribute encoding is variable and depends on the specific use case. However, whenever possible, follow the encoding standards mentioned above for integers and string
Tycho Protocol Integrations should communicate the following changes:
New Protocol Components: Notify any newly added protocol components, such as pools, pairs, or markets—essentially, anything that indicates a new operation can now be executed using the protocol.
ERC20 Balances: Whenever the balances of any contracts involved with the protocol change, report these changes in terms of absolute balances.
Protocol State Changes: For VM integrations, this typically involves reporting contract storage changes for all contracts whose state may be accessed during a swap operation (except token contracts).
For a hands-on integration guide, refer to the following pages:
The first step to execute a trade on chain is encoding.
To simplify the setup, we provide shortcut methods for common use cases:
initialize_tycho_router
: Uses the TychoRouter contract, assuming that you transfer the input token to the router contract in the same transaction.
initialize_tycho_router_with_permit2
: Uses Permit2 for token approvals and transfers. This method requires a signer private key.
When initializing the EVMEncoderBuilder, you must define a strategy. The strategy determines how the trade is encoded.
Currently, we support two encoding strategies:
These are the models used as input and output of the encoding crate.
The Solution
struct specifies the details of your order and how it should be filled. This is the input of the encoding module.
The Solution
struct consists of the following attributes:
Note: For security reasons, if you encode the solution for execution against the TychoRouter
, checks on the final amount will be performed at the end of the swap. This means that you must either pass the checked_amount
in the solution, or the slippage
with the expected_amount
. This does not apply if you encode the solution for direct execution against the Executor
contracts.
Our router accepts wrapping native tokens to wrapped token before performing the first swap, and unwrapping wrapped tokens to native tokens after the final swap, before sending the funds to the receiver.
In order to perform this, the native_action
parameter of the solution must be set to either Some(NativeAction.WRAP)
or Some(NativeAction.UNWRAP)
.
When wrapping:
The given_token
of the solution should be ETH
The token_in
of the first swap should be WETH
When unwrapping:
The checked_token
of the solution should be ETH
The token_out
of the final swap should be WETH
A solution consists of one or more swaps. A swap represents a swap operation to be performed on a pool.
The Swap
struct has the following attributes:
Solutions can have splits where one or more token hops are split between two or more pools. We perform internal validation on split swaps. A split swap is considered valid if:
The checked token is reachable from the given token through the swap path
There are no tokens that are unconnected
Each split amount is small than 1 (100%) and larger or equal to 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%)
Certain protocols, such as Uniswap V4, allow you to save token transfers between consecutive swaps thanks to their flash accounting. In case your solution contains sequential (non-split) swaps of such protocols, our encoders compress these consecutive swaps into a single swap group, meaning that a single call to our executor is sufficient for performing these multiple swaps.
In the example above, the encoder will compress three consecutive swaps into the following swap group to call the UniswapV4 executor:
One solution will contain multiple swap groups if different protocols are used.
The following diagram shows a swap from ETH to DAI through USDC. ETH arrives in the router and is wrapped to WETH. The solution then splits between three (WETH, USDC) pools and finally swaps from USDC to DAI on one pool.
The Solution
object for the given scenario would look as follows:
Encoding returns you a Transaction
struct. It has the following attributes:
First, build and install the binary:
After installation, you can use the tycho-encode
command from any directory in your terminal.
tycho-router
: Encodes a transaction using the Tycho Router encoding strategy. Requires a private key for signing Permit2.
tycho-router-permit2
: Encodes a transaction using the Tycho Router encoding strategy. Requires a private key for signing Permit2.
direct-execution
: Encodes a transaction using the direct execution encoding strategy. Does not require a private key.
The commands accept the following options:
--executors-file-path
: Path to the executor addresses configuration file (defaults to config/executor_addresses.json
)
--swapper-pk
: Private key for signing approvals (required for tycho-router-permit2
)
Example
Here's a complete example that encodes a swap from WETH to DAI using Uniswap V2 and the Tycho Router strategy with Permit2:
contact us via
To solve orders on , you'll need to prepare your solution following specific formatting requirements.
When solving for CoW Protocol, you need to return a that contains a list of interactions to be executed in sequence.
Alternatively, you can create a new StrategyEncoder
specific for CoWSwap that encodes the extra token transfer. See how to contribute to Tycho.
Tycho Client helps you consume data from Tycho Indexer. It's the recommended way to connect to the Indexer data stream, whether you're using our or running your own instance.
If you are developing in Rust and is using Tycho to simulate DeFi Protocol's behavior, we recommend checking out our package - this tool extends Tycho Client's data streaming functionality with powerful simulation capabilities.
We welcome community contributions to expand language support. See our contribution guidelines .
Snapshots are simple messages that contain the complete state of a component (ComponentWithState) along with the related contract data (ResponseAccount). Contract data is only emitted for protocols that require vm simulations, it is omitted for protocols implemented natively (like UniswapV2 - see the list of and how they're implemented).
Note: for related tokens, only their addresses are emitted with the component snapshots. If you require more token information, you can request using 's endpoint
state_updates
: Includes attribute changes, given as a component to state key-value mapping, with keys being strings and values being bytes. The attributes provided are protocol-specific. Tycho occasionally makes use of reserved attributes, see for more details.
The repository is available .
The tycho-simulation
package will soon be available on . Until then, you can import it directly from our GitHub repository.
Note: Replace x.y.z
with the latest version number from our . Using the latest release ensures you have the most up-to-date features and bug fixes.
All protocols implement the ProtocolSim
trait (see definition ). It has the main methods:
You can use the to easily set up and manage multiple protocols within one stream. An example of creating such a stream with Uniswap V2 and Balancer V2 protocols is as follows:
For a full list of supported protocols and the simulation state implementations they use, see .
You can find an example of a price printer .
You'll need an RPC to fetch some static protocol info. You can use any RPC provider – e.g. set one up with .
Tycho offers another packaged called Tycho Simulation, which uses Tycho Client to handle data streams and also implements simulations, allowing you to leverage the full power of Tycho. If your goal is to simulate the protocol's behavior, please check our guide.
You can also use the client to interact with Tycho RPC for fetching static information. For example, you can fetch tokens (available at endpoint) with the following:
Once you have the calldata from , you can execute your trade in one of two ways:
The source code for the Tycho Router is (see contract addresses ). To execute a trade, simply send the calldata generated by the TychoEncoder
to the router. If you chose to encode your data without permit2 for token approval and transferring, then you need to send the tokens to our router beforehand.
For an example of how to execute trades using the Tycho Router, refer to the .
If you use the Execution Strategy (see how to select strategies), you will receive only the calldata for a single swap without any Tycho Router-specific data.
You need to integrate Tycho Executors into your own router contract. Implement a mechanism similar to our , which uses delegate calls to interact with the Executor
contracts.
Currently supported protocols and Tycho status:
Interested in adding a protocol? Refer to the documentation for implementation guidelines.
is a library to integrate liquidity layer protocols (DEXs, Staking, Lending etc.) into Tycho.
Provide a package that emits a specified set of messages. If your protocol already has a , you can adjust it to emit the required messages.
VM Integration: Implement an adapter interface in a language that compiles to VM bytecode. This SDK provides a Solidity interface (). Simulations run in an empty VM loaded only with the indexed contracts, storage and token balances.
Example, for Mainnet:
Details of the data model that encodes these changes, transactions, and blocks in messages are available . These models facilitate communication between Substreams and the Tycho Indexer, as well as within Substreams modules. Tycho Indexer expects to receive a BlockChanges
output from your Substreams package.
Some attribute names are reserved for specific functions in our simulation process. Use these names only for their intended purposes. .
Our Rust offers functionality to convert your trades into calldata, which the Tycho contracts can execute.
See this section for an example of how to encode your trade.
To create an , you need to use an . A valid encoder needs to have a chain and a strategy encoder set. More info about the strategies can be found .
initialize_direct_execution
: Generates calldata for direct execution via the Executor contracts, bypassing the Tycho router. This is useful if you're integrating Tycho Executors with your own routing logic. See more details .
Split Swap Strategy: Enables multi-hop trade execution via the Tycho Router. Supports solutions.
Executor Strategy: Generates calldata for direct execution via the Executor contracts (no router interaction). Supports multi-swap trades only if all swaps can be compressed into a single , otherwise, only single-hop trades are supported.
To see how both encoding and executing fit together, have a look at our .
To create a Swap
, use the new
where you can pass any struct that implements Into<ProtocolComponent>
.
The command lets you choose the encoding strategy to be used. These correspond to the shortcut methods of the . The available strategies are:
uniswap_v2
Native (UniswapV2State
)
1 μs (0.001 ms)
Ethereum, Base
uniswap_v3
Native (UniswapV3State
)
20 μs
Ethereum, Base
uniswap_v4
Native (UniswapV4State
)
3 μs
Ethereum, Base
vm:balancer_v2
VM (EVMPoolState
)
0.5 ms
Ethereum
vm:curve
VM (EVMPoolState
)
1 ms
Ethereum
sushiswap_v2
Native (UniswapV2State
)
1 μs (0.001 ms)
Ethereum
pancakeswap_v2
Native (UniswapV2State
)
1 μs (0.001 ms)
Ethereum
pancakeswap_v3
Native (UniswapV3State
)
20 μs
Ethereum
Ethereum (Mainnet)
tycho-beta.propellerheads.xyz
Base Mainnet
tycho-base-beta.propellerheads.xyz
Unichain Mainnet
[soon]
given_token
Bytes
The token being sold (exact in) or bought (exact out)
given_amount
BigUint
Amount of the given token
checked_token
Bytes
The token being bought (exact in) or sold (exact out). This token's final balance will be checked by the router if you provide a checked_amount
.
sender
Bytes
Address of the sender of the given token
receiver
Bytes
Address of the receiver of the checked token
exact_out
bool
False if the solution is an exact input solution (i.e. solves a sell order). Currently only exact input solutions are supported.
router_address
Bytes
Address of the router contract to be used. See Tycho addresses here.
swaps
Vec<Swap>
List of swaps to fulfil the solution.
slippage
Option<f64>
If set, this value will be applied to expected_amount
and will become the checked_amount
if greater than the user-inputted checked_amount
(exact in) or less than the user-inputted checked_amount
(exact out).
expected_amount
Option<BigUint>
Amount expected to receive (exact in) or pay (exact out) for this solution. Slippage is applied onto this amount.
checked_amount
Option<BigUint>
Minimum amount out (exact in) or maximum amount in (exact out) to be checked for the solution to be valid if passing through the TychoRouter
. If not set, the slippage
and expected_amount
will be used to perform checks.
native_action
Option<NativeAction>
If set, the native token will be wrapped before the swap or unwrapped after the swap (more here).
component
ProtocolComponent
Protocol component from Tycho core
token_in
Bytes
Token you provide to the pool
token_out
Bytes
Token you expect from the pool
split
f64
Percentage of the amount in to be swapped in this operation (for example, 0.5 means 50%)
to
Bytes
The contract address to which you send the transaction.
value
BigUint
Amount of ETH to send (for native transactions)
data
Vec<u8>
The encoded calldata containing the swap details.
Before integrating, ensure you have a thorough understanding of the protocol’s structure and behavior. Key areas to focus on include:
Contracts and their roles: Identify the contracts involved in the protocol and the specific roles they play. Understand how they impact the behavior of the component you're integrating.
Conditions for State Changes: Determine which conditions, such as oracle updates or particular method calls, trigger state changes (e.g. price updates) in the protocol.
Component Addition and Removal: Check how components are added or removed within the protocol. Many protocols either use a factory contract to deploy new components or provision new components directly through specific method calls.
Once you have a clear understanding of the protocol's mechanics, you can proceed with the implementation.
We provide two templates that outline all necessary implementation steps to get you started:
ethereum-template-factory
: Use when the protocol deploys one contract per pool (e.g., UniswapV2, UniswapV3)
ethereum-template-singleton
: Use when the protocol uses a fixed set of contracts (e.g., UniswapV4)
If you are unsure which one to choose please ask in the tycho.build group for support.
Once you have chosen a template:
Create a new directory for your integration by copying the template, rename all the references to ethereum-template-[factory|singleton]
to [CHAIN]-[PROTOCOL_SYSTEM]
(please use lowercase letters):
Now, generate the required protobuf code by running:
Next, register the new package within the workspace by adding it to the members list in substreams/Cargo.toml
.
Add any ABIs specific to your protocol under [CHAIN]-[PROTOCOL-SYSTEM]/abi/
Your project should now compile and be runnable with substreams:
If you're using a template, at minimum you'll need to implement three key sections to ensure proper functionality:
The templates include TODO comments at lines that likely require your attention. Additionally, each function is documented with explanations and hints on when modifications may be necessary.
Identify newly created ProtocolComponents
and Metadata
Extract relevant protocol components and attach all necessary metadata needed for encoding swaps (or other actions) or filtering components. Examples of such attributes could be: pool identifier, pool keys, swap fees, pool_type and any other relevant static properties. Note that some attribute names are reserved. They may not always be needed but must be respected for compatibility.
Emit balances for ProtocolComponents
Tycho tracks TVL per component, so you must emit a BalanceChange whenever an event impacts the balances associated with a component. Absolute balances are expected here. Often protocols are only able to identify balance deltas - to handle these effectively please refer to our page detailing handling relative balances.
Track relevant storage slot changes [VM implementations only]
For factory-like protocols: the template covers this automatically as long as the ProtocolComponent.id
is equivalent with the contract address.
For singleton contracts: you'll have to collect the contract changes for the contracts you need tracked. To handle these effectively please refer to our page detailing tracking of contract storage.
Some protocols may require additional customisation based on their specific architecture. See Common Problems & Patterns for how to handle these cases.
Note: this implementation pattern is, by default, used in the ethereum-template-factory template.
If protocols use factories to deploy components, a common pattern used during indexing is to detect the creation of these new components and store their contract addresses to track them downstream. Later, you might need to emit balance and state changes based on the current set of tracked components.
Implement logic to identify newly created components. A recommended approach is to create a factory.rs
module to facilitate the detection of newly deployed components.
Use the logic/helper module from step 1 in a map handler that consumes substreams_ethereum::pb::eth::v2::Block
models and outputs a message containing all available information about the component at the time of creation, along with the transaction that deployed it. The recommended output model for this initial handler is BlockTransactionProtocolComponents.
Note that a single transaction may create multiple components. In such cases, TransactionProtocolComponents.components
should list all newly created ProtocolComponents
.
After emitting, store the protocol components in a Store
. This you will use later in the module to detect relevant balance changes and to determine whether a contract is relevant for tracking.
Emitting state or balance changes for components not previously registered/stored is considered an error.
Install Rust. You can do so with the following command:
You can do so with any of the following:
For other installation methods, see the official buf website
Start by making a fork of the Tycho Protocol SDK repository
Clone the fork you just created
Make sure everything compiles fine
We provide a comprehensive testing suite for Substreams modules. The testing suite facilitates end-to-end testing, ensuring your Substreams modules function as expected. For unit tests, please use standard Rust unit testing practices.
The testing suite runs Tycho Indexer with your provided Substream implementation for a specific block range and verifies that the end state matches the expected state specified on the testing YAML file. This confirms that your substreams package is indexable and outputs what you're expecting.
Next it simulates transactions using Tycho Simulation engine. This will verify that the that all necessary data was indexed as well as the functionality of the provided SwapAdapter
contract.
It is important to understand that the simulation engine runs entirely offchain and only has access to the data and contracts you index (token contracts are mocked and need not be indexed).
Inside your substreams directory you'll need an integration_test.tycho.yaml file. This test template file already outlines everything you need, however for clarity some test configs are expanded upon here:
skip_balance_check
By default this should be false. During testing the balances reported for the component are verified by comparing them to the onchain balances of the Component.id. This should be set to false if:
the Component.id does not correlate to a contract address
balances are not stored on the component's contract (i.e. they're stored on a vault)
If this skip is set to true, it is required that you comment the reason why.
initialized_accounts
Set to a list of addresses of contracts that are required during simulation, but their creation is not indexed within the test block range. Leave empty if not required.
It is important to note that this config is used during testing only. It is expected that the accounts listed here are still properly initialised by your substreams package. This configuration only eliminates the need to include historical blocks containing the initialisation events in your test data. This is useful for ensuring tests are targeted and quick to run.
The initialized_accounts
config can be used on 2 levels in the test configuration file:
global: the accounts listed here are used for all tests in this suite
test level: the accounts listed here are scoped to that test only
expected_components
A list of the components whose creation you are testing, including all component data (tokens, static attributes etc). You do not need to include all components created within your test block range, only the ones you wish to focus on in the test.
skip_simulation
By default this should be set to false and should only be set to true temporarily if you want to isolate testing the indexing phase only, or for extenuating circumstances (such as you are testing indexing a pool type that simulation does not support yet). If set to true, it is required to comment the reason why.
At the most, an integration test is expected to take 5-10 minutes to run. If the tests take noticeably longer than that, there are a few key things you can look into:
ensure you have no infinite loops within your code.
ensure you are using a small block range for your test, ideally below 1000 blocks. The blocks you include in your test need to cover only the creation of the component you are testing and optionally extend to blocks with changes for that component that you want the test to cover. To help keep the test block range small, it might be useful for you to look into the initialized_accounts config.
make sure you are not indexing tokens. Token contracts tend to use a lot of storage, so are slow to fetch historical data for. Instead they are mocked on the simulation engine and do not need to be explicitly indexed. Exceptions include if they have unique behavior, such as can act as both a token and a pool, or rebasing tokens that provide a getRate
method.
Note: substreams uses cache to improve the speed of subsequent runs of the same module. The first run of a test will always be slower than subsequent runs (unless you adjust the substreams module).
There are 2 main causes for this error:
your substream package is not indexing a contract that is needed for simulations
your test starts at a block that is later than the block the contract is created on. To fix this, add the missing contract to the initialized_accounts test config.
For enhanced debugging, running the testing module with the --tycho-logs flag is recommended. It will enable Tycho-indexer logs.
In some cases, you may need to create custom intermediate protobuf messages, especially when facilitating communication between Substreams handler modules or storing additional data in stores.
Place these protobuf files within your Substreams package, such as ./substreams/ethereum-template/proto/custom-messages.proto
. Be sure to link them in the substreams.yaml
file. For more details, refer to the substreams manifest documentation or review the official Substreams UniswapV2 example integration.
Some protocol design choices follow a common pattern. Instructions on how to handle these cases are provided. Such cases include:
Tracking contract storage [VM implementations]
A common protocol design is to use factories to deploy components. In this case it is recommended to detect the creation of these components and store their contract addresses (an potentially other metadata) to track them for use later in the module. See Tracking Components.
For VM implementations it is essential that the contract code and storage of all involved contracts are tracked. See Tracking Contract Storage.
For some protocols, absolute component balances are not easily obtainable. Instead, balance deltas/changes are observed. Since absolute balances are expected by Tycho, it is recommended to use a balance store to track current balances and apply deltas as the occur. See Normalizing relative ERC20 Balances.
For protocols that store balances in an a-typical way (not on dedicated pool contracts), a special approach to balance tracking must be used. See Tracking Contract Balances.
When a contract change is indexed, consumers of the indexed data typically trigger recalculating prices on all pools marked as associated with that contract (the contract is listed in the ProtocolComponent
's contracts field). In the case where multiple components are linked to a single contract, such as a vault, this may cause excessive and unnecessary simulations on components that are unaffected by a specific change on the linked contract. In this case it is recommended to use 'manual update' triggers. See Reserved Attributes for more details.
It is often the case where data needs to be persisted between modules in your substream package. This may be because components and their metadata (such as their tokens, or pool type) are needed when handling state changes downstream, or could be because the protocol reports relative changes instead of absolute values and the relative changes must be compounded to reach an absolute value. For this, substream Stores and Custom Protobuf Models are recommended.
This implementation pattern is, by default, used in both the ethereum-template-factory and the ethereum-template-singleton templates.
In VM implementations, accurately identifying and extracting relevant contract changes is essential.
The tycho_substreams::contract::extract_contract_changes
helper function simplifies this process significantly.
Note: These contract helper functions require the extended block model from substreams for your target chain.
In factory-based protocols, each contract typically corresponds to a unique component, allowing its hex-encoded address to serve as the component ID, provided there is a one-to-one relationship between contracts and components.
The example below shows how to use a component store to define a predicate. This predicate filters for contract addresses of interest:
For protocols where contracts aren't necessarily pools themselves, you'll need to identify specific contracts to track. These addresses can be:
Hard-coded (for single-chain implementations)
Configured via parameters in your substreams.yaml file (for chain-agnostic implementations)
Read from the storage of a known contract (hardcoded or configured)
Here's how to extract changes for specific addresses using configuration parameters:
Sometimes the balances a component uses is stored on a contract that is not a dedicated single pool contract. During Tycho VM simulations, token contracts are mocked and any balances checked or used during a swap need to be overwritten for a simulation to succeed. Default behavior is for the component balances reported to be used to overwrite the pool contract balances. This assumes 2 things: there is a one-to-one relationship between contracts and components, and the hex-encoded contract address serves as the component ID.
If a protocol deviates from this assumption, the balances for each appropriate contract needs to be tracked for that contract. All contracts that have their balances checked/accessed during a simulation need to be tracked in this way.
Implement logic/a helper function to extract the absolute balances of the contract. This is protocol specific and might be obtained from an event, or extracted from a storage slot if an appropriate one is identified.
Create an InterimContractChange
for the contract and add the contract balances using upsert_token_balance
.
Add these contract changes to the appropriate TransactionChangesBuilder
using add_contract_changes
.
An example for a protocol that uses a single vault contract is as follows:
Some best practices we encourage on all integrations are:
Clear Documentation: Write clear, thorough comments. Good documentation:
Helps reviewers understand your logic and provide better feedback
Serves as a guide for future developers who may adapt your solutions
Explains why you made certain decisions, not just what they do
Module Organisation: For complex implementations it is recommended to:
Break large module.rs
files into smaller, focused files
Place these files in a modules
directory
Name files clearly with numerical prefixes indicating execution order (e.g., 01_parse_events.rs
, 02_process_data.rs
)
Use the same number for parallel modules that depend on the same previous module
A good example of this done well is in the uniswap-v4 implementation.
Substream Initial Block:
Your package will work just fine setting the initial block in your manifest file to 1
, however it means anyone indexing your protocol has to wait for it to process an excessive number of unnecessary blocks before it reaches the first relevant block. This increases substream costs and causes long wait times for the protocol to reach the current block.
A good rule of thumb is to identify the earliest deployed contract that you index and set this config to that block.
Performance Considerations:
Minimize use of .clone()
, especially in loops or on complex/nested data structures. Instead use references (&
) when possible.
Before continuing, ensure the following tools and libraries are installed on your system:
Docker: Containerization platform for running applications in isolated environments.
Conda: Package and environment manager for Python and other languages.
AWS CLI: Tool to manage AWS services from the command line.
Git: Version control tool
Rust: Programming language and toolchain
GCC: GNU Compiler Collection
libpq: PostgreSQL client library
OpenSSL (libssl): OpenSSL development library
pkg-config: Helper tool for managing compiler flags
Conda: Python package manager
pip: Python package installer
The testing system relies on an EVM Archive node to fetch the state from a previous block. Indexing only with Substreams, as done in Tycho's production mode, requires syncing blocks since the protocol's deployment date, which can take a long time. The node skips this requirement by fetching all the required account's storage slots on the block specified in the testing yaml
file.
The node also needs to support the debug_storageRangeAt method, as it's a requirement for our Token Quality Analysis.
The testing module runs a minified version of Tycho Indexer. You can ensure that the latest version is correctly setup in your PATH by running the following command on your terminal:
If the command above does not provide the expected output, you need to (re)install Tycho.
If you're running on a MacOS (either Apple Silicon or Intel) - or any architecture that is not supported by pre-built releases, you need to compile the Tycho Indexer:
Step 1: Clone Tycho-Indexer repo
Step 2: Build the binary in release mode
Step 3: Link the binary to a directory in your system's PATH:
NOTE: This command requires
/usr/local/bin
to be included in the system'sPATH.
While this is typically the case, there may be exceptions.If
/usr/local/bin
is not in yourPATH
, you can either:
Add it to your
PATH
by exporting it:Or create a symlink in any of the following directories (if they are in your
PATH
):
Step 4: Verify Installation
We provide a binary compiled for Linux x86/x64 architecture on our GitHub releases page.
This method will only work if you are running on a Linux with an x86/x64 architecture
Step 1: Download the pre-built binary
Navigate to the Tycho Indexer Releases page, locate the latest version (e.g.: 0.54.0)
and download the tycho-indexer-x86_64-unknown-linux-gnu-{version}.tar.gz
file.
Step 2: Extract the binary from the tar.gz
Open a terminal and navigate to the directory where the file was downloaded. Run the following command to extract the contents:
Step 3: Link the binary to a directory in your system's PATH:
NOTE: This command requires
/usr/local/bin
to be included in the system'sPATH.
While this is typically the case, there may be exceptions.If
/usr/local/bin
is not in yourPATH
, you can either:
Add it to your
PATH
by exporting it:Or create a symlink in any of the following directories (if they are in your
PATH
):
Step 4: Verify Installation
Tests are defined in a yaml
file. A documented template can be found at substreams/ethereum-template/integration_test.tycho.yaml
. The configuration file should include:
The target Substreams config file.
The corresponding SwapAdapter and args to build it.
The expected protocol types.
The tests to be run.
Each test will index all blocks between start-block
and stop-block
, verify that the indexed state matches the expected state, and optionally simulate transactions using the provided SwapAdapter
.
You will also need the VM Runtime file for the adapter contract. Our testing script should be able to build it using your test config. The script to generate this file manually is available under evm/scripts/buildRuntime.sh
.
To set up your test environment, run the setup environment script. It will create a Conda virtual env and install all the required dependencies.
This script must be run from within the tycho-protocol-sdk/testing
directory.
Lastly, you need to activate the conda env:
Export the required environment variables for the execution. You can find the available environment variables in the .env.default
file. Please create a .env
file in the testing
directory and set the required environment variables.
RPC_URL
Description: The URL for the Ethereum RPC endpoint. This is used to fetch the storage data.
The node needs to be an archive node and support debug_storageRangeAt method.
Example: export RPC_URL="https://ethereum-mainnet.core.chainstack.com/123123123123"
SUBSTREAMS_API_TOKEN
Description: The JWT token for accessing Substreams services. This token is required for authentication. Please refer to Substreams Authentication guide to setup and validate your token.
Example: export SUBSTREAMS_API_TOKEN=eyJhbGci...
If you do not have one already, you must build the wasm file of the package you wish to test. This can be done by navigating to the package directory and running:
Then, run a local Postgres test database using docker-compose.
Run tests for your package. This must be done from the main project directory.
Example
If you want to run tests for ethereum-balancer-v2
, use:
Testing CLI args
A list and description of all available CLI args can be found using:
For enhanced debugging, running the testing module with the --tycho-logs flag is recommended. It will enable Tycho-indexer logs
Please make sure that the following commands pass if you have changed the code:
We are using the stable toolchain for building and testing, but the nightly toolchain for formatting and linting, as it allows us to use the latest features of rustfmt
and clippy
.
If you are working in VSCode, we recommend you install the rust-analyzer extension, and use the following VSCode user settings:
Install foudryup and foundry
We use Slither to detect any potential vulnerabilities in our contracts.
To run locally, simply install Slither in your conda env and run it inside the foundry directory.
We use conventional commits as our convention for formatting commit messages and PR titles.
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 here).
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. Tycho Protocol SDK 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 here)
Install Foundry, 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 Ethereum Solidity interface. It describes the functions that need to be implemented and the manifest file.
Additionally, read through the docstring of the ISwapAdapter.sol interface and the ISwapAdapterTypes.sol 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
Write comprehensive tests:
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
Once you have the swap adapter implemented for the new protocol, you will need to:
Generate the adapter runtime file by running the evm/scripts/buildRuntime.sh
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 here for example implementations.
Tycho Execution offers an encoding tool (a Rust crate for generating swap calldata) and execution components (Solidity contracts). This is how everything works together.
The following diagram summarizes the code architecture:
The TychoEncoder
is responsible for validating the solutions of orders and providing the user with a list of transactions that you must execute against the TychoRouter
or Executor
s.
At initialization, you can choose which SwapStrategyEncoder
should the TychoEncoder
use:
SplitSwapStrategyEncoder:
executes the transaction through the TychoRouter
ExecutorStrategyEncoder:
bypasses the TychoRouter
and executes directly against our executors.
Internally, the user-selected SwapStrategyEncoder
chooses the appropriate SwapEncoder
(s) to encode the individual swaps, which depend on the protocols used in the solution.
The TychoRouter
calls one or more Executor
s (corresponding with the output of the SwapEncoder
s) to interact with the correct protocol and perform each swap of the solution. The TychoRouter
optionally verifies that the user receives a minimum amount of the output token.
If you select the ExecutorStrategyEncoder
during setup, you must execute the outputted calldata directly against the Executor
which corresponds to the solution’s swap’s protocol. Beware that you are responsible for performing any necessary output amount checks. This strategy is useful if you want to call Tycho executors through your own router. For more information direct execution, see here.
To integrate a new protocol into Tycho, you need to implement two key components:
SwapEncoder (Rust struct) – Handles swap encoding.
Executor (Solidity contract) – Executes the swap on-chain.
See more about our code architecture here.
Each new protocol requires a dedicated SwapEncoder
that implements the SwapEncoder
trait. This trait defines how swaps for the protocol are encoded into calldata.
This function encodes a swap and its relevant context information into calldata that is compatible with the Executor
contract. The output of the SwapEncoder
is the input of the Executor
(see next section). See current implementations here.
Every integrated protocol requires its own swap executor contract. This contract must conform to the IExecutor
interface, allowing it to interact with the protocol and perform swaps. See currently implemented executors here.
It has the main method:
This function:
Accepts the input amount (givenAmount
).
Processes the swap using the provided calldata (data
) which is the output of the SwapEncoder
.
Returns the final output amount (calculatedAmount
).
Ensure that the implementation supports transferring received tokens to a designated receiver address, either within the swap function or through an additional transfer step.
If the protocol requires token approvals (allowances) before swaps can occur, manage these approvals within the implementation to ensure smooth execution of the swap.
Make sure to have an integration test that uses the calldata from the SwapEncoder
as input.
As described in the Swap Group section in our solver encoding docs, our swap strategies support protocols which save token transfers between consecutive swaps using systems such as flash accounting. In such cases, as shown in the diagram below using Uniswap V4 as an example, the SwapEncoder
is still only in charge of encoding a single swap. These swaps will then be concatenated at the SwapStrategy
level as a single executor call.
Depending on the index of the swap in the swap group, the executor may be responsible for adding additional information which is not necessary in other swaps of the sequence (see the first swap in the diagram below).
Some protocols require a callback during swap execution. In these cases, the executor contract must inherit from ICallback
and implement the necessary callback functions.
Required Methods
handleCallback
: The main entry point for handling callbacks.
verifyCallback
: Should be called within handleCallback
to ensure that the msg.sender
is a valid pool from the expected protocol.
Once your implementation is approved:
Deploy the executor contract on the appropriate network.
Contact us to whitelist the new executor address on our main router contract.
Update the configuration by adding the new executor address to executor_addresses.json
and register the SwapEncoder
within the SwapEncoderBuilder
.
By following these steps, your protocol will be fully integrated with Tycho, enabling it to execute swaps seamlessly.
To integrate an EVM exchange protocol:
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 pool prices for specified amounts.
Return prices in buyToken/sellToken units.
Include all protocol fees (use minimum fee for dynamic fees).
Implement this method as view
for efficiency and parallel execution.
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)
.
If the price function isn't supported, return Fraction(0, 1)
for the price (we'll estimate it numerically).
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.
Certain attribute names are reserved exclusively for specific purposes. Please use them only for their intended applications. Attribute names are unique: if the same attribute is set twice, the value will be overwritten.
The following attributes names are reserved and must be given using ProtocolComponent.static_att
. These attributes MUST be immutable.
manual_updates
Determines whether the component updates should be manually triggered using the update_marker
state attribute. By default, updates occur automatically whenever there is a change indexed for any of the required contracts. For contracts with frequent changes, automatic updates may not be desirable. For instance, a change in Balancer Vault storage should only trigger updates for the specific pools affected by the change, rather than for all pools indiscriminately. The manual_updates
field helps to control and prevent unnecessary updates in such cases.
Set to [1u8]
to enable manual updates.
pool_id
The pool_id
static attribute is used to specify the identifier of the pool when it differs from the ProtocolComponent.id
. For example, Balancer pools have a component ID that corresponds to their contract address, and a separate pool ID used for registration on the Balancer Vault contract (needed for swaps and simulations).
Notice: In most of the cases, using ProtocolComponent.id
is preferred over pool_id
and pool_id
should only be used if a special identifier is strictly necessary.
This attribute value must be provided as a UTF-8 encoded string in bytes.
The following attributes names are reserved and must be given using EntityChanges
. Unlike static attributes, state attributes are updatable.
stateless_contract_addr
The stateless_contract_addr_{index}
field specifies the address of a stateless contract required by the component. Stateless contracts are those where storage is not accessed for the calls made to it during swaps or simulations.
Note: If no contract code is given, the consumer of the indexed protocol has to access a chain node to fetch the code. This is considered non-ideal and should be avoided where possible.
An index is used if multiple stateless contracts are needed. This index should start at 0 and increment by 1 for each additional stateless_contract_addr
.
The value for stateless_contract_addr_{index}
can be provided in two ways:
Direct Contract Address: A static contract address can be specified directly.
Dynamic Address Resolution: Alternatively, you can define a function or method that dynamically resolves and retrieves the stateless contract address at runtime. This can be particularly useful in complex contract architectures, such as those using a dynamic proxy pattern. It is important to note that the called contract must be indexed by the Substreams module.
This attribute value must be provided as a UTF-8 encoded string in bytes.
1. Direct Contract Address
To specify a direct contract address:
2. Dynamic Address Resolution
To specify a function that dynamically resolves the address:
stateless_contract_code
The stateless_contract_code_{index}
field is used to specify the bytecode for a given stateless_contract_addr
. The index used here must match with the index of the related address.
This attribute value must be provided as bytes.
update_marker
Set to [1u8]
to trigger an update.
balance_owner
[deprecated]The balance_owner
field specifies the address of the account that owns the protocol component tokens, when tokens are not owned by the protocol component itself or the multiple contracts are involved. This is particularly useful for protocols that use a vault, for example Balancer.
This attribute value must be provided as bytes.
Implement the interface.
If it's enable, updates on this component are only triggered by emitting an update_marker
state attribute (described ).
This is particularly useful in scenarios involving DELEGATECALL
. If the contract's bytecode can be retrieved in Substreams, provide it using the stateless_contract_code
attribute (see ).
The update_marker
field is used to indicate that a pool has changed, thereby triggering an update on the protocol component. This is particularly useful for when is enabled.
The use of the balance_owner
reserved attribute has been deprecated in favour of tracking contract balances directly. See .
Tracking balances is complex if only relative values are available. If the protocol provides absolute balances (e.g., through logs), you can skip this section and simply emit the absolute balances.
To derive absolute balances from relative values, you’ll need to aggregate by component and token, ensuring that balance changes are tracked at the transaction level within each block.
To accurately process each block and report balance changes, implement a handler that returns the BlockBalanceDeltas
struct. Each BalanceDelta
for a component-token pair must be assigned a strictly increasing ordinal to preserve transaction-level integrity. Incorrect ordinal sequencing can lead to inaccurate balance aggregation.
Example interface for a handler that uses an integer, loaded from a store to indicate if a specific address is a component:
Use the tycho_substream::balances::extract_balance_deltas_from_tx
function from our Substreams SDK to extract BalanceDelta
data from ERC20 Transfer events for a given transaction, as in the Curve implementation.
To efficiently convert BlockBalanceDeltas
messages into absolute values while preserving transaction granularity, use the StoreAddBigInt
type with a store module. The tycho_substream::balances::store_balance_changes
helper function simplifies this task.
Typical usage of this function:
Finally, associate absolute balances with their corresponding transaction, component, and token. Use the tycho_substream::balances::aggregate_balances_changes
helper function for the final aggregation step. This function outputs BalanceChange
structs for each transaction, which can then be integrated into map_protocol_changes
to retrieve absolute balance changes per transaction.
Example usage:
Each step ensures accurate tracking of balance changes, making it possible to reflect absolute values for components and tokens reliably.
This endpoint retrieves the state of contracts within a specific execution environment. If no
contract ids are given, all contracts are returned. Note that protocol_system
is not a filter;
it's a way to specify the protocol system associated with the contracts requested and is used to
ensure that the correct extractor's block status is used when querying the database. If omitted,
the block status will be determined by a random extractor, which could be risky if the extractor
is out of sync. Filtering by protocol system is not currently supported on this endpoint and
should be done client side.
The version of the requested state, given as either a timestamp or a block.
If block is provided, the state at that exact block is returned. Will error if the block has not been processed yet. If timestamp is provided, the state at the latest block before that timestamp is returned. Defaults to the current time.
This endpoint retrieves components within a specific execution environment, filtered by various criteria.
The minimum TVL of the protocol components to return, denoted in the chain's native token.
This endpoint retrieves the state of protocols within a specific execution environment.
Whether to include account balances in the response. Defaults to true.
The version of the requested state, given as either a timestamp or a block.
If block is provided, the state at that exact block is returned. Will error if the block has not been processed yet. If timestamp is provided, the state at the latest block before that timestamp is returned. Defaults to the current time.
This endpoint retrieves tokens for a specific execution environment, filtered by various criteria. The tokens are returned in a paginated format.
Quality is between 0-100, where: