Skip to content

Authenticate callbacks and actors

Trust the core-signed context without forwarding user or vault signing privileges.

Prerequisite: understand permissions. Use this page before implementing an allowlist or any policy that depends on who is trading.

Follow the trust chain

sequenceDiagram
participant A as Actor authority
participant C as grapl core
participant H as Hook program
A->>C: Signed request
C->>C: Validate actor<br/>and accounts
C->>C: Mark entered<br/>and serialize pool
C->>H: CPI + core PDA signer
H->>H: Verify origin<br/>and pool binding
H->>H: Evaluate policy
H-->>C: Tagged HookReply
C->>C: Validate reply
Note over C,H: Explicit state permissions<br/>No vault or actor signer

For market operations, MarketAccounts.actor is a required signer. Both actor token accounts must have the expected mint and that actor as their token authority. Liquidity changes also require the position's owner to equal the actor. Initialization uses the creator signer instead.

The core copies this identity into HookInvocation.actor. A hook trusts that field only after authenticating the callback. Proof bytes, a supplied public key, the transaction fee payer, or a router's name do not replace this chain.

Inspect the actual CPI accounts

OrderAccountPrivileges inside the callback
0Core PDA [b"hook", pool]Read-only signer
1Core-owned poolRead-only, not a signer
2 onwardDeclared policy, hook state, external state or program accountsPer-phase read/write permissions; never upstream signers

The callback PDA is derived under the core program, separately from the pool's vault authority. The runtime grants its signer privilege through invoke_signed. See Solana CPI with PDA signers.

Every extra has a matching HookAccountAccess descriptor in the core instruction. The core validates count, class, owner, program declarations, duplicates, protected aliases and per-phase privileges. It rebuilds CPI metas rather than forwarding transaction writability. Default permissions allow four read-only hook-owned extras; explicitly expanded permissions remain bounded at 16 extras and eight writes per callback.

For PolicyPda { index }, index zero is the byte [0], not ASCII "0". Hook-owned state still needs hook-side PDA/discriminator/relationship validation. External program ownership alone does not bind a token account to the intended reward mint or actor. The fee/window/allowlist examples expect one policy; the lifecycle probe expects no extras.

Core-owned accounts and protected pool, actor, vault, mint, position, tick-book, authority and settlement-token identities cannot be supplied as extras. A hook may sign its own PDA in a nested CPI, but that is separate from borrowing the actor or core vault authority.

Reuse the authenticated BaseHook entrypoint

The custom hook template calls grapl_amm::hooks::base::process_hook from its entrypoint. The dispatcher authenticates the pool and core signer before constructing HookContext and calling your implementation.

Generic authentication lives in programs/grapl-amm/src/hooks/base/authentication.rs. Existing example policy adapters delegate to those helpers from examples/hooks/shared/src/runtime/authentication.rs; policy-specific PDA checks and their existing error-code mapping remain in the example layer. The base dispatcher additionally checks operation sequence, mint pair and the exact capability contract before dispatch.

The lifecycle probe also uses BaseHook, so its rollback tests exercise the reusable entrypoint. See BaseHook reference for the checks and hook validation for the executable test harness.

Coming from Uniswap v4

In v4, msg.sender inside a hook is the PoolManager, and the callback's sender is commonly a router. Its identity guide discusses trusted routers. grapl uses a runtime-validated account signer plus the core PDA callback. There is no EVM msg.sender or tx.origin to read.

An upstream program might sign with its own PDA. That does not establish the identity of a human behind that PDA. A future routed identity model must define the end user, token authority, destination, and proof forwarding together; the current adapter rejects a distinct user and token-transfer authority.

Validate the trust boundary

Check direct calls, wrong authority, pool substitution, invalid privileges, and wrong policy state. Validate per-phase access and protected-account rejection. Hook validation connects these checks with reserve settlement and rollback.