fix(transfer-tokens): stop requiring the recipient to sign native token transfers - #1
Draft
SwineCoder101 wants to merge 2 commits into
Draft
fix(transfer-tokens): stop requiring the recipient to sign native token transfers#1SwineCoder101 wants to merge 2 commits into
SwineCoder101 wants to merge 2 commits into
Conversation
SwineCoder101
marked this pull request as draft
August 27, 2026 12:16
SwineCoder101
force-pushed
the
fix/transfer-tokens-native-recipient-signer
branch
from
August 27, 2026 12:23
d89c628 to
9554821
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: native
transfer_tokensrequires the recipient to sign the transactionThe native variant of
transfer-tokenslists the recipient wallet as a required signer of the SPL TokenTransferCPI. The runtime rejects that CPI withPrivilegeEscalationunless the recipient co-signed the outer transaction, so the program can only send tokens to wallets that are present to sign. That defeats the purpose of a transfer; the Anchor and Pinocchio variants only require the sender's signature. The TypeScript client masked the bug by always attaching the recipient keypair as a writable signer.Severity: functional. Anyone calling the program with a recipient they do not control (the normal case) hits the failure. There is no fund-loss vector, but the instruction is unusable as a transfer.
Affected
native/only.tokens/transfer-tokens/native/program/src/instructions/transfer.rstokens/transfer-tokens/native/ts/instructions/transfer.tstokens/transfer-tokens/native/tests/test.tsanchor/andpinocchio/are correct and unchanged (recipient is a plain non-signer account in both).Functionality
TransferTokens { quantity }creates the recipient's associated token account if it does not exist (paid bypayer), then movesquantitytokens from the owner's ATA to the recipient's ATA. Only the owner of the source token account has to authorise the transfer.The bug
native/program/src/instructions/transfer.rs:60-66built the CPI asThe fifth argument is
signer_pubkeys. Includingrecipient.keythere marks the recipient wallet asis_signer = truein the inner instruction. Because the program is not the recipient and holds no seeds for it, the runtime only allows that meta if the recipient signed the outer transaction. When it did not,invokefails withInstructionError(0, PrivilegeEscalation)("Cross-program invocation with unauthorized signer or writable account") and the whole transfer is rolled back, including the ATA creation.native/ts/instructions/transfer.ts:24,36tookrecipient: TransactionSignerand emitted it asAccountRole.WRITABLE_SIGNER, and the existing tests passed the recipient keypair, so the suite never exercised the real-world case.Reproduce
cd tokens/transfer-tokens/native pnpm install --frozen-lockfile pnpm build-and-testTest:
Transferring Tokens > Transfer tokens to a wallet that does not sign the transaction!(native/tests/test.ts). Against the unfixed program:Fix
transfer.rs:signer_pubkeysis now&[owner.key], matching the Anchor/Pinocchio variants. The SPLTransferinstruction only references source, destination and authority, somint_accountandrecipientwere also dropped from the CPI account list;recipientis still used for the ATA creation CPI, where it is a plain non-signer.ts/instructions/transfer.ts:recipientis now anAddresswithAccountRole.READONLY. Nothing writes the recipient wallet itself (the ATA creation writes the recipient ATA, which is alreadyWRITABLE), so it needs neither signer nor writable privileges.tests/test.ts: the existingTransfer tokens to another wallet!/Transfer NFT to another wallet!tests previously had the recipient keypair sign; they now passrecipientWallet.address. The new test transfers to a freshly generated wallet that never signs and has no lamports, and asserts the recipient ATA balance equalsquantityand the sender ATA decreased byquantity.Verification