Skip to content

feat(program): publish transfer context for Token-2022 transfer hooks - #244

Open
dev-jodee wants to merge 1 commit into
mainfrom
feat/transfer-context-for-transfer-hooks
Open

feat(program): publish transfer context for Token-2022 transfer hooks#244
dev-jodee wants to merge 1 commit into
mainfrom
feat/transfer-context-for-transfer-hooks

Conversation

@dev-jodee

Copy link
Copy Markdown
Collaborator

Summary

  • Pulls against a mint with an active transfer hook now create a TransferContext PDA at ["TransferContext", subscription_authority], record the initiator, delegation, kind, mint, amount and slot, and close it after the TransferChecked CPI. Rent round-trips through the initiator inside the transaction.
  • Hooks resolve it as an external PDA (Literal("TransferContext") + AccountKey(3)) and can seed their own policy accounts on its contents, e.g. an allowlist PDA over the initiator bytes at offset 3. Without this, spl-transfer-hook-interface forwards only accounts the validation list can resolve, and no seed source identifies the delegate.
  • No new instruction, no new error code, no change to any instruction's account layout. Mints without a hook, and hooks that don't ask for the context, behave exactly as before.
  • TS SDK resolves and attaches it automatically in transferFixed / transferRecurring / transferSubscription, feeding the resolver the bytes the program is about to write since the account can't be fetched before it exists.
  • Wire contract for hook authors documented in docs/005-transfer-context.md.

Closes TOO-671.

Test Plan

  • just unit-test — 59 passing.
  • cargo test -p tests-subscriptions — 282 passing, 8 new in test_transfer_context.rs: allowlisted initiator pulls, blocked initiator rejected inside the hook, context closed and rent refunded, context omitted fails closed at resolution, prefunded context address still creates, recurring and subscription paths screened on their own initiator.
  • pnpm vitest run test/transfer-context.test.ts — address derivation and initiator offset match the on-chain layout.
  • just check clean.

Notes

  • CU on a hooked fixed pull: 38.5k vs 22.8k on the existing hook test. Part of that is the richer validation list (4 metas vs 1); our share is one find_program_address plus the create CPI. Passing the bump in instruction data would trim it if it ever matters.
  • Callers must pass the context writable and the initiator writable (it funds the rent), and include the system program among the hook accounts. The SDK does this.
  • slot is unknown client-side and encoded as zero in the pending bytes, so hook seeds over slot can't be resolved off-chain.

Token-2022 only forwards the accounts an ExtraAccountMetaList can resolve,
and none of the resolver's seed sources identify the delegate pulling from
a user. A hook therefore sees the SubscriptionAuthority PDA as the transfer
authority and nothing about who initiated the pull, which makes per-delegate
policy unenforceable.

Pulls against a mint with an active transfer hook now create a TransferContext
PDA at ["TransferContext", subscription_authority], record the initiator,
delegation, mint, amount and slot, and close it once the transfer CPI returns.
Hooks resolve it as an external PDA and can seed their own policy accounts on
its contents. Mints without a hook, and hooks that do not ask for the context,
are unaffected.
@linear

linear Bot commented Aug 25, 2026

Copy link
Copy Markdown

TOO-671

@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
solana-subscriptions-program Ready Ready Preview Aug 25, 2026 4:43pm

Request Review

@github-actions

Copy link
Copy Markdown

Compute Unit Report

Instruction Samples CUs Est Cost (Low) [SOL] Est Cost (Med) [SOL] Est Cost (High) [SOL] Δ Avg vs main
cancel_subscription 28 1838 0.000005000 0.000005073 0.000005919
cancel_subscription_now 5 1873 0.000005000 0.000005074 0.000005936
close_subscription_authority 22 1804 0.000005000 0.000005072 0.000005902
create_fixed_delegation 58 3524 0.000005001 0.000005140 0.000006762
create_plan 142 3501 0.000005001 0.000005140 0.000006750
create_recurring_delegation 37 3556 0.000005001 0.000005142 0.000006778
delete_plan 10 359 0.000005000 0.000005014 0.000005179
init_subscription_authority 239 4856 0.000005001 0.000005194 0.000007428
resume_subscription 4 2041 0.000005000 0.000005081 0.000006020
revoke_abandoned_delegation 3 403 0.000005000 0.000005016 0.000005201
revoke_abandoned_subscription 2 2093 0.000005000 0.000005083 0.000006046
revoke_delegation 24 282 0.000005000 0.000005011 0.000005141
revoke_subscription_authority 8 6393 0.000005001 0.000005255 0.000008196
subscribe 52 6642 0.000005001 0.000005265 0.000008321
transfer_fixed 13 5635 0.000005001 0.000005225 0.000007817
transfer_recurring 22 5755 0.000005001 0.000005230 0.000007877
transfer_subscription 15 5996 0.000005001 0.000005239 0.000007998
update_plan 31 2142 0.000005000 0.000005085 0.000006071

🔺 increase · 🔻 decrease · – unchanged · 🆕 new · 🗑 removed (vs main)

Generated: 2026-08-25

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant