Uniswap V3 Integration
Comprehensive guide to securing Uniswap V3 interactions with Matador.
Automating liquidity provision or token swaps via smart accounts introduces significant risk. Matador allows you to define granular permission policies for Uniswap V3, ensuring that automated agents or delegated keys cannot drain funds or execute unfavorable trades.
Security Architecture
graph TD
User[Automated Agent] -->|Propose Swap| Safe[Smart Account]
Safe -->|Check Policy| Matador[Matador Interpreter]
Matador -->|Decode Calldata| Rules{Policy Rules}
Rules -- Pass --> Uniswap[Uniswap Router]
Rules -- Fail --> Revert[Revert Transaction]Permission Patterns
1. Basic Swap Protection
The most critical check is ensuring the recipient of the swap is the smart account itself. This prevents an attacker from using your funds to swap tokens into their own wallet.
import "abis/UniswapV3Router.json" as Uniswap;
permission SafeSwap -> 1.0.0 {
parameters: {
router: address
}
when: {
all {
// 1. Target must be the official router
context.target == parameters.router,
// 2. Must be an exactInputSingle call
Uniswap.exactInputSingle,
// 3. Recipient must be the account itself (self-custody)
context.args.params.recipient == context.account
}
}
}2. Token Whitelisting
Restrict the input and output tokens to a known list. This prevents "dusting" attacks or unauthorized trading of volatile assets.
permission TokenWhitelist -> 1.0.0 {
parameters: {
allowedTokens: address[]
}
when: {
all {
context.args.params.tokenIn in parameters.allowedTokens,
context.args.params.tokenOut in parameters.allowedTokens
}
}
}3. Slippage Protection (Oracle Integration)
Prevent malicious execution (e.g., sandwich attacks) by enforcing a minimum output amount derived from an on-chain oracle.
Oracle Pattern
This pattern uses the STATE_VARIABLE_CHECK opcode to read from a Chainlink oracle dynamically during execution.
import "abis/Chainlink.json" as Oracle;
permission SlippageGuard -> 1.0.0 {
parameters: {
priceFeed: Oracle,
slippageToleranceBps: uint256 // e.g. 50 = 0.5%
}
when: {
// amountOutMin >= (amountIn * Price) * (1 - slippage)
context.args.params.amountOutMinimum >=
(context.args.params.amountIn * Oracle.latestAnswer()) / 1e8 *
(10000 - parameters.slippageToleranceBps) / 10000
}
}Integration Tutorial
Setup Project
Create a new directory for your policies and download the Uniswap ABI.
mkdir my-defi-bot
cd my-defi-bot
npm install -D matador-policy-cli
mkdir abis
# Download SwapRouter02.json to ./abis/Write the Policy
Create policies/swap.matador with the content below. This combines target checking, selector checking, and recipient validation.
import "abis/SwapRouter02.json" as Router;
permission SwapPolicy -> 1.0.0 {
parameters: {
router: address,
usdc: address,
weth: address
}
when: {
all {
context.target == parameters.router,
Router.exactInputSingle,
context.args.params.recipient == context.account,
// Only allow swapping USDC -> WETH
context.args.params.tokenIn == parameters.usdc,
context.args.params.tokenOut == parameters.weth
}
}
}Compile
Compile the policy to generate the bytecode.
npx matador-policy-cli compile policies/swap.matadorDeploy & Provision
Use a script to deploy the policy to your smart account.
const policy = require('./policies/swap.json');
// Encode parameters (Router, USDC, WETH addresses)
// In production, these are part of the 'context' if hardcoded,
// or passed as 'args' if the policy is generic.
// Matador compiler handles this mapping.
await account.grantPermission(policyId, policy.hexData);Gas Optimization
Uniswap structs are large. To minimize gas costs:
- Order Matters: Place cheap checks (like
context.targetorselector) before expensive calldata decoding or external calls. - Use
enforceView: If you are using an ERC-4337 validation phase, all checks must be read-only. - Avoid Deep Nesting: Accessing
params.recipientrequires decoding the struct. If you only need to check the function selector, don't access the struct fields.
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
Execution Reverted | The policy condition evaluated to false. | Check transaction arguments against the policy rules. Ensure recipient is correct. |
Type Mismatch | Comparing tokenIn (address) to a number. | Ensure DSL types match the ABI definitions. |
Decoding Error | The transaction data does not match exactInputSingle. | Ensure the bot is calling the exact function defined in the policy. exactInput (multi-hop) has a different struct. |