Release: 0.5.0
One More Note About Pathfinding
The path finders are integrated into degenbot and I will be publishing 0.5.0 shortly — hooray! It’s been a long road but I’m pleased with the database integration, pathfinding improvements, removing the soft dependency on Cryo for chain data extraction, and needing to statically generate JSON pool data and arbitrage paths.
You should see it hit PyPi today once I finalize some release checks and final tests. As always, install it with pip or uv.
Multiple Routes
In the previous post I mentioned the ability to provide a mapping between equivalent tokens. However as I continued scaling the path finder to work for arbitrary depth path searches, I realized it was clunky and refactored it.
I ultimately refactored the equivalent tokens map with the more general ability to specify a list of start and end tokens. This accomplishes the same goal while simplifying the control flow.
Here’s a sample function call taken from a test:
def test_generic_algo_multiple_tokens():
depth = 2
# UniswapV4 pools hold both native and WETH pairs, so paths to
# and from both tokens can be found using this single type
pool_types: list[type] = [UniswapV4PoolTable]
generic_paths_weth_to_weth = list(
find_paths(
chain_id=BASE_CHAIN_ID,
start_tokens=[WETH_BASE_ADDRESS],
end_tokens=[WETH_BASE_ADDRESS],
max_depth=depth,
pool_types=pool_types,
)
)
generic_paths_weth_to_native = list(
find_paths(
chain_id=BASE_CHAIN_ID,
start_tokens=[WETH_BASE_ADDRESS],
end_tokens=[ZERO_ADDRESS],
max_depth=depth,
pool_types=pool_types,
)
)
generic_paths_weth_to_weth_or_native = list(
find_paths(
chain_id=BASE_CHAIN_ID,
start_tokens=[WETH_BASE_ADDRESS],
end_tokens=[WETH_BASE_ADDRESS, ZERO_ADDRESS],
max_depth=depth,
pool_types=pool_types,
)
)
assert len(
generic_paths_weth_to_weth_or_native
) == len(
generic_paths_weth_to_weth
) + len(
generic_paths_weth_to_native
)In this test, three distinct path finding searches are performed through the set of known Uniswap V4 pools on Base mainnet:
WETH → WETH
WETH → Ether
WETH → WETH | Ether (a hybrid search with multiple destinations)
At the end, the number of paths found by the hybrid search is confirmed to match the sum of the paths found by the two single-token searches.
I also included a more complex assertion after that that confirms that all of the paths match exactly, but that required some sorting and transformation, so it’s not relevant here.
You can also run a multiple start / multiple end search:
generic_paths_weth_or_native_to_weth_or_native = list(
find_paths(
chain_id=BASE_CHAIN_ID,
start_tokens=[WETH_BASE_ADDRESS, ZERO_ADDRESS],
end_tokens=[WETH_BASE_ADDRESS, ZERO_ADDRESS],
pool_types=pool_types,
)
)Ultimately this searches four routes, returning results as the Cartesian product of the inputs:
WETH → WETH
WETH → Ether
Native Ether → WETH
Native Ether → Ether
Variable Depth
The default argument to find_paths will yield all paths with minimum depth equal to 2. A path’s depth is measured by the number of pools it contains.
There is an optional maximum depth argument, which defaults to None! Beware of leaving this at the default, because the search will continue “forever” if allowed.
There is a finite limit on the number of routes through a graph since an edge (pool) can only be used once, but for practical purposes you should always set the maximum depth to a value between 2-4. The number of possible routes increases exponentially as you increase depth.
# Find 2-pool & 3-pool paths
find_paths(
chain_id=BASE_CHAIN_ID,
start_tokens=[WETH_BASE_ADDRESS],
end_tokens=[WETH_BASE_ADDRESS],
min_depth=2,
max_depth=3,
)To perform a fixed length search, set the minimum and maximum depth equal:
# Find only 2-pool paths
find_paths(
chain_id=BASE_CHAIN_ID,
start_tokens=[WETH_BASE_ADDRESS],
end_tokens=[WETH_BASE_ADDRESS],
min_depth=2,
max_depth=2,
)
# Find only 3-pool paths
find_paths(
chain_id=BASE_CHAIN_ID,
start_tokens=[WETH_BASE_ADDRESS],
end_tokens=[WETH_BASE_ADDRESS],
min_depth=3,
max_depth=3,
)As a curiosity, Uniswap V4 allows for a WETH-Ether pair, which technically satisfies a mixed asset 1-pool path. Since this pool is a special case and a bit of a nuisance (deposits and withdrawals through the Wrapped Ether contract are always 1:1), I implicitly exclude it by setting the default depth to 2.
It’s usually not what you want, but in the interest of flexibility you can set the minimum depth to 1.
Moving Forward
With all this wrapped I’m putting two related topics on the roadmap: liquidations and lending protocols, beginning with Aave.
