Skip to content

Allow Fallback for TokenTransferHookAccountDataNotFound in SPL Token JS #212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

dehypnosis
Copy link

To the Solana SDK Team,

We are the Fragmetric team, leveraging Token 2022 Transfer Hooks to improve transparency in yield distribution on De-Fi protocol. Our system tracks all transfer transactions using the transfer hook mechanism to adjust reward accrual efficiently.

1. Issue Overview

While integrating transfer hooks, we encountered a limitation in metadata resolution within SPL Token JS that prevents seamless transfers when the destination token account is missing.

How This Issue Arises

Our extra account meta references destination_token_account.owner to handle recipient-related updates.
If the destination token account does not exist, SPL Token JS throws a TokenTransferHookAccountDataNotFound error (reference: https://github.com/solana-program/token-2022/blob/main/clients/js-legacy/src/extensions/transferHook/seeds.ts#L74).

This means that while we can transfer to an already created ATA, if the ATA does not exist, we are forced to send two separate transactions:

  • First transaction: Create the ATA.
  • Second transaction: Perform transfer_checked.
    While this is manageable operationally, it disrupts wallet integrations and end-user experience, making it impossible to handle transfers seamlessly in one atomic transaction.

A Possible Solution

We propose introducing a fallback mechanism in SPL Token JS to allow unresolved accounts to be replaced by a placeholder (such as PublicKey.default). This enables on-chain programs to handle missing accounts gracefully instead of outright failing.

2. Implementation Detail

To address this, we have implemented an optional flag:

allowAccountDataFallback = false;

When set to true, this allows unresolved extra accounts to fall back to PublicKey.default instead of failing with TokenTransferHookAccountDataNotFound.

Our implementation modifies resolveExtraAccountMeta() in SPL Token JS as follows:

export async function resolveExtraAccountMeta(
    connection: Connection,
    extraMeta: ExtraAccountMeta,
    previousMetas: AccountMeta[],
    instructionData: Buffer,
    transferHookProgramId: PublicKey,
    allowAccountDataFallback = false, // New flag
): Promise<AccountMeta> {
    try {
        const seeds = await unpackSeeds(extraMeta.addressConfig, previousMetas, instructionData, connection);
        const pubkey = PublicKey.findProgramAddressSync(seeds, programId)[0];

        return { pubkey, isSigner: extraMeta.isSigner, isWritable: extraMeta.isWritable };
    } catch (error) {
        if (allowAccountDataFallback && error instanceof TokenTransferHookAccountDataNotFound) {
            return { pubkey: PublicKey.default, isSigner: false, isWritable: false };
        }
        throw error;
    }
}

3. Why This Change Matters

This simple fallback mechanism does not break any existing on-chain use cases and does not affect transaction simulation results, but it expands the utility of transfer hooks in the following ways:

4. Request

Would it be possible to merge this change into SPL Token JS or consider a similar fallback mechanism for handling missing accounts in transfer hooks? We believe this enhancement will greatly improve the flexibility of Token 2022 adoption, particularly in DeFi applications like us trying to resolve challenging problem utilizing token extensions.

Looking forward to your feedback!

Best,
Fragmetric Team

… to support optional accounts in transfer hook
@buffalojoec
Copy link
Contributor

This means that while we can transfer to an already created ATA, if the ATA does not exist, we are forced to send two separate transactions:

  • First transaction: Create the ATA.
  • Second transaction: Perform transfer_checked.
    While this is manageable operationally, it disrupts wallet integrations and end-user experience, making it impossible to handle transfers seamlessly in one atomic transaction.

I may be missing something, but is there a reason why it must be two separate transactions, rather than one transaction with two instructions?

When set to true, this allows unresolved extra accounts to fall back to PublicKey.default instead of failing with TokenTransferHookAccountDataNotFound.

I see why you need a fallback on the client, since it will try to fetch the destination and won't be able to find it. However, defaulting to Pubkey.default may prevent the client fetch from erroring, but that would fail in the on-chain transfer hook, since the account key wouldn't match the expected destination owner. Do you have a test where you've got a transfer hook program set up and this entire workflow passess?

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.

2 participants