patcha
xgithub
wallet

documentation

The spec.

Architecture, hook semantics, the security model, the on-chain executor, and the API reference.

Architecture

Patcha is a monorepo: a Next.js web app, a FastAPI backend (hook simulation + DEX adapters), a Rust hook runtime, an Anchor hook-executor program, a TypeScript SDK, a CLI, and a VS Code extension.

Requests flow from the web app to the backend through same-origin /api/* route handlers (no cross-origin calls). The Anchor program enforces installed hooks via PDA-derived accounts on Orca Whirlpools, Raydium CLMM, and Meteora DLMM pools.

Components

Package Purpose
apps/web Next.js Hook Designer, marketplace, devtools, docs
service/ FastAPI backend (hook simulate, DAS proxy, marketplace listing)
packages/anchor-program Anchor 0.31 patcha_hook_executor program (mainnet EPcW7e8…rNRa)
packages/hook-runtime Pure-Rust hook eval, shared off-chain (backtest) and on-chain
packages/hook-library Six standard hooks: schemas + metadata
packages/sdk-ts TypeScript SDK — PatchaClient for register/install/trigger
packages/cli patcha-cliinit / create / list / simulate / install / deploy
packages/vscode-extension VS Code Designer webview
packages/whirlpools-adapter Orca Whirlpools wrapper (@orca-so/whirlpools-sdk)
packages/raydium-adapter Raydium CLMM wrapper (@raydium-io/raydium-sdk-v2)
packages/meteora-adapter Meteora DLMM wrapper (@meteora-ag/dlmm)

DEX adapter boundary

Each CLMM venue exposes its own pool schema and quote function. Patcha normalizes them via a Rust adapter (src/adapters/{orca,raydium,meteora}.rs) that maps a venue-specific lifecycle event into a neutral TriggerCtx. The eight standard hooks consume that context unchanged across venues.

Venue Adapter DEX tag Mainnet program
Orca Whirlpools orca.rs 0 whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc
Raydium CLMM raydium.rs 1 CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK
Meteora DLMM meteora.rs 2 LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo

Meteora DLMM uses discrete bins instead of ticks; the LbPair activeId is the analogue of a CLMM tick and is what the adapter maps to TriggerCtx.tick for DynamicFee, RangeOrder, and AntiMEV to consume identically.

On-chain executor

Program ID: EPcW7e8RxBNPpQK2XKoKG9maWH6QvmU3ejxifoU5rNRa (Solana mainnet).

Instructions (Anchor 0.31):

  • initialize_registry — one-time setup of the hook registry PDA
  • register_hook(slug, kind, code_hash) — register a hook in the marketplace
  • install_hook(pool, slug, dex, params_blob) — install on a pool; idempotent (re-install with the same (pool, slug) updates params without bumping the install counter)
  • update_params(params_blob) — installer-only param refresh
  • trigger_hook(callback, ...) — emit HookTriggered event
  • uninstall_hook — deactivate without closing PDAs

All eight standard hooks are registered on mainnet (dynamic-fee, time-lock, whitelist-gate, range-order, anti-mev, kyc-gate, price-impact-cap, jit-defense). The executor accepts all three DEX tags (0=orca, 1=raydium, 2=meteora).

Requests flow

browser → /market | /designer | /devtools (Next.js)
       └─ /api/hook/list      → builtin hook library (server-side)
       └─ /api/hook/simulate  → service/ FastAPI → byte-identical Rust fee fn
       └─ /api/das/asset/…    → Helius DAS proxy

CLI → service/ /hook/simulate   (offchain backtest)
   → mainnet RPC → patcha_hook_executor  (onchain install / register / trigger)

SDK → mainnet RPC → patcha_hook_executor

The off-chain backtest and the on-chain executor compute the fee from a byte-identical function: the number printed by patcha simulate is the exact number a live HookTriggered event would emit.

Hooks specification

Patcha maps Uniswap v4's ten hook callbacks onto the Solana CLMM lifecycle (Orca Whirlpools, Raydium CLMM, and Meteora DLMM). A hook is a small module installed against a pool; the on-chain executor invokes it at the matching point in the pool's lifecycle.

DEX venues

The executor stamps each lifecycle event with a venue tag, so the same six builtin hooks compose with any of the three venues.

Venue DEX tag Mainnet program
Orca Whirlpools 0 whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc
Raydium CLMM 1 CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK
Meteora DLMM 2 LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo

Meteora DLMM uses discrete bins; the LbPair activeId is the analogue of a CLMM tick and is what DynamicFee, RangeOrder, and AntiMEV consume.

Uniswap v4 callbacks → Solana CLMM trigger

Uniswap v4 callback Patcha CLMM trigger
beforeInitialize before pool/position init
afterInitialize after pool/position init
beforeAddLiquidity before increaseLiquidity
afterAddLiquidity after increaseLiquidity
beforeRemoveLiquidity before decreaseLiquidity
afterRemoveLiquidity after decreaseLiquidity
beforeSwap before swap CPI
afterSwap after swap CPI
beforeDonate before fee donation
afterDonate after fee donation

Builtin hooks

Hook Category Reacts on
Dynamic Fee fees beforeSwap, afterSwap
TimeLock timing beforeAddLiquidity, beforeRemoveLiquidity
WhitelistGate gating beforeSwap, beforeAddLiquidity
RangeOrder range afterSwap
AntiMEV mev beforeSwap, afterSwap
KYCGate kyc beforeSwap, beforeAddLiquidity
PriceImpactCap gating beforeSwap
JIT-Defense mev beforeSwap, beforeAddLiquidity, beforeRemoveLiquidity

PriceImpactCap rejects swaps whose estimated price impact exceeds a per-swap cap (LP-owned slippage ceiling). JIT-Defense rejects same-block add-swap-remove patterns from one wallet (just-in-time LP attack defense). Both ride the same install_hook_burning path the other six do; see token-economics.md for the holder-tier burn table.

The eight builtin hooks and their parameter schemas are shared across the web designer, SDK, CLI, and VS Code extension from a single hook-library package, so all surfaces agree on slugs, parameters, and on-chain encoding.

Reference: Uniswap v4 hooks whitepaper (Uniswap Labs, 2024).

Security notes

Core principles enforced across the on-chain program and the backend.

Anchor PDA + account constraints

  • Every privileged account relationship is enforced with has_one and PDA seed derivation rather than runtime address comparison.
  • PDA seeds:
    • ["hook_registry"] — global registry
    • ["hook", slug] — per-hook metadata
    • ["installation", pool, slug] — per-pool install (the LP authority signs)
    • ["params", installation] — hook parameters

Secret handling

  • Secrets (Helius API key / RPC URL, database and cache URLs, keypair paths) are server-only and never carry a NEXT_PUBLIC_ prefix, so they are never inlined into the client bundle.
  • The wallet adapter uses a public RPC only.
  • Helius DAS calls are proxied server-side via /api/das/*; the key stays on the server.

CORS

  • The backend allows a fixed list of explicit origins (no wildcard) with credentials enabled. The web app talks to its own /api/* route handlers, so browser requests are same-origin.

PATCHA token economics

The install_hook_burning instruction on the mainnet executor burns a tier-derived amount of PATCHA from the installer's wallet on every call. The burn rate depends on the installer's share of total PATCHA_MINT supply — holding is the discount.

Burn-rate table

Tier Holder share of supply Burn per install_hook_burning
T1 ≥ 2.0% 100 PATCHA
T2 ≥ 1.0% 300 PATCHA
T3 ≥ 0.5% 1,000 PATCHA
T4 ≥ 0.1% 5,000 PATCHA
T5 < 0.1% 50,000 PATCHA

The table is a pure function over public on-chain state — the same calculate_burn(supply, holder_balance) runs in the executor program, in the SDK, and in the CLI's patcha tiers command, so on-chain and off-chain results agree by construction.

Why this shape

  • T1 (≥ 2.0%) — whale rate. ~$520 of PATCHA committed today buys effectively-free installs (100 PATCHA ≈ $0.003 per call). The discount rewards real conviction, not speculative passers-through.
  • T2 / T3 / T4 — graded holder rates. As your share of supply drops, the per-install burn rises. Each tier was sized so a tier-N holder can run ~200–5,000 installs before their balance falls into the next bracket.
  • T5 (< 0.1%) — non-holder rate. The wallet has to acquire ≥ 50,000 PATCHA to install one hook with the burn path. After the install the balance is roughly zero again, which forces a buy → install → buy loop. That loop is the supply-side pressure of the design.

Hard invariants

  • PATCHA_MINT is a constant in the executor (AKSYuSqinmiYt5pSQxsfb4m97seTP37s32TSs9Lpump). The burn instruction cannot be retargeted at a different mint.
  • The mint's mint authority is null. No party — including the dev — can issue new PATCHA. Burn is therefore a permanent supply reduction, not a cosmetic transfer.
  • The mint's freeze authority is null. No party can freeze a holder's token account; the discount applies to whoever holds.
  • No admin switch. INSTALL_BURN_AMOUNT is not a const that an admin can flip; the burn amount is derived per call by calculate_burn and the only knob — the tier table itself — is a const. Changing it requires a public program upgrade transaction that any observer can see on Solscan.
  • Insufficient balance reverts with PatchaError::InsufficientPatcha (#6011). The CLI surfaces the exact shortfall (need / have / short) and points the user at the mint address or the --no-burn escape hatch.
  • Legacy install_hook is preserved. Older integrations can keep calling the non-burning instruction via patcha install --no-burn so the upgrade is non-breaking.

Verifying your tier

npm i -g patcha-cli@latest
patcha tiers --wallet <your-wallet-pubkey>

The command reads the live PATCHA_MINT supply and the wallet's Token-2022 ATA balance, then prints the wallet's tier, the burn amount per install, and how many installs remain before the next tier change.

Verifying the burn on chain

Every install_hook_burning call emits a PatchaBurned event carrying the burn amount, the tier, the holder balance before, and the supply before. You can confirm a specific transaction on Solscan and reconcile the supply delta on chain:

spl-token display AKSYuSqinmiYt5pSQxsfb4m97seTP37s32TSs9Lpump
solana account AKSYuSqinmiYt5pSQxsfb4m97seTP37s32TSs9Lpump --output json

mintAuthority: null and freezeAuthority: null are visible in both outputs, so the "no new mints / no freezes" claim is independently verifiable.

patcha-cli quickstart

patcha-cli is the command-line entry point to the Patcha hook executor on Solana. It scaffolds hook projects, backtests builtins against live pools, installs hooks on chain (with the holder-tier PATCHA burn), and surfaces the burn-tier table.

Prerequisites

The CLI is a thin client on top of Solana and your own keypair, so you need the Solana toolchain even though patcha-cli itself is one npm install.

  1. Node 20 or higher. node --version should print v20.x or above. Get it from nodejs.org or with nvm install 20.
  2. Solana CLI installed and on your PATH. Install with sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" and verify with solana --version. Without it, the CLI can still scaffold and simulate, but cannot sign or send transactions.
  3. A Solana keypair file. solana-keygen new creates one at ~/.config/solana/id.json by default. The CLI reads from that path unless you pass --keypair <path>.
  4. Some SOL in that wallet for tx fees. A few hundredths of a SOL is enough for many installs (~0.000005 SOL per signature). Top up via solana airdrop 1 on devnet, or a transfer on mainnet.
  5. (Burn path only) PATCHA in that same wallet, ≥ the burn amount for your tier. See token-economics.md for the exact tier table. patcha install --no-burn skips this requirement and uses the legacy non-burning instruction.

The burn never auto-fires from holding alone — it only runs when you call patcha install ... and sign the transaction yourself, and it burns only from that same signer wallet. PATCHA sitting in a wallet is never touched otherwise.

Install

npm i -g patcha-cli@latest
patcha --version

Or, if you'd rather pin to the tarball mirrored from the site:

npm i -g https://patcha.fi/downloads/patcha-cli-latest.tgz

First commands

# 1. browse the hook marketplace
patcha list

# 2. quote your wallet's burn tier (read-only, no signature)
patcha tiers --wallet <your-wallet-pubkey>

# 3. backtest a builtin hook against a real pool (no wallet needed)
patcha simulate dynamic-fee --pool <pool-addr> --dex orca

# 4. install a hook on a real pool (mainnet tx; signs from your keypair)
patcha install dynamic-fee --pool <pool-addr> --dex orca

# 5. scaffold your own hook project
patcha init my-hook && cd my-hook && patcha simulate hook.toml

Global flags

Every command accepts:

  • --cluster mainnet|devnet|testnet|localnet (default mainnet)
  • --rpc <url> (overrides --cluster and PATCHA_RPC)
  • --wallet <path> (default ~/.config/solana/id.json)

patcha install additionally accepts:

  • --pool <addr> (required)
  • --dex orca|raydium|meteora (default orca)
  • --keypair <path> (overrides --wallet for this command only)
  • --program <id> (override the executor program id; rarely needed)
  • --no-burn (use the legacy install_hook ix with no PATCHA burn)

patcha tiers accepts:

  • --wallet <addr|path> — pubkey to quote (or a keypair file)
  • --rpc <url> / --cluster <cluster> to read from a non-default RPC

Common errors

  • insufficient PATCHA for tier N: need X, wallet holds Y, short Z — your signer wallet doesn't have enough PATCHA to cover the tier's burn. Buy the shortfall, or pass --no-burn to use the legacy free path.
  • keypair not found at <path>solana-keygen new to create one, or pass --keypair <path> / set ANCHOR_WALLET.
  • Insufficient funds for rent / transaction simulation failed — add SOL to the signer wallet.
  • failed to get recent blockhash on patcha-cli before 0.3.x — the bundled fetch shim was incompatible with Undici; upgrade to patcha-cli@0.3.1 or later.

Where to go next