Pump Hooks
05 / Docs

How Pump Hooks works.

A router program that wraps Pump and PumpSwap trades with a deterministic, composable hook stack. Program 91kRNewiEDrKVtUXpahsDXYiURuKQKwhCt2hnaKNfkgD.

01 Routing boundary

Pump and PumpSwap do not call external programs before or after a swap. There is no Uniswap-v4-style callback inside the venue. Pump Hooks therefore runs hooks at its own router boundary: a trader calls route_curve or route_amm to buy or sell, the router runs before-callbacks, performs the trade by CPI into Pump (bonding curve) or PumpSwap (after migration), observes the executed amounts and runs after-callbacks — all in one atomic transaction.

Bypass is possible. Anyone can trade the same token directly on Pump or PumpSwap. Those trades do not run hooks. Guards protect routed flow, and rewards accrue only to routed flow; neither can restrict the venue itself.

A hooked market is a MarketConfig PDA at ["market", mint, namespace] for an existing Pump token. Many namespaces can hook the same token; the creator's market is marked creator-owned when the namespace is the Pump creator.

02 Hook ABI

Callback wire values are frozen. Reserved values are never reused or reordered; V1 subscribes to the six trade callbacks only.

#CallbackSemantics
0BeforeBuyRouted buy, before the Pump CPI. Can deny.
1AfterBuyRouted buy, after the Pump CPI. Sees executed amounts; can still fail the transaction.
2BeforeSellRouted sell, before the Pump CPI. Can deny.
3AfterSellRouted sell, after the Pump CPI.
4BeforeSwapEither side, before the Pump CPI. Can deny.
5AfterSwapEither side, after the Pump CPI.
6BeforeLiquidityReserved (wire value 6).
7AfterLiquidityReserved (wire value 7).
8BeforeFeeActionReserved (wire value 8).
9AfterFeeActionReserved (wire value 9).
10OnGraduationReserved (wire value 10).
11OnStrategySignalReserved (wire value 11).

Each slot holds 48 bytes of config and 48 bytes of state. Modules implement one interface and return an 8-byte decision:

pub trait PumpHook {
    const MODULE_ID: u16;
    fn validate_config(config: &[u8; 48]) -> Result<(), HookError>;
    fn init_state(_config: &[u8; 48], _state: &mut [u8; 48], _now: i64) {}
    fn on_callback(env: &mut HookEnv<'_>) -> Result<HookDecision, HookError>;
}

// 8 bytes, deterministic
pub struct HookDecision { pub allow: u8, pub _pad: u8, pub reason_code: u16, pub flags: u32 }

Capabilities are granted at install and enforced by the executor, not trusted from the module:

READ_MARKETRead market config, venue reserves and the trade context
VETO_ROUTED_SWAPDeny a routed trade in a before-callback (reverts the transaction)
WRITE_HOOK_STATEPersist its 48-byte slot state on the market
WRITE_USER_STATEUpdate the trading wallet's per-market state
AWARD_POINTSCredit XP / points in the wallet's per-market ledger
TREASURY_SIGNALEmit treasury allocation intents for the strategy vault
STRATEGY_EXECUTIONBound or request vault strategy execution (TWAP, orders)
ORACLE_READRead an external oracle account (reserved; not granted in V1)
EXTERNAL_CPICall an external hook program (reserved; no permissionless CPI hooks in V1)
EMIT_SIGNALEmit or consume signed strategy signals

V1 ships 9 first-party modules (see Hooks). External programs as hooks (CPI hooks) are specified but not enabled in V1.

03 Execution order

  1. Validate the market, venue accounts and kill switch. Paused markets and disabled modules fail closed.
  2. Run before-callbacks in ascending priority. The first DENY reverts the whole transaction with HookDenied (6018).
  3. CPI into Pump or PumpSwap with the trader's own signature and slippage limit.
  4. Measure executed amounts from balance deltas, not from inputs.
  5. Run after-callbacks and active policies; commit state only for modules holding the write capability.
  6. Emit RoutedSwapEvent, HookExecutionEvent and PolicyExecutionEvents, decodable by the SDK, CLI, explorer and indexer.

Limits: 6 hook slots, 4 runtime hooks, 4 policies per market. Measured overhead on top of a direct PumpSwap buy is about 21–32k CU for 0–4 hooks (see docs/benchmarks.md).

04 Policy VM

Policies are WHEN trigger IF conditions THEN actions programs evaluated by a bounded interpreter: at most 8 conditions, 4 actions and 4 registers; no loops, no external calls, at most 10000 points per action. DENY is valid only on Before triggers and reports error 9000 + reason. Vault actions only record an intent; no vault is deployed in V1. Build policies in the Composer.

05 SENTIENT

The on-chain half of an autonomous risk engine. An off-chain engine signs a StrategySignal with Ed25519; the program checks the market-configured signer, nonce, freshness and TTL, then moves the market between NORMAL, CAUTIOUS, DEFENSIVE and CIRCUIT_BREAKER. Signals can only restrict routed trades: the engine never holds user funds, never signs for users and never trades. Every regime expires.

06 SDK

@pump-hooks/sdk builds every instruction, decodes every account and event, and validates hook configs and policies with the same rules as the program.

import { Connection, PublicKey } from "@solana/web3.js";
import { PumpHooksClient, PUMP_ALT, ModuleId, CONFIG_PRESETS } from "@pump-hooks/sdk";

const client = new PumpHooksClient(new Connection("https://api.devnet.solana.com"), {
  lookupTables: [PUMP_ALT.devnet],
});

// 1. Detect an existing Pump token: curve or PumpSwap, Token / Token-2022, reserves.
const token = await client.detect(new PublicKey(MINT));

// 2. Preview create + install + a routed buy as one simulated transaction.
const preview = await client.previewStack({
  mint: token.mint,
  authority: wallet,
  hooks: [
    { module: ModuleId.VolumeGuard, config: CONFIG_PRESETS[ModuleId.VolumeGuard] },
    { module: ModuleId.HolderXp, config: CONFIG_PRESETS[ModuleId.HolderXp] },
  ],
  quoteIn: 100_000_000n, // 0.1 SOL
});
const result = await client.simulate(preview, wallet);
result.trace?.entries; // [{ callback, moduleId, allowed, reasonCode }, ...]

// 3. Route a buy through an existing hooked market.
const market = await client.getMarket(MARKET);
const tx = await client.buy({ market, user: wallet, quoteIn: 100_000_000n });

// 4. Decode any confirmed transaction.
const trace = await client.getExecutionTrace(signature);

07 CLI

The CLI never reads your Solana CLI config; pass --url and --keypair explicitly. Write commands accept --dry-run.

pump-hooks inspect <mint> --url https://api.devnet.solana.com
pump-hooks market create <mint> --keypair ./authority.json
pump-hooks install <market> volume-guard --config maxBuyQuote=5,windowSecs=3600 --dry-run
pump-hooks policy create <market> --spec policy.json
pump-hooks simulate <market> --buy 0.1
pump-hooks tx <signature>

08 Security model

  • No token delegate and no persistent custody: users sign each routed trade; the router moves only what that trade moves.
  • Hooks cannot transfer user assets; they can deny, record, and update their own state and per-wallet ledgers.
  • Fail-closed: any hook error, bad config or unexpected venue state reverts the transaction.
  • Fees Pump Hooks does not control are never redirected. Treasury Router only describes how explicitly deposited funds would be split; the vault that would enforce it is not deployed.
  • A global kill switch can pause routing or disable individual modules; it cannot move funds.
  • Status: not audited. Not deployed on mainnet. Read docs/threat-model-pump-router.md before using real funds.