Introducing AggregationRouter
The AggregationRouter
is a swap router that facilitates seamless trades across multiple DeFi protocols within a single transaction. By aggregating liquidity from decentralized exchanges (DEXs), lending protocols, and liquid staking protocols, it enables efficient execution of trades. This allows users to optimize their trading strategies and access the best liquidity available across various platforms.
Inspired by the architecture of Synthetix: CoreRouter, AggregationRouter
aims to improve the efficiency and accessibility of DeFi transactions. Whether you’re executing trades or leveraging liquidity from multiple sources, the AggregationRouter
provides a unified and streamlined approach to managing DeFi interactions.
Technical Overview
The AggregationRouter
is a decentralized aggregation protocol that allows the execution of swaps across multiple DeFi protocols in a single transaction. It interfaces with a variety of decentralized exchanges (DEXs), lending protocols, and liquidity staking protocols to facilitate efficient and cost-effective trades.
Encoding Calls and Path Structure
The AggregationRouter
performs swaps by delegating calls to various adapters, each corresponding to different DeFi protocols. These calls are encoded into a bytes32 path
that contains all the necessary information for the swap.
Path Encoding
The path
is a compact encoding of the following parameters:
name | type | description |
---|---|---|
pool | address | The address of the liquidity pool |
i | uint8 | The index of the currency to swap from |
j | uint8 | The index of the currency to swap for |
wrapIn | uint8 | Command for wrapping the input currency |
wrapOut | uint8 | Command for wrapping the output currency |
Wrapping Commands
The wrapIn
and wrapOut
parameters are crucial for handling native and wrapped native assets. These commands ensure that tokens are appropriately wrapped or unwrapped during the swap process to maintain compatibility with the liquidity pools involved.
value | description |
---|---|
0 | No action |
1 | Wrap native currency |
2 | Unwrap wrapped native currency |
The adapters and wrappers utilize this encoded path to execute their respective swaps, with different adapters requiring different paths based on their unique implementations.
Contract Overview
AggregationRouter
The core of the AggregationRouter
contract is a function called aggregate
. This function allows for the aggregation of multiple swap actions across various protocols in a single transaction.
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.20;
3
4interface IAggregationRouter {
5 function aggregate(
6 bytes[] calldata calls,
7 address recipient,
8 Currency currencyIn,
9 Currency currencyOut,
10 uint256 amountIn,
11 uint256 amountOutMin
12 ) external payable returns (uint256 amountOut);
13}
14
The aggregate
function delegates the encoded calls to different adapters based on their function selectors
. Each swap is executed by calling the corresponding adapter.
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.20;
3
4contract AggregationRouter {
5
6 fallback() external payable {
7 bytes4 selector = msg.sig;
8 address adapter;
9
10 assembly ("memory-safe") {
11 function map(sig) -> imp {
12 if lt(sig, 0x355672eb) {
13 if lt(sig, 0x1f99f749) {
14 switch sig
15 case 0x00befae3 {
16 imp := ATOKEN_WRAPPER // wrapAToken(bytes32)
17 }
18 case 0x04addc5c {
19 imp := FRXETH_WRAPPER // unwrapSFRXETH(bytes32)
20 }
21 case 0x07aa7eb5 {
22 imp := UNISWAP_V2_ADAPTER // uniswapV2Swap(bytes32)
23 }
24 case 0x07af82a0 {
25 imp := PANCAKE_V2_ADAPTER // pancakeV2Swap(bytes32)
26 }
27 case 0x0c3896b8 {
28 imp := STETH_WRAPPER // unwrapWSTETH(bytes32)
29 }
30 leave
31 }
32 switch sig
33 case 0x1f99f749 {
34 imp := FRAXSWAP_V2_ADAPTER // fraxV2Swap(bytes32)
35 }
36 case 0x23a69e75 {
37 imp := PANCAKE_V3_ADAPTER // pancakeV3SwapCallback(int256,int256,bytes)
38 }
39 case 0x2707a06c {
40 imp := STETH_WRAPPER // wrapWSTETH(bytes32)
41 }
42 case 0x2d66d91f {
43 imp := FRXETH_WRAPPER // wrapSFRXETH(bytes32)
44 }
45 case 0x3395962c {
46 imp := SUSHI_V3_ADAPTER // sushiV3Swap(bytes32)
47 }
48 leave
49 }
50
51 if lt(sig, 0xf1553d53) {
52 if lt(sig, 0x85abd8bb) {
53 switch sig
54 case 0x355672eb {
55 imp := FRXETH_WRAPPER // wrapFRXETH(bytes32)
56 }
57 case 0x3d36c8c0 {
58 imp := SUSHI_V2_ADAPTER // sushiV2Swap(bytes32)
59 }
60 case 0x617c7f56 {
61 imp := CTOKEN_WRAPPER // unwrapCToken(bytes32)
62 }
63 case 0x648d8619 {
64 imp := ATOKEN_WRAPPER // unwrapAToken(bytes32)
65 }
66 case 0x75c033fa {
67 imp := UNISWAP_V3_ADAPTER // uniswapV3Swap(bytes32)
68 }
69 leave
70 }
71
72 switch sig
73 case 0x85abd8bb {
74 imp := DODO_V1_ADAPTER // dodoV1Swap(bytes32)
75 }
76 case 0x89d0b46b {
77 imp := STETH_WRAPPER // wrapSTETH(bytes32)
78 }
79 case 0x9d6a221b {
80 imp := BALANCER_V2_ADAPTER // balancerV2Swap(bytes32)
81 }
82 case 0xbc6b4e64 {
83 imp := CTOKEN_WRAPPER // wrapCToken(bytes32)
84 }
85 case 0xe9fd0ca0 {
86 imp := DODO_V2_ADAPTER // dodoV2Swap(bytes32)
87 }
88 leave
89 }
90
91 switch sig
92 case 0xf1553d53 {
93 imp := CURVE_ADAPTER // curveSwap(bytes32)
94 }
95 case 0xf78144f4 {
96 imp := PANCAKE_V3_ADAPTER // pancakeV3Swap(bytes32)
97 }
98 case 0xfa461e33 {
99 imp := UNISWAP_V3_ADAPTER // uniswapV3SwapCallback(int256,int256,bytes)
100 }
101 leave
102 }
103
104 adapter := map(shr(224, selector))
105 }
106
107 if (adapter == address(0)) revert InvalidSelector(selector);
108
109 assembly ("memory-safe") {
110 let ptr := mload(0x40)
111
112 calldatacopy(ptr, 0x00, calldatasize())
113
114 let success := delegatecall(gas(), adapter, ptr, calldatasize(), 0x00, 0x00)
115
116 returndatacopy(ptr, 0x00, returndatasize())
117
118 switch success
119 case 0x00 {
120 revert(ptr, returndatasize())
121 }
122 default {
123 return(ptr, returndatasize())
124 }
125 }
126 }
127}
Fallback Function and Adapter Mapping
The fallback
function in the AggregationRouter
contract handles incoming calls and dispatches them to the correct adapter based on the function selector
. The function utilizes assembly to perform this task efficiently and securely.
By dynamically mapping the function selectors to the corresponding adapters, the fallback
function ensures that the AggregationRouter
can handle a wide variety of protocol interactions in a single, unified system.
Adapters & Wrappers
To support a wide range of protocols, the AggregationRouter
utilizes adapters and wrappers.
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.20;
3
4interface IAdapter {
5 function query(
6 Currency currencyIn,
7 Currency currencyOut,
8 uint256 amountIn
9 ) external view returns (bytes32 path, uint256 amountOut);
10
11 function quote(bytes32 path, uint256 amountIn) external view returns (uint256 amountOut);
12}
13
- Adapters are responsible for interacting with the liquidity pools of different DEX protocols, like Uniswap, Curve, and Balancer. Each adapter defines the unique logic needed to perform a swap on that protocol.
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.20;
3
4interface IWrapper {
5 function query(
6 Currency wrapped,
7 Currency underlying,
8 uint256 amountIn,
9 bool direction
10 ) external view returns (bytes32 path, uint256 amountOut);
11
12 function quote(bytes32 path, uint256 amountIn) external view returns (uint256 amountOut);
13}
- Wrappers manage the wrapping and unwrapping of assets, enabling the
AggregationRouter
to handle the conversion of tokens to wrapped tokens such as aTokens, cTokens, frxETH, and stETH, vice versa.
- query: returns the encoded path and expected amount of currency-out to be received in exchange of currency-in.
- quote: returns expected amount of currency-out to be received in exchange of currency-in.
By using these adapters and wrappers, the AggregationRouter
ensures that it can aggregate liquidity from a wide variety of DeFi protocols, whether they are DEXs, lending platforms, or liquid staking providers.
Integrated Protocols
The AggregationRouter
can interface with an extensive list of DeFi protocols, providing access to a wide range of liquidity sources. Here’s a list of supported protocols:
- Uniswap V3 Pools
- Uniswap V2 Pools
- Balancer V2 Pools
- Curve Pools
- DODO V2 Pools
- DODO V1 Pools
- FraxSwap V2 Pools
- PancakeSwap V3 Pools
- PancakeSwap V2 Pools
- SushiSwap V3 Pools
- SushiSwap V2 Pools
- Aave V2 & V3 aTokens
- Compound cTokens
- Frax: frxETH & sfrxETH
- Lido: stETH & wstETH
This broad support enables users to access the best pricing and liquidity across different platforms, optimizing trades in a way that would be difficult to achieve manually. The ability to aggregate liquidity from multiple sources ensures that users can always access the most efficient trades, regardless of which protocol offers the best pricing at any given time.
Deployment Notes
The constant addresses for adapters and wrappers used in the AggregationRouter
are defined for testing purposes using the CREATE3 pattern. These adapters and wrappers must be deployed, and the corresponding addresses must be updated in the AggregationRouter
contract before production deployment.
You can explore the full source code and integration tests here.