Matador Docs
Integrations

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.

policies/uniswap-basic.matador
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.

policies/uniswap-whitelist.matador
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.

policies/uniswap-slippage.matador
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.matador

Deploy & 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:

  1. Order Matters: Place cheap checks (like context.target or selector) before expensive calldata decoding or external calls.
  2. Use enforceView: If you are using an ERC-4337 validation phase, all checks must be read-only.
  3. Avoid Deep Nesting: Accessing params.recipient requires decoding the struct. If you only need to check the function selector, don't access the struct fields.

Troubleshooting

IssueCauseFix
Execution RevertedThe policy condition evaluated to false.Check transaction arguments against the policy rules. Ensure recipient is correct.
Type MismatchComparing tokenIn (address) to a number.Ensure DSL types match the ABI definitions.
Decoding ErrorThe 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.

On this page