fix(transfer-hook/counter): persist the transfer count and key the counter PDA per mint - #12
Open
SwineCoder101 wants to merge 2 commits into
Open
fix(transfer-hook/counter): persist the transfer count and key the counter PDA per mint#12SwineCoder101 wants to merge 2 commits into
SwineCoder101 wants to merge 2 commits into
Conversation
…nd blocks a second mint Two new cases in tests/transfer-hook.ts: - 'Counter PDA records every transfer' performs four hook-gated transfers and asserts the counter account holds 4; today it stays at 0. - 'Set up the transfer hook for a second mint' initialises the ExtraAccountMetaList for a fresh mint; today it fails with AccountAlreadyInUse because the counter PDA is a global singleton.
…unter PDA per mint
transfer_hook computed counter + 1 into a local and never wrote it back,
and counter_account was not mut, so the on-chain counter stayed at 0.
The counter PDA was also seeded with the literal "counter" only and
created with init, so initialize_extra_account_meta_list could only ever
succeed for one mint per deployment.
- assign the incremented value and mark counter_account mut
- seed the counter with [b"counter", mint] in both account structs and
store Seed::AccountKey { index: 1 } (the mint) in the ExtraAccountMeta
so the resolver finds the per-mint counter at transfer time
- tests derive the per-mint PDA and check the second mint's counter
SwineCoder101
force-pushed
the
fix/transfer-hook-counter-persist
branch
from
August 27, 2026 12:23
bcd5855 to
37de182
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug: transfer counter never increments and the counter PDA blocks every mint after the first
The
countertransfer hook exists to count how many times a token has been transferred, but thetransfer_hookinstruction never writes the incremented value back and Anchor never serialises theaccount, so the on-chain counter is
0forever. Separately, the counter PDA is derived from theliteral seed
"counter"alone and created withinit, soinitialize_extra_account_meta_listsucceeds exactly once per program deployment: the second mint that tries to use the hook fails with
AccountAlreadyInUse. This is a correctness bug (medium: the example's only feature does not work;low: the hook is single-use), not a style issue, and it hits anyone who follows the example.
Affected
anchor/(the only variant)tokens/token-2022/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rstokens/token-2022/transfer-hook/counter/anchor/tests/transfer-hook.tsFunctionality
initialize_extra_account_meta_listcreates theExtraAccountMetaListPDA for a mint and thecounter PDA that Token-2022 must pass to the hook on every transfer.
transfer_hook(theExecuteinterface instruction, invoked by Token-2022 via CPI duringtransfer_checked) verifies the source account is mid-transfer and increments the counter.The bug
lib.rsonmain, lines 69-76 and 156-157):transfer_hookcomputescount = counter_account.counter.checked_add(1)into a local, logs itand returns.
counter_account.counteris never assigned.TransferHook::counter_accountis declared#[account(seeds = [b"counter"], bump)]withoutmut, so even if the field were assigned Anchor would not write the account back on exit.transfer_checkedcalls the counter account still reads0.The on-chain log "transferred N times" always says
1.lib.rsonmain, lines 113 and 126-128):InitializeExtraAccountMetaList::counter_accountusesinit, seeds = [b"counter"]. The PDAdoes not depend on the mint, so the first
initialize_extra_account_meta_listcreates it andevery later call, for any other mint, fails inside the
initCPI with system program errorAccountAlreadyInUse(Custom(0)). The hook can only ever serve one mint per deployment.Reproduce
Tests:
Counter PDA records every transfer,Set up the transfer hook for a second mint(
tests/transfer-hook.ts). Against the unmodified program:Fix
programs/transfer-hook/src/lib.rs:transfer_hooknow assigns the incremented value:counter_account.counter = counter_account.counter.checked_add(1)...and logs the stored value.TransferHook::counter_accountis#[account(mut, seeds = [b"counter", mint.key().as_ref()], bump)]so Anchor serialises it on exit.
InitializeExtraAccountMetaList::counter_accountis seeded per mint:seeds = [b"counter", mint.key().as_ref()], so every mint gets its own counter and the hook can beset up for any number of mints.
ExtraAccountMetaseeds are now[Seed::Literal("counter"), Seed::AccountKey { index: 1 }].Index 1 is the mint in the
Executeaccount layout (source,mint,destination,owner,extra_account_meta_list, extras...), so Token-2022 / the client resolver derive the per-mintcounter at transfer time.
The existing
transferring-flag check is unchanged.tests/transfer-hook.ts: the counter PDA is derived with the mint key, and the second-mint testadditionally asserts the second mint's counter reads
1while the first mint's counter is untouched.Verification