Scope: one Vault contract per user (fund isolation). An Agent periodically adjusts Uniswap v4 LP positions based on an off-chain strategy.
Assumption: interact only with “no-hook” Uniswap v4 pools; do not require atomic rebalancing (the Agent may complete a rebalance via multiple transactions).
- After a user funds their personal Vault, the Agent can, according to an off-chain strategy, operate in the specified v4 pool to:
- create/adjust multiple LP positions (to produce a desired liquidity distribution)
- keep part of the funds idle in the Vault (not necessarily fully deployed into LP)
- (optional) perform swaps for rebalancing (controlled by a user permission toggle)
- The user can set risk boundaries (the pool, the tick range in which liquidity may be provided, whether swaps are allowed, and a maximum number of positions K).
- The user can pause the Agent at any time and force-remove all liquidity to enable withdrawal.
- No atomic rebalancing requirement: the Agent may use multiple transactions (reducing per-tx complexity).
- No hooks support: hookData is always empty bytes; only interact with no-hook pools.
- No multi-hop swaps / cross-pool routing: swaps are single-pool exact-in only.
- No explicit deposit API: users fund the Vault via direct ERC20
transfer/transferFrom(handled externally) or ETHtransfer.
| Parameter | Description |
|---|---|
poolKey |
The target v4 pool the Vault may provide liquidity to (currency0/currency1/fee/tickSpacing, etc.) |
| Parameter | Description |
|---|---|
agent |
The Agent address authorized to operate this Vault |
K |
Max number of positions the Agent may manage/create; K = 0 means unlimited |
allowedTickLower / allowedTickUpper |
The tick range in which the Agent may add/increase liquidity (applies to mint / increase) |
swapAllowed |
Whether the Agent is allowed to swap for rebalancing |
agentPaused |
Whether the Agent's access is paused |
Semantics of updating the tick boundaries:
- After updating the tick boundary, the Agent must not mintPosition / increaseLiquidity outside the new boundary.
- Existing positions whose tick ranges exceed the new boundary are not immediately affected (they can remain as-is, be decreased, burned, or have fees collected). However, they must not be increased (if an increase would keep it in an out-of-bound range, it must be rejected).
- The only party who can withdraw assets (
withdraw). - Can update
agent, pause/resume the Agent, toggleswapAllowed, update tick boundaries, and set/update K (if K is designed to be adjustable). - Can trigger emergency exit (
pauseAndExitAll): pauses the Agent and burns all positions so funds return to the Vault.
- Can only call restricted Vault methods:
- LP: mint / increase / decrease / burn / collect fees
- swaps (only if
swapAllowed=true)
- Cannot withdraw assets, cannot transfer assets out of the Vault, cannot move position NFTs, and cannot modify owner settings.
- PositionManager (v4-periphery): manages positions via the command-based
modifyLiquidities(). - Universal Router: performs swaps via
execute()+ v4 swap actions. - Permit2: manages token spending permissions via allowance + expiration (spenders are PositionManager / UniversalRouter).
- The user calls
Factory.createVault()with:poolKey- the target Uniswap v4 poolagent- address authorized to manage positionsallowedTickLower/allowedTickUpper- initial tick boundariesswapAllowed- whether agent can perform swapsmaxPositionsK- max positions (0 = unlimited)
- The Factory deploys a new V4AgenticVault via CREATE2 with the user as owner
- The vault address can be pre-computed using
Factory.getVault()
- Users transfer ERC20 tokens directly into the Vault (
transfer), or use an external flow that callstransferFrom; the Vault provides no deposit function. - If the pool involves native ETH, the Vault must keep a
receive()function to accept ETH transfers. - The user configures Permit2 allowances:
- at minimum for the PositionManager
- additionally for the UniversalRouter if swaps are enabled
Based on the strategy output, the Agent may execute a transaction sequence such as:
collectFeesToVaultdecreaseLiquidityToVault/burnPositionToVault(remove/adjust existing positions)swapExactInputSingle(optional, if allowed)mintPosition/increaseLiquidity(create/adjust multiple positions)- leave idle funds in the Vault (by doing nothing)
- The user calls
pauseAndExitAll(pause + burn all positions), returning assets to the Vault. - The user calls
withdrawto move funds to an external wallet.
- immutable:
poolKeyposm(PositionManager),universalRouter,permit2
- mutable (updatable by Owner):
agentagentPausedswapAllowedallowedTickLower/allowedTickUpperK
- position tracking:
positionIds[]isManagedPosition[tokenId]tokenId -> (tickLower, tickUpper)
- Owner methods:
- config:
setAgent,setAgentPaused,setSwapAllowed,setAllowedTickRange,setMaxPositionsK - funds:
withdraw - approvals:
approveTokenWithPermit2 - emergency:
pauseAndExitAll - compatibility:
receive()(needed only if currency0 or currency1 is native ETH)
- config:
- Agent methods (restricted):
- LP:
mintPosition,increaseLiquidity,decreaseLiquidityToVault,collectFeesToVault,burnPositionToVault - swaps (restricted):
swapExactInputSingle(single-pool exact-in)
- LP:
- Tick constraints (for mint / increase):
tickLower >= allowedTickLowertickUpper <= allowedTickUppertickLower < tickUpper- ticks must be multiples of
tickSpacing
- Existing positions after boundary updates:
- allow
decrease / burn / collect - reject any
mint / increasethat would create or add liquidity outside the new boundary
- allow
- Position count limit K:
K = 0: unlimitedK > 0: beforemintPosition, requirepositionIds.length < K
- Swap constraints:
- only the fixed
poolKey - requires
minAmountOut+deadline swapAllowedmust be true
- only the fixed
Deploys and tracks V4AgenticVault instances for users.
- immutable:
posm(PositionManager)universalRouterpermit2
- tracking:
vaults[]- array of all created vault addressesvaultsCreatedBy[creator][]- vaults created by each addressisVault[address]- quick lookup for valid vaults
createVault(poolKey, agent, allowedTickLower, allowedTickUpper, swapAllowed, maxPositionsK)→ returns vault address- Deploys a new V4AgenticVault with
msg.senderas owner - Uses CREATE2 with salt derived from
(msg.sender, poolKey, nonce)for deterministic addresses - Emits
VaultCreatedevent
- Deploys a new V4AgenticVault with
computeVaultAddress(creator, poolKey, nonce)→ computes vault address without deployment (for UX)getVaultsCreatedBy(creator)→ returns array of vault addresses created by an addressgetAllVaults()→ returns all created vaults
VaultCreated(address indexed owner, address indexed vault, PoolKey poolKey, address agent)
- CREATE2: Enables deterministic addresses for better indexing, pre-computation, and UX
- Centralized protocol addresses: Factory holds immutable references to PositionManager, UniversalRouter, and Permit2 so users don't need to specify them
- Registry: On-chain tracking enables enumeration and validation of legitimate vaults
- Permissionless: Anyone can create a vault for themselves (no allowlist)
- The Vault must not approve v4 positions to the Universal Router (to prevent third parties from using the router to remove liquidity).
- The Agent must never be able to
withdraw, must not be able to transfer NFTs, and must not be able to modify owner settings. - The Vault should expose only the minimal swap interface to prevent arbitrary calldata injection.
- Swaps must use
minAmountOut+deadlineto reduce MEV and adverse price movement risk.
- Use Permit2
expirationto limit long-lived approval risk (allowances expire automatically).
pauseAndExitAllprovides a one-click exit: remove all positions, return funds to the Vault, then the Owner withdraws.
- Position Manager guide (command-based interface)
https://docs.uniswap.org/contracts/v4/guides/position-manager - Manage liquidity quickstarts (mint / increase / burn / collect)
https://docs.uniswap.org/contracts/v4/quickstart/manage-liquidity/mint-position
https://docs.uniswap.org/contracts/v4/quickstart/manage-liquidity/increase-liquidity
https://docs.uniswap.org/contracts/v4/quickstart/manage-liquidity/burn-liquidity
https://docs.uniswap.org/contracts/v4/quickstart/manage-liquidity/collect - Swap quickstart (Universal Router + V4_SWAP)
https://docs.uniswap.org/contracts/v4/quickstart/swap - Swap routing guide (why use the Universal Router)
https://docs.uniswap.org/contracts/v4/guides/swap-routing - SDK v4 single-hop swapping guide
https://docs.uniswap.org/sdk/v4/guides/swaps/single-hop-swapping
- AllowanceTransfer reference
https://docs.uniswap.org/contracts/permit2/reference/allowance-transfer - Permit2 integration guide (Uniswap blog)
https://blog.uniswap.org/permit2-integration-guide - Permit2 repo
https://github.com/Uniswap/permit2 - Permit2 & Universal Router introduction (Uniswap blog)
https://blog.uniswap.org/permit2-and-universal-router
- OpenZeppelin: Uniswap v4 periphery + Universal Router audit (position approval risk)
https://www.openzeppelin.com/news/uniswap-v4-periphery-and-universal-router-audit