ZK-Permit

Documentation

ZK-Permit is a permissionless execution firewall for wallets and agents on Base. Users sign intents (EIP-712) bound to their current on-chain policy. The protocol enforces the constraints on-chain, then executes the target call.

What ZK-Permit enables

  • Permissionless execution: anyone can submit, but only valid user-signed intents execute.
  • Wallet-level controls: deadline horizon, calldata size cap, ETH value cap, and optional swap/day limit.
  • DeFi guardrails: router + selector allowlisting and router-deadline sanity checks (Uni V2/V3, Aerodrome-style routers).
  • Auditable activity via on-chain events (and optional indexing).

How it works (high level)

  1. The user sets a policy on-chain (PolicyManager). This produces a current policyHash.
  2. The user signs an EIP-712 intent that includes policyHash, nonce, deadline, dataHash, and actionType.
  3. Anyone can submit executeWithUserSig (in v1.1 the user pays gas). The Checkpoint contract verifies signature + policyHash, enforces policy/allowlists, then executes the target call.

Quickstart (for users)

  1. Open the console and connect your wallet.
  2. Go to Policies and configure safe defaults:
    • maxDeadlineSeconds: keep tight (e.g. 600–1800 seconds)
    • maxCallDataBytes: cap calldata (e.g. 2–8 KB)
    • maxSwapsPerDay: optional rate limit for swaps
  3. Go to Intent Builder, choose an action (ETH transfer / Approve / Swap), preview selector + dataHash, then click Sign & execute.
  4. Verify in Activity that the execution was recorded (indexed DB if available, otherwise RPC logs).

Intent format (EIP-712)

The user signs an intent including dataHash=keccak256(calldata), a replay protection nonce, a deadline, and the current policyHash. Execution requires the same on-chain policy hash (prevents “sign once, then policy changes silently”).

  • actionType=0: ETH transfer (requires empty calldata)
  • actionType=1: ERC-20 approve(spender, amount)
  • actionType=2: router swap call (router + selector allowlist + deadline check)

Integrators (wallets / agents / apps)

Integrations typically follow the same pattern as the console, but in your own UI:

  1. Read the user policy + current policyHash from chain.
  2. Build calldata for the target action (approve / swap).
  3. Compute dataHash and build EIP-712 typed data.
  4. Ask the user to sign.
  5. Submit executeWithUserSig.

SDK helpers

This repo includes an SDK with helpers for typed data and calldata builders.

  • getZKPermitTypedData(): domain + types + message (EIP-712)
  • Builders: buildApproveIntent, buildUniswapV2SwapIntent, buildUniswapV3ExactInputSingleIntent, buildAerodromeSwapIntent
  • validateIntentAgainstPolicy(): client-side sanity checks

MCP for Agents (plug-and-play)

Connect an AI agent to the live ZK-Permit deployment on Base. The MCP server reads policy + nonce, builds intents, generates EIP-712 typed data, checks allowlists, and (optionally) submits executeWithUserSig.

Prerequisites
  • Node.js 20+ (ships with npm and npx)
  • Cursor, Claude Code, Claude Desktop, or any MCP-compatible client

Config (same block everywhere)

Package: zkpermit-mcp on npm. Paste this JSON — npx downloads and runs the server automatically.

{
  "mcpServers": {
    "zkpermit": {
      "command": "npx",
      "args": ["-y", "zkpermit-mcp"],
      "env": {
        "ZKPERMIT_RPC_URL": "https://mainnet.base.org",
        "ZKPERMIT_CHECKPOINT_ADDRESS": "0x6209C1d46832726bb95B344F33ea643343fDD55A"
      }
    }
  }
}

Optional — add inside env only if your agent should broadcast txs (never share real keys): "ZKPERMIT_SUBMITTER_PRIVATE_KEY": "0x..."

Where to paste it

  • Cursor — Settings → MCP, or ~/.cursor/mcp.json
  • Claude Code — top-level mcpServers in ~/.claude.json (all projects), or .mcp.json in a project root. Restart the session, then run /mcp to confirm it is connected.
  • Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the equivalent config file on your OS. Restart the app.

Typical agent workflow

  1. zkpermit_get_policy + zkpermit_get_nonce for the user wallet
  2. zkpermit_check_allowlist before building a swap/approve
  3. zkpermit_build_intentzkpermit_get_typed_data (with auto: true)
  4. User signs the EIP-712 payload in their wallet
  5. Optional: zkpermit_submit_execute if a submitter key is configured

Available tools

  • zkpermit_get_policy — read on-chain policy + policyHash
  • zkpermit_get_nonce — current replay-protection nonce
  • zkpermit_build_intent — ETH / approve / UniV2 / UniV3 / Aerodrome
  • zkpermit_get_typed_data — EIP-712 typed data (auto=true fetches nonce + policyHash)
  • zkpermit_check_allowlist — router / selector / token / spender checks
  • zkpermit_submit_execute — optional, requires ZKPERMIT_SUBMITTER_PRIVATE_KEY

Activity

Every execution emits a structured Executed event including the policyHash, selector, and dataHash. Dashboards can index these events for pagination and analytics.

Security checklist

  • Keep allowArbitraryCalldata disabled for production wallets.
  • Use a tight maxDeadlineSeconds (e.g. 10–30 minutes).
  • Set maxCallDataBytes to a safe cap to prevent griefing.
  • For swaps: allowlist only known routers and selectors you explicitly support.
  • For approvals: allowlist token + spender pairs and cap maxApproveAmount.
Console: /app. This page explains the protocol and how users/integrators use the live deployment.