CommunityCoding & Developmentgithub.com

mohit-1710/transfer-hook-scaffold

Generate a secure Token-2022 transfer hook, or audit an existing one. A compiling Anchor reference, a scaffold generator, and LiteSVM attack tests. Built for the Solana AI Kit.

Works with~Claude Code~Codex CLI~Cursor
npx skills add mohit-1710/transfer-hook-scaffold

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Token-2022 Transfer-Hook Scaffold

Transfer hooks are the hardest Token-2022 extension to get right: the program must expose an interface discriminator Anchor doesn't generate, lay out an ExtraAccountMetaList with byte-exact ordering, and resist out-of-band invocation. Small mistakes surface as opaque runtime errors. This skill is the secure-by-default path, backed by a compiling program and LiteSVM attack tests.

vs the token-extensions skills: solana-token-extensions / token-2022-extensions treat hooks as one feature (a single static example, plus a checklist audit in one case). This is the dedicated hook tool — the only one that generates a fresh secure hook on demand (scaffold.sh) and backs its auditor with passing attack tests. Complementary: install it alongside them when you're actually building or hardening a hook.

When to use this skill

  • Building a transfer hook: whitelist/KYC gating, royalty enforcement, fee routing, per-transfer accounting/limits, soulbound-ish rules.
  • Debugging a hook: AccountNotFound, NotEnoughAccountKeys, IncorrectAccountAddress, or "transfer works without the hook but fails with it."
  • Reviewing/auditing an existing hook program for the standard footguns.
  • Creating a Token-2022 mint that points at a hook, and wiring the client transfer.

Integrator note: if you are on the consuming side (accepting a third-party mint that has a hook), that's a different problem — most integrators should reject unknown hook mints. See the companion token2022-integration-guard skill.

The mental model (read this first)

  1. A hook program implements Execute (the SPL transfer-hook interface), which Token-2022 CPIs on every transfer. Anchor dispatches by sha256("global:<ix>"), which does NOT match the interface discriminator — so you add a fallback that decodes the interface instruction and dispatches in-process (never a self-CPI). → program.md
  2. The hook can require extra accounts. You declare them once in an ExtraAccountMetaList PDA (["extra-account-metas", mint]); Token-2022 reads it and passes those accounts to Execute, resolving seed-based PDAs at transfer time. Order is load-bearing. → extra-account-metas.md
  3. The client must build the transfer with createTransferCheckedWithTransferHookInstruction (a plain transferChecked omits the extra accounts and reverts). → client-wiring.md
  4. A hook is attacker-reachable: guard it. → security.md
  5. Prove it with tests that transfer through the hook. → testing.md

Routing table

The user is…Read
Writing the hook program (Execute, fallback, init meta list)program.md
Fighting ExtraAccountMetaList / opaque account errorsextra-account-metas.md
Wiring the mint + the client transferclient-wiring.md
Hardening / auditing a hook for securitysecurity.md
Writing tests for a hooktesting.md
Scaffolding a fresh hook workspacerun /transfer-hook:scaffold
Auditing an existing hook programrun /transfer-hook:audit
Sources / further readingresources.md

What ships in this skill

ArtifactPathWhat it is
Reference hook programexamples/hook/A compiling Anchor 1.0 hook: whitelist gate + per-mint counter, the fallback router, and the is_transferring guard.
Attack testsexamples/hook/tests/LiteSVM tests that transfer through the hook: whitelisted passes (counter increments), non-whitelisted fails closed, direct Execute rejected.
Scaffold scriptscripts/scaffold.shEmits a fresh hook workspace from the reference.

Pinned stack (2026)

Anchor 1.0, anchor-spl 1.0.2, spl-transfer-hook-interface =2.1.0, spl-tlv-account-resolution 0.11, @solana/spl-token 0.4.14, litesvm 1.2 (kit-native, Token-2022 preloaded). The reference program builds and its tests pass on this stack.

Non-negotiable rules

  • Add a fallback that decodes TransferHookInstruction::Execute and dispatches in-process (__private::__global::transfer_hook) — never invoke your own program.
  • Initialize the ExtraAccountMetaList before the first transfer, and keep the Vec<ExtraAccountMeta> order identical to your Execute accounts struct.
  • Guard Execute with is_transferring — reject calls outside a real transfer.
  • Bind every injected account to PDA seeds (Account<T> + seeds), never a bare UncheckedAccount, or an attacker spoofs your whitelist.
  • Keep the hook minimal and total — it runs on every transfer; a panic/heavy CU bricks the token.
  • Test the transfer path, not just unit logic — a hook that compiles can still fail at resolve time.

Related Skills