Docs

Hooks

Bounds

Every limit Socket puts on a hook, what happens when a hook crosses it, and the one it cannot enforce.

A pool's bounds are written at creation and checked by Socket on every call. They don't depend on the hook behaving well.

The limits#

BoundSet per poolRangeCrossing it
Max feemax_fee_ppmbase fee to 100,000 ppm (10%)6012 HookFeeCap
Surcharge capmax_delta_bps0 to 1,000 bps (10%) of the swap's CLMM input6013 HookDeltaCap
Hook computehook_budgetany positive number of compute units6014 HookComputeCap
Extra accountsthe account listat most 8, literal addresses6010 InvalidExtra, 6016 ForbiddenAlias
Reply shape—≤ 64 bytes, exact Borsh, version 1, same phase, from the hook program6011 InvalidReply
Reentry—the pool is locked while its hook runs6003 Locked

Every crossing fails the whole transaction. Program state, the hook's own writes and token transfers revert together.

Fees#

Before swap, a hook with fee override (16) may reply with fee_ppm. Socket doesn't clamp it: a value above the pool's max fee fails the swap with 6012. The built-in dynamic-fee hook reads max_fee_ppm from the call and never asks for more.

In a pool without fee override, a reply that sets any fee fails the swap with 6012 as well, so those pools always charge their base fee. The same goes for a surcharge from a pool without permission 32: 6013.

Surcharges#

After swap, a hook with input surcharge (32) may reply with input_delta, an extra amount of the input token. It is bounded by floor(amount_in × max_delta_bps / 10,000), where amount_in is the swap's CLMM input before the surcharge.

The swapper consents to it explicitly:

  • Exact input: the swap carries max_total_input, the most the user can be debited including the surcharge.
  • Exact output: the threshold is the most the user will pay, surcharge included.

A surcharge is recorded as owed to the hook (hook_owed_a or hook_owed_b) and stays in the vault, apart from LP principal and fees. Only the pool's creator can withdraw it, with CollectHookFees, and only up to the recorded amount. Negative surcharges (rebates) are not supported.

Compute#

Socket reads the remaining compute before and after each hook call and fails the transaction if the call used more than the pool's hook_budget. The measurement includes the cost of the call itself.

Accounts#

A hook's extra accounts are listed literally when the pool is created: address, owning program and write access, at most eight. Before any call, Socket checks that the accounts passed match the list exactly, that no extra account aliases a core account or another extra, and that none is a signer. A hook can't ask for a different account at run time; dynamic seeds are not supported.

Call depth#

Router → Socket → hook → anything the hook calls uses most of Solana's cross-program call depth. A hook that calls other programs leaves less room for a router above it, and it can't call back into the pool it serves. Keep hooks shallow.

Failure is the safe default#

A hook that panics, returns an error, overspends compute or replies out of bounds doesn't leave a half-done swap. Nothing is charged and nothing moves; the user loses only the network fee. A router's simulation fails on that pool and it can quote another.

That is also how hooks refuse things on purpose. The range order hook rejects swaps that would undo a filled order the same way.