Skip to content

Choose phases, profiles, and account permissions

Request only the callbacks and state access your hook implements.

Prerequisite: identify your phase in the callback catalog. The public contract is programs/grapl-amm/HOOK_FRAMEWORK.md; implementation types live in src/hooks/abi/, with permission and account checks in src/hooks/{permissions,accounts}.rs.

Select callbacks and reply behavior

Each phase has a bit from HookPhase::bit(). The complete mask is 1023; BeforeSwap alone is 64, and both swap phases are 192. These are grapl values, not Uniswap address flags.

This configuration fragment enables policy-only swap callbacks with the default read-only account policy:

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

let capabilities = HookCapabilities {
    bits: HookPhase::BeforeSwap.bit() | HookPhase::AfterSwap.bit(),
    profile: HookProfile::PolicyOnly,
    ..HookCapabilities::default()
};
assert!(capabilities.is_supported());
assert_eq!(capabilities.bits, 192);

is_supported checks callback/profile compatibility; it is not the complete validation of account permissions. The fee, window, and allowlist examples require exactly BeforeSwap. A composed stateful example can select a broader set, but each selected phase needs a valid implementation.

ProfileRequirementSuccessful reply
PolicyOnlyAny supported callback subsetallow: true, fee_ppm: None
DynamicFeeMust include BeforeSwapBeforeSwap returns Some(fee); other phases return None
CustomAccountingUnsupportedRejected

The core accepts a configured pool cap up to 990000 ppm (99%). That protocol ceiling does not raise an existing pool's immutable cap or default fee. A hook cannot exceed the cap chosen for its pool. An error, allow: false, missing return data, wrong producer/version/phase, or incompatible fee rejects the operation.

Declare the pool's access ceiling

HookCapabilities.permissions bounds what callers may request. Its default allows at most four read-only hook-owned extras, zero writable extras, and no external programs. A callback bit alone grants no write access.

Permission fieldGrants within enabled phases
max_output_fee_ppmIndependent output-fee ceiling, default 0; at most 990000 and requires BeforeSwap
hook_write_phasesWrites to hook-owned policy/state accounts
external_read_phasesRead-only external state
external_write_phasesWritable external state
external_program_phasesAccess to declared executable programs
max_extra_accountsConfigured total, at most 16
max_writable_accountsConfigured writable total per callback, at most 8
external_program_count / external_programsDeclared prefix of at most four program IDs

Masks must be subsets of enabled lifecycle phases. The program-ID prefix must be unique; unused entries must be zero. Core and attached-hook IDs cannot appear in that prefix. The System Program's zero ID is valid only as an explicitly included prefix entry. An external ID authorizes a program/owner class, not arbitrary role-specific addresses.

Request exact accounts for each operation

The hookable instruction's hook_accounts vector must describe every remaining account in order. Each descriptor selects a class and writable_phases. An empty vector supplies no accounts; it does not request a permissive default.

ClassCore requirementAdditional hook responsibility
PolicyPda { index }Hook-owned, non-executable, canonical [b"policy", pool, index], index 0–3Decode policy and validate its pool/configuration
HookStateHook-owned, non-executableBind its PDA, discriminator, actor and other relationships
ExternalStateNon-executable; owner is a declared external programBind the exact mint/account/authority and supported state
ExternalProgramExecutable; key is a declared programUse only the intended CPI interface
CreatableHookStateHook-owned state, or empty System-owned account with System explicitly authorizedBind the canonical PDA before allocating or assigning; writes need hook-write permission
PoolMintReadOnlyExact pool reserve mint; zero write mask and no signerUse only as a readonly mint view for token CPI
ActorReadOnlyExact authenticated actor; zero write mask; no CPI signing privilegeUse as an identity/ATA-owner view, never as a payer or signer
InstructionsReadOnlyCanonical Instructions sysvar key/owner; readonly, nonsigner, non-executable; explicit external-read phaseVerify the signed top-level envelope instead of trusting CPI signer flags

Writes need both pool permission and the descriptor's phase bit. An external state account also needs read permission in any enabled phase where it is passed read-only. External programs are always read-only. Transaction-level writability is an upper bound; the core rebuilds CPI metas separately for each phase and can downgrade an account to read-only.

All enabled phases of the operation are validated before the first callback. A missing AfterSwap permission therefore cannot let a BeforeSwap side effect execute first. Duplicate extras, wrong owners/programs, excess counts, and protected-account aliases reject. Signer extras reject except for ActorReadOnly: it may refer to the outer actor signer, but the callback receives it strictly read-only and without signing privilege.

Preserve the authority boundary

The core hook PDA remains a read-only signer, and the pool remains read-only. Actor signer privileges and vault authority are never forwarded. State permissions do not grant arbitrary settlement. PoolMintReadOnly explicitly permits a reserve mint view. A nonzero output-fee ceiling allows a bounded BeforeSwap request; core performs the token transfer and checks net slippage without forwarding vault authority.

A hook may sign for its own PDA in a nested CPI using its own program seeds. For example, a reward hook may control a separate Token-2022 mint. Its program/state accounts must be declared and validated; that authority cannot spend the AMM's vaults. See authentication and reward credits.

Apply the permissions

Check per-phase privilege downgrades and rollback for every stateful operation. Serialized permission masks alone do not establish runtime behavior. Continue with pool binding and hook validation.