Skip to content

Bind and configure a hook

Connect the pool to one program and create the policy at the correct point in its lifecycle.

Start with your first hook and choose its permissions. The binding and setup sequence determines when the policy can safely run.

1. Select the program and capabilities

The InitializePool account list includes hook_program and the core-derived hook_authority. InitializePoolArgs.configuration.hook_capabilities selects callbacks, profile, and bounded account permissions. The target is stored in Pool.hook_program; it is not a field embedded in a token mint.

You can create a pool without any hook: use bitmap 0, PolicyOnly, and the default public-key/System Program sentinel. Token-account ownership, user limits, and accounting checks still apply. Those core safety checks are separate from optional example policies such as allowlists.

Example hookCapabilitiesConfiguration account
Dynamic feeBeforeSwap, DynamicFeeRequired
Trading windowBeforeSwap, PolicyOnlyRequired
AllowlistBeforeSwap, PolicyOnlyRequired
Lifecycle probeAll ten, PolicyOnlyNone
Decaying launch feeBeforeSwap, DynamicFeeRequired
Reward creditsBeforeSwap and AfterSwap, PolicyOnly with explicit writes/external programRequired, with issuer and participant state

Resolve the program ID from your deployment configuration. These docs do not publish hook deployment addresses.

For the lifecycle probe, the setup fragment is:

use grapl_amm::hooks::abi::{HookCapabilities, HookProfile, CALLBACK_MASK};

let capabilities = HookCapabilities {
    bits: CALLBACK_MASK,
    profile: HookProfile::PolicyOnly,
    ..HookCapabilities::default()
};
assert!(capabilities.is_supported());

Use the matching IDL to construct InitializePoolArgs, derive PDAs, and order initialization accounts. The lifecycle example needs no policy extras.

2. Solve initialization bootstrap explicitly

The three economic policies enable only BeforeSwap. Pool initialization therefore does not call them. Once the pool exists, its creator can configure the policy before the first swap. A swap attempted without the required valid policy fails; it does not fall back to a permissive path.

The lifecycle probe can run during initialization because it needs no policy account. A custom hook that demands an already configured external policy during BeforeInitialize cannot use the existing post-pool setup sequence unchanged. Design that bootstrap before enabling the initialization bit.

Both initialization callbacks currently see the assigned candidate pool with entered = true; the setup instruction instead requires an existing pool with entered = false. See the exact timing.

3. Initialize an economic policy once

The creator sends configure_instruction(&HookPolicy) to the selected example program. That function encodes instruction bytes; the caller still constructs the Solana instruction and signs it.

Setup account orderRequired role
CreatorPool creator, signer, writable rent payer
PoolRead-only core pool bound to this hook
PolicyWritable PDA [b"policy", pool, &[0]] under the hook program
System ProgramAllocation, funding, and assignment

Read examples/hooks/shared/src/runtime/initialize.rs and the fixture's configuration_instruction builder in tests/svm_support/transactions.rs. Setup validates the program binding and policy shape. A prefunded system PDA is supported; an already initialized policy rejects.

There is no example policy-update, root-rotation, or policy-close instruction. Do not document a second configure call as an update.

4. Supply descriptors with the operation accounts

For the fee/window/allowlist examples, the configured policy is the first remaining account of the core instruction. Supply a matching HookAccountAccess with class: HookAccountClass::PolicyPda { index: 0 } and writable_phases: 0 in SwapArgs.hook_accounts. The core validates it and forwards it after the callback authority and pool.

Every remaining account needs a descriptor in exact order, including when a selected callback is disabled. The no-hook and lifecycle-probe paths use an empty vector and no extras. A stateful hook explicitly requests HookState or ExternalState writes within its configured permission ceiling; setup writability never implicitly grants callback access.

Prepare required hook state before swapping. The reward example supports either participant preparation in advance or opt-in atomic preparation funded by the hook's sponsor PDA. The callback receives no actor signing privilege. See reward credits for the provisioning flows; these accounts are not core pool defaults.

Keep policy extras out of the lifecycle probe, which expects only two callback accounts. Fee collection also rejects unexpected remaining accounts and does not dispatch a hook.

Binding and upgrade governance are different

The pool has no instruction to replace its hook binding. That fixed address does not make the code immutable: a program deployed with an upgrade authority may change at the same address. Pool/core upgrades and hook upgrades are separate governance decisions. Document their authorities and consequences before deployment.

Removal hooks can reject withdrawals. There is no implemented emergency escape or policy-bypass control. The current three BeforeSwap examples do not enable removal callbacks, but a broader hook can. Make that distinction visible to LPs.

Unlike Uniswap's address-encoded hook flags, grapl stores capabilities in pool configuration. No CREATE2 address mining applies. Continue with a concrete fee policy.