js-legacy: let callers supply data for accounts created in the same transaction - #1347
Open
0xbl33p wants to merge 1 commit into
Open
js-legacy: let callers supply data for accounts created in the same transaction#13470xbl33p wants to merge 1 commit into
0xbl33p wants to merge 1 commit into
Conversation
…ransaction Extra-account-meta resolution reads on-chain account data to derive seeds (`Seed::AccountData`) and pubkeys (`PubkeyData::AccountData`). That assumes every referenced account already exists, which is not true when the same transaction creates one of them first — most commonly the recipient's associated token account, which is created immediately before the transfer. In that case `unpackSeedAccountData` fetches the account, gets `null`, and throws `TokenTransferHookAccountDataNotFound`, so the transaction is never built. This makes any transfer hook whose extra accounts are derived from the destination unusable for first-time recipients, even though the transfer would succeed on-chain: by the time the instruction executes, the account exists. Add an optional `preloadedAccounts` map, keyed by base-58 address, threaded through `createTransferCheckedWithTransferHookInstruction`, `createTransferCheckedWithFeeAndTransferHookInstruction`, `addExtraAccountMetasForExecute` and `resolveExtraAccountMeta`. Entries take precedence over `Connection.getAccountInfo`. Callers supply only data that matches what the account will contain; the on-chain resolver re-derives every address and rejects a mismatch, so this cannot be used to smuggle in a different account. The parameter is optional and trailing, so existing behaviour is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Problem
Extra-account-meta resolution reads on-chain account data to derive seeds (
Seed::AccountData) and pubkeys (PubkeyData::AccountData). That assumes every referenced account already exists.It often doesn't. The overwhelmingly common transfer shape on Solana is:
A hook whose extra accounts are derived from the destination — for example an allowlist or eligibility check on the recipient — cannot be resolved for a first-time recipient.
unpackSeedAccountDatafetches the destination, getsnull, and throwsTokenTransferHookAccountDataNotFound, so the transaction is never built.The transfer itself would have succeeded: by the time the instruction executes, instruction 1 has created the account. Only the client-side resolution fails, because it runs before the transaction does.
The practical effect is that per-recipient transfer hooks are unusable through any client built on this library, unless every recipient's token account is created in advance — which is per (wallet, mint) and doesn't scale.
Change
Add an optional
preloadedAccounts?: Map<string, Buffer>, keyed by base-58 address, threaded through:createTransferCheckedWithTransferHookInstructioncreateTransferCheckedWithFeeAndTransferHookInstructionaddExtraAccountMetasForExecuteresolveExtraAccountMetaunpackSeeds/unpackPubkeyDataEntries take precedence over
Connection.getAccountInfo. A caller that knows the recipient can supply the account's future contents:Safety
Supplying incorrect data cannot be used to smuggle in a different account. The on-chain resolver re-derives every address from real account state and rejects a mismatch (
spl-tlv-account-resolution,ExtraAccountMetaList::check_account_infos), so a wrong guess fails the transaction rather than bypassing the hook.The parameter is optional and trailing, so existing behaviour and call sites are unchanged.
Tests
Three unit tests in
test/unit/transferHook.test.ts:TokenTransferHookAccountDataNotFoundwhen a seed account is absent and nothing is supplied (existing behaviour preserved)lint,format,tsc --noEmitandtest:unit(97 passing) are all clean.Context
Found while building a transfer hook that gates a bonding curve on the recipient holding a particular token. It works on-chain, but no third-party terminal or aggregator can build a buy for a first-time recipient, because they all resolve through this library. Happy to adjust the API shape — a callback instead of a map, or narrowing it to the destination account specifically — if maintainers prefer.