Async Swap AMM
- AsyncCSMM - hook contract
- Router - add liquidity, swap & fill async orders
- Live Demo Frontend
- Video Walkthrough
- Transaction Ordering rules walkthorugh video
Install
To install dependencies in all our packages in packages/*
bun install
Install foundry dependencies (v4-periphery)
forge install
Setup
[!TIP] We suggest you set up local anvil account with cast.
cast wallet import --mnemonic "test test test test test test test test test test test junk" anvil
- This will allow you to use
--account anvilin the deploys scripts in.dev/start_script.sh
Run local anvil node with Unichain fork
anvil --fork-url https://unichain.drpc.org
# or simulate block mining and finality
anvil --block-time 13
Local Deployment
Run deployment script
./dev/start_script.sh # scripts that you use --account setup of you choice
[!NOTE]
The start scripts will do the following:
- Deploy local PoolManger
./script/00_DeployPoolManager.s.sol- Deploy Hook & Router contracts
./script/01_DeployHook.s.sol- Initialize a pool with your hook attached
./script/02_InitilizePool.s.sol- Add liqudity to previously initialized pool
./script/03_AddLiquidity.s.sol- Submit an async swap transaction through custom router
./script/04_Swap.s.sol- Fill previously submitted swap transaction
./script/05_ExecuteOrder.s.sol
Testing
Run tests
forge test -vvvv
Huff tests:
# sender is poolManager address
hnc src/AsyncSwap.huff test --sender 0x0000000000000000000000000000000000000000
Offchain Indexer
Start local indexer
bun run dev
[!Tip]
- If you need typescript abi for your contracts on frontend or indexer use this script
./dev/generateAbi.sh./dev/generateAbi.sh
Go to http://localhost:42069 to query orders from hook events
Docs
View documentation:
forge doc --serve --port 4000 --watch
Acknowledgment
Thanks to Atrium Academy, over the past 2 months we build this project during Uniswap Hook incubator program.
Team Socials:
Contents
BaseAlgorithm
Inherits: IAlgorithm
State Variables
HOOKADDRESS
The address of the hook that will call this algorithm.
address public immutable HOOKADDRESS
Functions
constructor
Constructor to set the hook address.
constructor(address _hookAddress) ;
Parameters
| Name | Type | Description |
|---|---|---|
_hookAddress | address | The address of the hook that will call this algorithm. |
onlyHook
Modifier to restrict access to the hook address.
only hook contract can call
modifier onlyHook() ;
_checkCallerIsHookContract
function _checkCallerIsHookContract() internal view;
name
Returns the name of the ordering algorithm.
function name() external pure virtual returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The name of the algorithm as a string. |
version
Version of the algorithm.
function version() external pure virtual returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The version of the algorithm as a string. |
orderingRule
Executes the transaction ordering algorithm.
function orderingRule(bool zeroForOne, uint256 amount) external virtual;
Parameters
| Name | Type | Description |
|---|---|---|
zeroForOne | bool | Indicates the direction of the trade (true for currency0 to currency1, false for currency1 to currency0). |
amount | uint256 | The amount of the order being processed. |
getVolatility
function getVolatility(AsyncOrder[] memory orders) external pure virtual override returns (uint256);
CLVR
Inherits: BaseAlgorithm
Author: Meek Msaki @ Async Labs
This contract implements the CLVR algorithm for ordering transactions.
CLVR selects the next trade that minimizes price volatility (ln p0 - ln P(d, t))^2. The rule picks at each step t as the next trade that causes minimal local one-step price volatility from the status quo price p0.
Note: reference: https://arxiv.org/abs/2408.02634
Functions
constructor
constructor(address _hookAddress) BaseAlgorithm(_hookAddress);
name
Returns the name of the ordering algorithm.
function name() external pure override returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The name of the algorithm as a string. |
version
Version of the algorithm.
function version() external pure override returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The version of the algorithm as a string. |
orderingRule
Executes the transaction ordering algorithm.
function orderingRule(bool zeroForOne, uint256 amount) external override onlyHook;
Parameters
| Name | Type | Description |
|---|---|---|
zeroForOne | bool | Indicates the direction of the trade (true for currency0 to currency1, false for currency1 to currency0). |
amount | uint256 | The amount of the order being processed. |
getVolatility
function getVolatility(AsyncOrder[] memory orders) external pure override returns (uint256);
Contents
IAlgorithm
Author: Async Labs
This interface defines the functions for transaction ordering algorithms used in the Async Swap AMM hook.
Functions
orderingRule
Executes the transaction ordering algorithm.
function orderingRule(bool zeroForOne, uint256 amount) external;
Parameters
| Name | Type | Description |
|---|---|---|
zeroForOne | bool | Indicates the direction of the trade (true for currency0 to currency1, false for currency1 to currency0). |
amount | uint256 | The amount of the order being processed. |
name
Returns the name of the ordering algorithm.
function name() external view returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The name of the algorithm as a string. |
version
Version of the algorithm.
function version() external view returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The version of the algorithm as a string. |
getVolatility
function getVolatility(AsyncOrder[] memory orders) external pure returns (uint256);
IAsyncSwapAMM
Inherits: IAsyncSwapOrder
Author: Async Labs
This interface defines the functions for the Async CSMM (Constant Sum Market Maker) contract.
Functions
executeOrder
Fill an async order in an Async Swap AMM.
function executeOrder(AsyncOrder calldata order, bytes calldata userParams) external;
Parameters
| Name | Type | Description |
|---|---|---|
order | AsyncOrder | The async order to be filled. |
userParams | bytes | Additional data for the user. |
executeOrders
Fills async orders in batching mode, allowing multiple orders to be executed in a single transaction.
function executeOrders(AsyncOrder[] calldata orders, bytes calldata userParams) external;
Parameters
| Name | Type | Description |
|---|---|---|
orders | AsyncOrder[] | An array of async orders to be executed. |
userParams | bytes | Additional data for the user, allowing user to specify an executor. |
Structs
UserParams
Struct representing the user parameters for executing an async order.
struct UserParams {
address user;
address executor;
}
Properties
| Name | Type | Description |
|---|---|---|
user | address | |
executor | address |
IAsyncSwapOrder
Author: Async Labs
This interface defines the functions for the Async Swap orders.
Functions
asyncOrderAmount
Returns the claimable amount for an async order.
function asyncOrderAmount(PoolId poolId, address user, bool zeroForOne) external view returns (uint256 claimable);
Parameters
| Name | Type | Description |
|---|---|---|
poolId | PoolId | The poolId of the pool where the order is placed. |
user | address | The user who placed the order. |
zeroForOne | bool | Whether the order is for a swap from currency0 to currency1 (true) or currency1 to currency0 (false). |
Returns
| Name | Type | Description |
|---|---|---|
claimable | uint256 | The amount that can be claimed by the user. |
isExecutor
Checks if the given executor is valid for the async order.
function isExecutor(PoolId poolId, address owner, address executor) external returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
poolId | PoolId | The poolId the executor is interacting with. |
owner | address | The async order owner be checked against. |
executor | address | The address of the executor to be checked. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | isExecutor True if the executor is valid for the async order, false otherwise. |
Events
AsyncOrderFilled
Emitted when an async order is filled.
event AsyncOrderFilled(PoolId poolId, address owner, bool zeroForOne, uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
poolId | PoolId | The poolId of the pool where the order is placed. |
owner | address | The owner of the order, who can claim the order. |
zeroForOne | bool | Whether the order is for a swap from currency0 to currency1 (true) or currency1 to currency0 (false). |
amount | uint256 | The amount of the order. |
AsyncSwapOrder
Emitted when an async swap order is created.
event AsyncSwapOrder(PoolId poolId, address owner, bool indexed zeroForOne, int256 indexed amountIn);
Parameters
| Name | Type | Description |
|---|---|---|
poolId | PoolId | The poolId of the pool where the order is placed. |
owner | address | The owner of the order, who can claim the order. |
zeroForOne | bool | Whether the order is for a swap from currency0 to currency1 (true) or currency1 to currency0 (false). |
amountIn | int256 | The amount of the order that is being filled. |
Errors
InvalidOrder
Error thrown when an order is invalid.
error InvalidOrder();
ZeroFillOrder
Error thrown when an order is of zero amount.
error ZeroFillOrder();
IRouter
Author: Async Labs
This interface defines the functions for the Router contract, which allows users to swap tokens and fill orders using async orders.
Functions
swap
Swaps tokens using an async order.
function swap(AsyncOrder calldata order, bytes calldata userData) external;
Parameters
| Name | Type | Description |
|---|---|---|
order | AsyncOrder | The async order to be placed. |
userData | bytes | Additional data for the user, allowing user to specify an executor. |
fillOrder
Fills an async order.
function fillOrder(AsyncOrder calldata order, bytes calldata userData) external;
Parameters
| Name | Type | Description |
|---|---|---|
order | AsyncOrder | The async order to be filled. |
userData | bytes | Additional data for the user, allowing user to specify an executor. |
Structs
SwapCallback
Callback structure for the swap function.
struct SwapCallback {
ActionType action;
AsyncOrder order;
}
Properties
| Name | Type | Description |
|---|---|---|
action | ActionType | The action type, either Swap or FillOrder. |
order | AsyncOrder | The async order that is in context for the swap or fill operation. |
Enums
ActionType
Enum representing the action type for the swap callback.
- Swap - If the action is a swap, this will specify the async swap order intent.
- FillOrder - If the action is a fill order, this will specify fill order intent.
enum ActionType {
Swap,
FillOrder
}
Contents
AsyncFiller
Author: Async Labs
This library provides functionality for filling async swap orders in the Uniswap V4 pool.
Functions
isExecutor
Checks if the given executor is valid for the async order.
function isExecutor(AsyncOrder calldata order, State storage self, address executor) internal view returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
order | AsyncOrder | The async order to be filled. |
self | State | The storage slot that controls the state of orders. |
executor | address | The address of the executor to be checked. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | isExecutor True if the executor is valid for the async order, false otherwise. |
executeOrder
Fills async orders in batching mode, allowing multiple orders to be executed in a single transaction.
function executeOrder(AsyncOrder[] calldata orders, State storage self, bytes calldata userParams) external;
Parameters
| Name | Type | Description |
|---|---|---|
orders | AsyncOrder[] | An array of async orders to be executed. |
self | State | |
userParams | bytes | Additional data for the user, allowing user to specify an executor. |
_execute
Fill an async order in an Async Swap AMM.
function _execute(AsyncOrder calldata order, State storage self, bytes calldata) internal;
Parameters
| Name | Type | Description |
|---|---|---|
order | AsyncOrder | The async order to be filled. |
self | State | The state of the AsyncFiller library, containing async orders and executors. |
<none> | bytes |
Events
AsyncOrderFilled
Emitted when an async order is filled.
event AsyncOrderFilled(PoolId poolId, address owner, bool zeroForOne, uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
poolId | PoolId | The poolId of the pool where the order is placed. |
owner | address | The owner of the order, who can claim the order. |
zeroForOne | bool | Whether the order is for a swap from currency0 to currency1 (true) or currency1 to currency0 (false). |
amount | uint256 | The amount of the order. |
Errors
InvalidOrder
Error thrown when an order is invalid.
error InvalidOrder();
ZeroFillOrder
Error thrown when an order is of zero amount.
error ZeroFillOrder();
Structs
State
Represents the state of the AsyncFiller library storage for async orders and executors.
struct State {
IPoolManager poolManager;
IAlgorithm algorithm;
mapping(address user => mapping(bool zeroForOne => uint256 claimable)) asyncOrderAmount;
mapping(address owner => mapping(address executor => bool)) isExecutor;
}
Properties
| Name | Type | Description |
|---|---|---|
poolManager | IPoolManager | The PoolManager contract that manages the pools. |
algorithm | IAlgorithm | |
asyncOrderAmount | mapping(address user => mapping(bool zeroForOne => uint256 claimable)) | |
isExecutor | mapping(address owner => mapping(address executor => bool)) |
Contents
AsyncOrder
Represents an async order for a swap in the Uniswap V4 pool.
struct AsyncOrder {
PoolKey key;
address owner;
bool zeroForOne;
uint256 amountIn;
uint160 sqrtPrice;
}
Properties
| Name | Type | Description |
|---|---|---|
key | PoolKey | The Uniswap V4 PoolKey that identifies the pool. |
owner | address | The owner of the order, who can claim the order. |
zeroForOne | bool | Whether the order is for a swap from currency0 to currency1 (true) or currency1 to currency0 (false). |
amountIn | uint256 | The amount of the order that is being filled. |
sqrtPrice | uint160 | The square root price of the pool at the time of the order. |
Contents
TransientStorage
Author: Async Labs
This contract provides functions to interact with the transient storage.
Functions
tload
Load a value from the transient storage.
function tload(bytes32 key) internal view returns (bytes32 value);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to store or retrieve the value. |
tstore
Store a value in the transient storage.
function tstore(bytes32 key, bytes32 value) internal;
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to store or retrieve the value. |
value | bytes32 | The value to store in the transient storage. |
AsyncSwap
Inherits: BaseHook, IAsyncSwapAMM
Author: Asyncswap Labs
State Variables
asyncOrders
Mapping to store async orders.
mapping(PoolId poolId => AsyncFiller.State) public asyncOrders
ALGORITHM
Ordering algortim
IAlgorithm public immutable ALGORITHM
kvolatility
mapping(uint256 block => uint256 volatility) public kvolatility
Functions
constructor
Initializes the Async Swap Hook contract with the PoolManager address and sets an transaction ordering algorithm.
constructor(IPoolManager poolManager, IAlgorithm orderingAlgorithm) BaseHook(poolManager);
Parameters
| Name | Type | Description |
|---|---|---|
poolManager | IPoolManager | The address of the PoolManager contract. |
orderingAlgorithm | IAlgorithm | The address of the ordering algorithm |
_beforeInitialize
function _beforeInitialize(address, PoolKey calldata key, uint160) internal virtual override returns (bytes4);
getHookPermissions
Returns a struct of permissions to signal which hook functions are to be implemented
Used at deployment to validate the address correctly represents the expected permissions
function getHookPermissions() public pure override returns (Hooks.Permissions memory);
_beforeAddLiquidity
function _beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata)
internal
pure
override
returns (bytes4);
asyncOrderAmount
function asyncOrderAmount(PoolId poolId, address user, bool zeroForOne) external view returns (uint256 claimable);
isExecutor
function isExecutor(PoolId poolId, address user, address executor) external view returns (bool);
calculateHookFee
function calculateHookFee(uint256) public pure returns (uint256);
calculatePoolFee
function calculatePoolFee(uint24, uint256) public pure returns (uint256);
executeOrders
Fills async orders in batching mode, allowing multiple orders to be executed in a single transaction.
function executeOrders(AsyncOrder[] calldata orders, bytes calldata userParams) external;
Parameters
| Name | Type | Description |
|---|---|---|
orders | AsyncOrder[] | An array of async orders to be executed. |
userParams | bytes | Additional data for the user, allowing user to specify an executor. |
executeOrder
Fill an async order in an Async Swap AMM.
function executeOrder(AsyncOrder calldata order, bytes calldata) external;
Parameters
| Name | Type | Description |
|---|---|---|
order | AsyncOrder | The async order to be filled. |
<none> | bytes |
_beforeSwap
function _beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookParams
) internal override returns (bytes4, BeforeSwapDelta, uint24);
Events
HookSwap
Event emitted when a swap is executed.
event HookSwap(
bytes32 indexed id,
address indexed sender,
int128 amount0,
int128 amount1,
uint128 hookLPfeeAmount0,
uint128 hookLPfeeAmount1
);
Parameters
| Name | Type | Description |
|---|---|---|
id | bytes32 | The poolId of the pool where the swap occurred. |
sender | address | The address that initiated the swap. |
amount0 | int128 | The amount of currency0 taken in the swap (negative for exact input). |
amount1 | int128 | The amount of currency1 taken in the swap (negative for exact input). |
hookLPfeeAmount0 | uint128 | Fee amount taken for LP in currency0. |
hookLPfeeAmount1 | uint128 | Fee amount taken for LP in currency1. |
Errors
UnsupportedLiquidity
Error thrown when liquidity is not supported in this hook.
error UnsupportedLiquidity();
Router
Inherits: IRouter
Author: Async Labs
This contract implements the Router interface, allowing users to swap tokens and fill async orders through the PoolManager and Async Swap hook.
State Variables
POOLMANAGER
PoolManager contract to interact with the pools.
IPoolManager immutable POOLMANAGER
HOOK
Async Swap Hook contract to execute async orders.
IAsyncSwapAMM immutable HOOK
ACTION_LOCATION
keccak256("Router.ActionType") - 1
bytes32 constant ACTION_LOCATION = 0xf3b150ebf41dad0872df6788629edb438733cb4a5c9ea779b1b1f3614faffc69
USER_LOCATION
keccak256("Router.User") - 1
bytes32 constant USER_LOCATION = 0x3dde20d9bf5cc25a9f487c6d6b54d3c19e3fa4738b91a7a509d4fc4180a72356
ASYNC_FILLER_LOCATION
keccak256("Router.AsyncFiller") - 1
bytes32 constant ASYNC_FILLER_LOCATION = 0xd972a937b59dc5cb8c692dd9f211e85afa8def4caee6e05b31db0f53e16d02e0
Functions
constructor
Initializes the Router contract with the PoolManager and Async CSMM hook.
constructor(IPoolManager _poolManager, IAsyncSwapAMM _hook) ;
Parameters
| Name | Type | Description |
|---|---|---|
_poolManager | IPoolManager | The PoolManager contract that manages the pools. |
_hook | IAsyncSwapAMM | The Async CSMM hook contract that executes async orders. |
onlyPoolManager
Only allow the PoolManager to call certain functions.
modifier onlyPoolManager() ;
_checkCallerIsPoolManager
function _checkCallerIsPoolManager() internal view;
swap
Swaps tokens using an async order.
function swap(AsyncOrder calldata order, bytes memory userData) external;
Parameters
| Name | Type | Description |
|---|---|---|
order | AsyncOrder | The async order to be placed. |
userData | bytes | Additional data for the user, allowing user to specify an executor. |
fillOrder
Fills an async order.
function fillOrder(AsyncOrder calldata order, bytes calldata) external;
Parameters
| Name | Type | Description |
|---|---|---|
order | AsyncOrder | The async order to be filled. |
<none> | bytes |
unlockCallback
Callback handler to unlock the PoolManager after a swap or fill order.
function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory);
Parameters
| Name | Type | Description |
|---|---|---|
data | bytes | The callback data containing the action type and order information. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes | Data to return back to the PoolManager after unlock. |