Mintvelocity

Protocol

The hook protocol

A hook is an ordinary program whose instructions are named after the callbacks. The token program calls token hooks, the DEX calls pool hooks; both sign every call and apply what the hook answers.

How a hook is called

By CPI, with an Anchor-style discriminator and Borsh arguments, signed by the caller’s ["hook-authority", hook_program] PDA, always the first account. There is one signer per hook program, so a hook accepts only its own: a signer another hook passes on vouches for nothing. A hook never moves funds itself and never gets the user’s signature; it answers, and the caller checks and applies the answer.

Callbacks

HookCallbacksCalled byFirst accounts
Token hookbefore_transfer after_transfer before_mint after_mint before_burn after_burnThe token programhook_signer, mint, source, destination, authority
Pool hookbefore_initialize after_initialize before_add_liquidity after_add_liquidity before_remove_liquidity after_remove_liquidity before_swap after_swapThe DEXhook_signer, pool, base_mint, quote_mint, actor

For a mint or a burn, the mint stands in for the side the operation lacks. Pool callbacks also carry the caller’s opaque hook data (Uniswap v4’s hookData), up to 256 bytes.

Flags

Mint.hook_flags and Pool.hook_flags subscribe a hook. A callback whose flag is off is never called, and an answer is read only when a flag allows one.

Token hook flag (u16, 8 bits used)Calls
BEFORE_TRANSFER (1)before_transfer
AFTER_TRANSFER (2)after_transfer
BEFORE_MINT (4)before_mint
AFTER_MINT (8)after_mint
BEFORE_BURN (16)before_burn
AFTER_BURN (32)after_burn

Arguments

A before_* callback, and a pool’s after_swap, may answer in its return data. A field it may not fill must be empty, zero or None, or the whole answer is refused (UnsupportedHookReturn).

pub const MAX_DELTAS: usize = 3; pub const HOOK_DATA_LEN: usize = 64;

FieldCallbackNeeds
deltas (up to 3)token before_transferTRANSFER_RETURNS_DELTA
deltas, burnpool before_swapBEFORE_SWAP_RETURNS_DELTA
deltas, burnpool after_swapAFTER_SWAP_RETURNS_DELTA
lp_fee_bpspool before_swapBEFORE_SWAP_OVERRIDES_FEE
source_hook_datatoken before_transfer, before_burnWRITES_HOOK_DATA
destination_hook_datatoken before_transfer, before_mintWRITES_HOOK_DATA

The caller reads an answer only when the flags allow it and the return data is the hook’s own, then checks:

at most three cuts, each above zero, no account named twice, the sums in checked arithmetic (DeltaTooLarge); a transfer: the cuts add up to at most the amount, each to a writable, unfrozen holding of the mint that is neither the source nor the destination - the destination gains the amount less the cuts; a swap: the cuts and the burn stay below the side’s amount, never to a vault or the trader’s own holdings - a burn needs the mint writable (MintNotWritable), and min_amount_out is checked against what the recipient actually gained; a cut credits a holding without calling the hook for it, so a hook that keeps per-holder state names only holdings it leaves out of that state.

Extra accounts

A hook that needs accounts of its own publishes them at PDA(["bordrless-hook-accounts", mint-or-pool], hook_program): literal keys, or PDAs whose seeds are literals or accounts already in the callback’s list. Clients resolve the list and append the accounts after the fixed prefix; the calling programs pass them through untouched.

A swap, in order

A pool hook’s answer is taken at two points, drawn on ink: from the input before the curve, and from the output after it. The LP fee stays on the input side; the protocol fee is always taken in the quote token.

A buy (quote in, base out)

1. The before_swap answer, from the input: each cut a transfer from the trader’s input holding, then the burn. 2. The rest reaches the quote vault; the LP and protocol fees are charged on it; the curve runs on what remains. 3. The after_swap answer, from the curve’s output: each cut from the output vault (the pool signs), then the burn. 4. The rest goes to the recipient’s holding.

A sell (base in, quote out)

1. The before_swap answer, from the input. On a launch pool: the burn. 2. The rest reaches the base vault; the LP fee is charged on it; the curve gives the output in the quote token. 3. The protocol fee is taken from that output, kept apart from the reserves. 4. The after_swap answer, from what is left. On a launch pool: the creator and holder fees. 5. The rest goes to the recipient’s holding.

Who is paid

Each pool copies its fee model (fee_model) from the DEX config when it is created and keeps it for life. A launch pool is a curve the launchpad creates as its own hook; a curve any other program opens pays the flat rate.

PoolProtocol feeLP fee
Ordinary pool1% of the quote, flatStays with the pool’s liquidity
Launch pool25% of what the pool’s hooks cut (creator fee, holder rewards, a creator’s own hook), in SOL. A base-side cut is valued at the swap’s price; burns are not shared.Mintvelocity’s, all of it, in SOL

A launch whose rules collect nothing pays the LP fee alone. Anyone may send collect_protocol_fees_sol: it unwraps a launch pool’s fees through the bridge and pays them to the fee collector as SOL.