AtomicLP

Privacy-preserving LP agent: EIP-712 intents → Merkle batching → atomic hook execution. Zero MEV.

AtomicLP

Created At

HackMoney 2026

Project Description

Privacy-preserving batched LP position management for Uniswap v4


The MEV Problem

When you add liquidity to a Uniswap pool, your transaction is visible in the mempool before it's mined. MEV (Maximal Extractable Value) bots exploit this:

  1. See your pending LP transaction in the mempool
  2. Front-run by adding their own liquidity first
  3. Sandwich attack by manipulating prices around your transaction
  4. Extract value from your position

LPs lose an estimated $500M+ annually to MEV extraction.


How PrivBatch Solves This

The Privacy Pipeline

| Step | What Happens | Visibility | |------|-------------|------------| | 1. Intent Creation | User fills LP parameters in browser | Private (browser only) | | 2. EIP-712 Signing | MetaMask signs structured typed data | Private (signature proves consent) | | 3. Agent Queue | Signed intent sent to off-chain agent | Private (no on-chain footprint) | | 4. Merkle Batching | Agent groups intents into Merkle tree | Private (individual data hidden in tree) | | 5. Batch Execution | Single tx sends root + proofs to hook | Public (but all-or-nothing atomic) | | 6. Hook Verification | Hook verifies proofs + signatures | Public (positions minted atomically) |

Key insight: By the time data hits the blockchain, all positions are already minted atomically in a single transaction. There's no window for MEV bots to front-run.


Security Model: Agent API vs Mempool

Q: Can't bots just monitor the agent API instead of the mempool?

In the demo, the /intents/pending endpoint is public so judges can see the queue. However, this is fundamentally different from mempool monitoring:

| Attack Vector | Mempool | PrivBatch Agent API | |---------------|---------|---------------------| | What bots see | Pending transactions | Intent data (not txs) | | Can bot front-run? | Yes (same block) | No (different blocks) | | Timing control | User's tx is public | Agent controls batch timing | | Execution | Individual txs | Atomic batch (all-or-nothing) |

Why API visibility doesn't enable MEV:

  1. Intents aren't transactions - Bots can't insert them into a block
  2. Agent controls timing - Bot doesn't know when batch will execute
  3. Atomic execution - All positions mint in one tx, no sandwich window
  4. Different blocks - By the time bot reacts, batch may already be mined

Production hardening (not in demo):

  • Add API authentication (JWT/API keys)
  • Remove /intents/pending endpoint
  • Rate limiting and IP allowlisting
  • Private agent deployment (VPN/internal network)

The demo API is intentionally open for hackathon demonstration purposes.


Architecture

┌──────────────────────────────────────────────────────────────────┐
│                        Frontend (Next.js)                        │
│  Dashboard | Submit Intent | Mint Tokens | Monitor | Privacy     │
└─────────────────────────────┬────────────────────────────────────┘
                              │ EIP-712 signed intents (HTTP)
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│                     Agent (Python / FastAPI)                      │
│  Collector → Optimizer → Merkle Tree Builder → Batch Submitter   │
└─────────────────────────────┬────────────────────────────────────┘
                              │ Single batch transaction
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│                    Smart Contracts (Sepolia)                      │
│  BatchExecutor → PositionManager → PoolManager + PrivBatchHook   │
└──────────────────────────────────────────────────────────────────┘

Deployed Contracts (Sepolia)

| Contract | Address | |----------|---------| | PrivBatchHook | 0x08ee384c6AbA8926657E2f10dFeeE53a91Aa4e00 | | BatchExecutor | 0x79dcDc67710C70be8Ef52e67C8295Fd0dA8A5722 | | CommitContract | 0x5f4E461b847fCB857639D1Ec7277485286b7613F | | TestTokenA (TTA) | 0x486C739A8A219026B6AB13aFf557c827Db4E267e | | TestTokenB (TTB) | 0xfB6458d361Bd6F428d8568b0A2828603e89f1c4E |


Technical Details

EIP-712 Intent Signing

Users sign structured typed data in MetaMask:

Domain: {
  name: "PrivBatch",
  version: "1",
  chainId: 11155111,
  verifyingContract: <hookAddress>
}

Types:
  PoolKey(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks)
  LPIntent(address user, PoolKey pool, int24 tickLower, int24 tickUpper, uint256 amount, uint256 nonce, uint256 deadline)

Merkle Batching

The agent:

  1. Collects signed intents
  2. Computes leaf: keccak256(abi.encodePacked(user, tickLower, tickUpper, amount, nonce, deadline, signature))
  3. Builds Merkle tree (OpenZeppelin-compatible sorted pairs)
  4. Submits root + proofs in single transaction

Hook Verification

PrivBatchHook hooks into beforeAddLiquidity:

  1. Verify Merkle proof against batch root
  2. Recover signer from EIP-712 signature
  3. Confirm signer matches intent.user
  4. If all pass → mint positions atomically

Hackathon Tracks

Privacy DeFi Track

  • Commit-reveal scheme hides intent data until execution
  • Merkle batching obscures individual positions in the tree
  • EIP-712 signatures prove consent without on-chain exposure
  • Atomic execution eliminates sandwich attack window

Agentic Finance Track

  • Autonomous agent collects, optimizes, and executes batches
  • Adaptive k-multiplier learns optimal tick ranges from IL history
  • Gas optimization via batching (1 tx vs N individual txs)
  • 24/7 operation without manual intervention

How it's Made

Three-Part Architecture Built with Next.js frontend, Python FastAPI agent, and Solidity smart contracts on Sepolia. The core innovation: users sign intents off-chain, agent batches them into Merkle trees, and a Uniswap v4 hook verifies everything atomically. Frontend (Next.js 14 + wagmi v2) Implemented EIP-712 typed data signing using wagmi's useSignTypedData hook. Users sign structured intent data (pool, tick range, amount, nonce, deadline) without submitting blockchain transactions. RainbowKit handles wallet connections. Contract interactions use viem with auto-generated ABIs from Foundry artifacts. Real-time batch monitoring via WebSocket connections to the agent API. Hacky part: Built a custom hookData encoder that packs dynamic-length Merkle proofs into Uniswap v4's bytes parameter. Had to manually serialize proof arrays to match Solidity's abi.decode expectations. Agent (Python 3.10 + FastAPI) Four-module autonomous pipeline:

Collector - Validates EIP-712 signatures on receipt using eth_account.messages and stores intents in-memory with UUID keys Optimizer - Groups intents by pool for gas efficiency, implements adaptive k-multiplier for tick range suggestions based on historical IL data Merkle Builder - Constructs OpenZeppelin-compatible trees with keccak256(abi.encodePacked(...)) leaves. Had to manually implement abi.encodePacked in Python using eth_abi.packed.encode_packed with careful uint256 padding Submitter - Triggers on 5 intents OR 2 minutes, calls BatchExecutor.execute() with dynamic gas estimation via Alchemy RPC

Hacky part: Getting Python's Merkle tree to match Solidity's MerkleProof.sol required sorted pair hashing (smaller hash first) and bit-perfect leaf encoding. Debugged by comparing intermediate hashes between Python and a Solidity reference contract. Smart Contracts (Solidity 0.8.26 + Foundry) PrivBatchHook - Custom Uniswap v4 hook implementing beforeAddLiquidity. Verifies Merkle proofs using OpenZeppelin's MerkleProof.verify(), recovers signers from EIP-712 signatures with ECDSA.recover(), checks nonces (prevents replay), validates deadlines. The hook intercepts every addLiquidity call and only allows positions with valid proofs to mint. BatchExecutor - Entry point contract receiving Merkle root, intents[], proofs[], and signatures[]. Loops through intents calling PositionManager.modifyLiquidities() with custom-encoded hookData containing the proof for each intent. Hacky part: Uniswap v4's hookData has a 24KB limit. Compressed proofs by only sending unique siblings in the Merkle path, not the full proof tree. The hook reconstructs the full verification path on-chain using the root. Key Technical Challenges Solved EIP-712 Mismatch: Frontend signatures initially failed on-chain verification. Issue was domain separator calculation - fixed by ensuring verifyingContract field matched between TypeScript and Solidity. Merkle Proof Failures: Python builder used abi.encode (adds padding) instead of abi.encodePacked (packed encoding). Switched to packed encoding and verified byte-by-byte equivalence. Gas Optimization: Reduced batch execution from 450k to 320k gas (5 intents) by batching same-pool positions, using calldata over memory, and removing redundant checks. Tech Stack Frontend: Next.js 14, wagmi v2, viem, RainbowKit, TailwindCSS Backend: Python 3.10, FastAPI, Web3.py, APScheduler, eth_abi Contracts: Solidity 0.8.26, Foundry, OpenZeppelin, Uniswap v4 Infrastructure: Sepolia testnet, Alchemy RPC, Vercel, Railway Why It Works Separating intent expression (EIP-712 signatures) from execution (batched transactions) eliminates mempool exposure. Merkle trees hide individual positions until atomic execution. Uniswap v4 hooks enable trustless verification at minting time without extra token approvals. MEV resistance through architecture, not complexity.

background image mobile

Join the mailing list

Get the latest news and updates

AtomicLP | ETHGlobal