Shush Swap is a Uniswap V4 plugin allowing private interactions with a compliant pool.
The private swaps hook allows users to deposit ERC-20 tokens into a custodian smart contract along with a commitment used later to facilitate swaps that are verifiably backed and may only be executed once. This allows users to anonymously swap currencies or send any ERC-20 token to another account.
To use the private swaps hook in a conforming V4 pool, users must initially approve the external hooks contract to use their input ERC-20 token. Following, users will invoke the depositSwapPayment(IERC20 tokenIn, unit256 amount, bytes32 depositCommitment)
which will transfer an amount
of tokenIn
to the contract. Attached to the deposit is a private depositCommitment
which is poseidon("DEPOSIT", amount, nullifier)
. The nullifier is used by the prover to create proofs that a swap is associate with an existing deposit that has yet to be used. During the deposit, the depositCommitment
is insert as a leaf in a merkle tree which is associate with the input token.
When a swapper (either the original depositor or anyone with the nullifier) attempts to make a private swap they must create a proof that:
- They know a nullifier (+
amount
andtokenIn
) that satisfies the constraintposeidon("DEPOSIT", amount, nullifier)
is a leaf in the merkle tree for a token (private inputs:nullifier
,merklePath
; public inputs:tokenIn
,amount
,merkleRoot
)
Prior to executing the swap, the swapper may call prepareSwap(address swapper, address tokenIn, uint256 amount, bytes proof)
which will verify the proof and ensure:
- The
merkleRoot
for thetokenIn
andamount
matches the proof's public inputs - the swap commitment (
poseidon("SWAP", amount, nullifier)
) does not exist
Following the proof verification, the private swaps contract will store the swapCommitment
(which is associated with the tokenIn
) and increase the credit of the swapper
by the amount
. By increasing the swapper
's credit for the tokenIn
, it allows the swapper
to use an amount
of tokenIn
during a V4 swap - provided during the beforeSwap
hook.
By altering the swap proving system, the private swaps hook can allow a swapper to use an amount
of tokenIn
where amount
is less than or equal to their remaining balance of the initial deposit. The swapper must also specify the swapNonce
which is a counter that increments per swap using the deposit. When adding an inital deposit to the private swaps contract, the leaf is poseidon("DEPOSIT", 0, amount, nullifier)
. To achieve this, the contract will store the depositCommitment
and amount
.
The swap proof is slightly modified where:
- The swap commitment checks the swapper know the nullifier, initial deposit amount and swapNonce (where
swapNonce = # of swaps using the deposit
) - The current swap amount is less than or equal to the corresponding deposit's amount
- The proof outputs a newly generated deposit commitment where
depositCommitment = poseidon("DEPOSIT", swapNonce + 1, remainingAmount, nullifier)
andremainingAmount = initialDepositAmount - swapAmount
The private swap contract contains an additional input remainingDepositCommitment
that will be inserted to the deposit tree for the tokenIn
and may be used later to execute further swaps.
One extension to the private swaps hook is to allow swappers to execute swaps to a stealth address. This stealth address can be deterministically derived from the depositor's address and their depositNullifier
. In the beforeSwap
hook, the private swaps hook will check if a stealth address (an output of the proof) has credited balance and will pull from this balance if so.
- Lifecycle Hooks: initialize, position, swap and donate
- Hook managed fees
- swap and/or withdraw
- static or dynamic
- Swap and withdraw protocol fees
- ERC-1155 accounting of multiple tokens
- Native ETH pools like V1
- Donate liquidity to pools
Contract dependencies
This project uses Foundry to manage dependencies, compile contracts, test contracts and run a local node. See Foundry installation for instructions on how to install Foundry which includes forge
and anvil
.
git clone [email protected]:naddison36/uniswap-v4-hooks.git
cd uniswap-v4-hooks
forge install
forge update v4-periphery
The following will run the unit tests in test/CounterScript
forge test -vvv
- The CounterHook demonstrates the
beforeModifyPosition
,afterModifyPosition
,beforeSwap
andafterSwap
hooks. - The CounterScript deploys the v4 pool manager, test tokens, counter hook and test routers. It then sets up a pool, adds token liquidity and performs a swap.
- The CounterScript script deploys to a local Anvil node and does a swap.
Because v4 exceeds the bytecode limit of Ethereum and it's business licensed, we can only deploy & test hooks on a local node like Anvil.
The following runs the script/CounterScript Forge script against a local Anvil node that:
- Deploys the Uniswap v4 PoolManager
- Uses the
CounterFactory
to deploy a CounterHook contract with the correct address prefix. - Creates a new pool with
CounterHook
as the hook. - Adds token liquidity to the pool
- Performs a token swap
# start anvil with a larger code limit
anvil --code-size-limit 30000
# in a new terminal, run the Forge script
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
forge script script/CounterScript.sol \
--rpc-url http://localhost:8545 \
--code-size-limit 30000 \
--broadcast
WARNING The above private key for account 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 is only used for Foundry testing. Do not use this known account on mainnet.
# Get the counter values from the CounterHook
export COUNTER=0x3CD91b522f2a6CB67F378d7fBee767602d5140bB
cast call $COUNTER "beforeSwapCounter()(uint256)" --rpc-url http://localhost:8545
cast call $COUNTER "afterSwapCounter()(uint256)" --rpc-url http://localhost:8545
Summary of the modify position calls
Summary of the swap calls
See here for more detailed transaction traces with call parameters and events.
- The MyHook contract has empty
beforeInitialize
,afterInitialize
,beforeModifyPosition
,afterModifyPosition
,beforeSwap
,afterSwap
,beforeDonate
andafterDonate
hooks that can be implemented. - The MyHookScript script deploys to a local Anvil node and does a swap.
# start anvil with a larger code limit
anvil --code-size-limit 30000
# in a new terminal, run the Forge script
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
forge script script/MyHookScript.sol \
--rpc-url http://localhost:8545 \
--code-size-limit 30000 \
--broadcast
- The DynamicFeeHook contract has an empty
getFee
function that is used to set the fee for dynamic fee pools. It also has emptybeforeModifyPosition
,afterModifyPosition
,beforeSwap
andafterSwap
hooks but they are not required for a dynamic fee hook. They can be removed if not needed. - The DynamicFeeScript script deploys to a local Anvil node and does a swap. Note the fee in the PoolKey is set with the
DYNAMIC_FEE_FLAG
.
// Derive the key for the new pool
poolKey = PoolKey(
Currency.wrap(address(token0)), Currency.wrap(address(token1)), FeeLibrary.DYNAMIC_FEE_FLAG, 60, hook
);
// Create the pool in the Uniswap Pool Manager
poolManager.initialize(poolKey, SQRT_RATIO_1_1);
Summary of the swap calls
See here for more examples and details.
# start anvil with a larger code limit
anvil --code-size-limit 30000
The following runs the DynamicFeeScript against a local Anvil node
# in a new terminal, run the Forge script
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
forge script script/DynamicFeeScript.sol \
--rpc-url http://localhost:8545 \
--code-size-limit 30000 \
--broadcast
This repository was created from this GitHub project template https://github.com/saucepoint/v4-template. Thanks @saucepoint for an excellent starting point. This repo has significantly evolved from the starting template.
- Uniswap V3 Development Book
- Uniswap v4 Core whitepaper
- Uniswap v4-core repository
- Uniswap v4-periphery repository contains advanced hook implementations that serve as a great reference.
- A curated list of Uniswap v4 hooks
- Uniswap v4 Hooks: Create a fully on-chain "take-profit" orders hook on Uniswap v4
- SharkTeam's Best Security Practices for UniswapV4 Hooks
- Research - What bad hooks look like