Skip to content

feat/transfer hook support#192

Draft
bangyro wants to merge 10 commits intorelease_0.1.11from
feat/transfer_hook_support2
Draft

feat/transfer hook support#192
bangyro wants to merge 10 commits intorelease_0.1.11from
feat/transfer_hook_support2

Conversation

@bangyro
Copy link
Copy Markdown
Collaborator

@bangyro bangyro commented Apr 16, 2026

No description provided.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transfer hook events have the same properties but different name to isolate them from the non-transfer hook events

@bangyro bangyro force-pushed the feat/transfer_hook_support2 branch from 6dc418a to 41d18ae Compare April 16, 2026 04:52
@bangyro
Copy link
Copy Markdown
Collaborator Author

bangyro commented Apr 16, 2026

/bot review

@metoeraag-bot
Copy link
Copy Markdown

metoeraag-bot bot commented Apr 16, 2026

Bot Agent progress: /bot review

Progress:

  • Repo checks (3 ok, 1 failed)
  • Context build
  • Codex review (1 pass)
  • Parallel review (parallel fallback to Codex)
  • Post review

Comment on lines +133 to +140
require!(
transfer_hook_program.executable
&& transfer_hook_program.key().ne(&crate::ID)
&& transfer_hook_program
.key()
.ne(&ctx.accounts.token_program.key()),
PoolError::InvalidTransferHookProgram
);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessary, just to be safe. ensuring that the transfer_hook_program is not one of the program involved in the transfer chain (dbc & token_2022)

Comment on lines +221 to +227
it("Initialize extra account meta list for transfer hook", async () => {
await initializeExtraAccountMetaList(
svm,
operator,
virtualPoolState.baseMint
);
});
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need creator to init extra account meta list . Might cause issues if they do not init it

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not add support for transfer hook in the deprecated ix (ix_claim_protocol_fee and ix_zap_protocol_fee)

Copy link
Copy Markdown

@metoeraag-bot metoeraag-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

Comment

Summary

⚠️ Limited-confidence review: check failed: bun run lint.

Posted 4 validated inline finding(s) after final filtering.

Findings
  • [P2] programs/dynamic-bonding-curve/src/instructions/initialize_pool/ix_initialize_virtual_pool_with_token2022_transfer_hook.rs:106 This account is only checked for != Pubkey::default(), but it is not required to sign or be tied to the creator/partner. Because the new DAMM v2 migration path now requires the transfer-hook program id and authority to be revoked first, a typo or arbitrary third-party pubkey here can create a pool that nobody can ever migrate. Please require the hook authority to sign (or otherwise constrain it to a known signer-controlled authority) at initialization time.
  • [P2] tests/create_pool_with_token2022_with_transfer_hook.tests.ts:214 getMint was changed to use unpackMint, which now validates the token program owner and returns the higher-level Mint shape. This call is reading a Token-2022 mint with the default TOKEN_PROGRAM_ID, so it will throw, and the later mintAuthorityOption assertion no longer matches the returned type. Please pass TOKEN_2022_PROGRAM_ID here and check mintAuthority === null / freezeAuthority === null instead of the old *AuthorityOption fields.
  • [P2] tests/first_swap_with_transfer_hook.tests.ts:326 The helper is hard-coding referralTokenAccount: program.programId for a test that is exercising the no-referral path. On-chain this optional account will be treated as present, which either fails account deserialization immediately (it's not a token account) or, even if it somehow got through, defeats validate_contain_initialize_pool_ix_and_no_cpi() because that branch explicitly requires referral_token_account.is_none(). Please omit this optional account / pass null here.
  • [P2] tests/utils/token.ts:199 After a transfer hook is revoked, the extension still exists and getTransferHook(mintState) returns a struct whose programId is PublicKey.default() (the new migration tests assert exactly that). This helper only checks for a missing extension, so revoked mints are still treated as active-hook mints and extra accounts keep getting built for them. Downstream helpers like withdrawLeftoverWithTransferHook then pass hook accounts even though the on-chain transfer path now expects none. Please also short-circuit when transferHook.programId.equals(PublicKey.default()).
Repo checks

Repo Checks

  • LLM checks planner: added package install step before running JS commands.

  • cargo fetch --locked: ok

  • bun install --frozen-lockfile: ok

bun install v1.3.2 (b131639c)

+ @anchor-lang/borsh@1.0.0
+ @solana/spl-token@0.4.13
+ @solana/spl-token-metadata@0.1.6
+ @solana/web3.js@1.98.4
+ @types/bn.js@5.1.6
+ @types/chai@4.3.20
+ @types/mocha@9.1.1
+ chai@4.5.0
+ decimal.js@10.5.0
+ litesvm@0.1.0
+ mocha@9.2.2
+ prettier@2.8.8
+ ts-mocha@10.1.0
+ tsx@4.20.3
+ typescript@5.9.3
+ @anchor-lang/core@1.0.0
+ @metaplex-foundation/mpl-token-metadata@3.4.0

207 packages installed [25.78s]
  • cargo check --workspace: ok
   = note: this warning originates in the attribute macro `event_cpi` (in Nightly builds, run with -Z macro-backtrace for more info)

warning: use of deprecated function `dynamic_bonding_curve::claim_protocol_fee`: Use claim_protocol_fee2 through protocol_fee program instead
  --> programs/dynamic-bonding-curve/src/lib.rs:60:12
   |
60 |     pub fn claim_protocol_fee<'info>(
   |            ^^^^^^^^^^^^^^^^^^

warning: use of deprecated function `dynamic_bonding_curve::zap_protocol_fee`: Use claim_protocol_fee2 through protocol_fee program instead
  --> programs/dynamic-bonding-curve/src/lib.rs:80:12
   |
80 |     pub fn zap_protocol_fee<'info>(
   |            ^^^^^^^^^^^^^^^^

warning: use of deprecated struct `instructions::migration::dynamic_amm_v2::migration_damm_v2_create_metadata::MigrationDammV2CreateMetadataCtx`
   --> programs/dynamic-bonding-curve/src/lib.rs:306:29
    |
306 |         ctx: Context<'info, MigrationDammV2CreateMetadataCtx<'info>>,
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: use of deprecated function `dynamic_bonding_curve::migration_damm_v2_create_metadata`: It's unneeded. Will be removed in next release version
   --> programs/dynamic-bonding-curve/src/lib.rs:305:12
    |
305 |     pub fn migration_damm_v2_create_metadata<'info>(
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: use of deprecated field `instructions::migration::dynamic_amm_v2::migrate_damm_v2_initialize_pool::MigrateDammV2Ctx::migration_metadata`
  --> programs/dynamic-bonding-curve/src/instructions/migration/dynamic_amm_v2/migrate_damm_v2_initialize_pool.rs:48:9
   |
48 |     pub migration_metadata: UncheckedAccount<'info>,
   |         ^^^^^^^^^^^^^^^^^^

warning: `dynamic-bonding-curve` (lib) generated 27 warnings (2 duplicates)
    Checking dynamic-bonding-curve-sdk v0.1.0 (/data/workdir/2962945/MeteoraAg/dynamic-bonding-curve/dynamic-bonding-curve-sdk)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 25.83s
  • bun run lint: failed
Checking formatting...

$ prettier */*.js "*/**/*{.js,.ts}" --check
[warn] tests/backwards_compatibility/utils.ts
[warn] tests/claim_protocol_liquidity_migration_fee.tests.ts
[warn] tests/instructions/creatorInstructions.ts
[warn] tests/instructions/dammV2Migration.ts
[warn] tests/migrate_damm_v2_dynamic_config.tests.ts
[warn] tests/utils/idl/damm_v2.ts
[warn] tests/utils/idl/dynamic_vault.ts
[warn] tests/utils/idl/locker.ts
[warn] tests/zap_protocol_fee.tests.ts
[warn] Code style issues found in 9 files. Forgot to run Prettier?
error: script "lint" exited with code 1

pub transfer_hook_program: UncheckedAccount<'info>,

/// CHECK: transfer hook authority for the base mint
pub transfer_hook_authority: UncheckedAccount<'info>,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 This account is only checked for != Pubkey::default(), but it is not required to sign or be tied to the creator/partner. Because the new DAMM v2 migration path now requires the transfer-hook program id and authority to be revoked first, a typo or arbitrary third-party pubkey here can create a pool that nobody can ever migrate. Please require the hook authority to sign (or otherwise constrain it to a known signer-controlled authority) at initialization time.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is intentional

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow for creator to have flexibility of setting the authority during dbc stage

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional flexibility noted, but the core issue still stands: a mistyped or uncontrolled hook authority can make the hook impossible to revoke later and leave the pool unmigratable. If arbitrary-authority support is required, please at least add an explicit guard or warning for this irreversible footgun.

Comment thread tests/create_pool_with_token2022_with_transfer_hook.tests.ts
Comment thread tests/first_swap_with_transfer_hook.tests.ts Outdated
Comment thread tests/utils/token.ts
@bangyro
Copy link
Copy Markdown
Collaborator Author

bangyro commented Apr 16, 2026

/bot review

@metoeraag-bot
Copy link
Copy Markdown

metoeraag-bot bot commented Apr 16, 2026

Bot Agent progress: /bot review

Progress:

  • Repo checks (3 ok, 1 failed)
  • Context build
  • Codex review (1 pass)
  • Parallel review (Claude (API) + Codex merged)
  • Post review

Copy link
Copy Markdown

@metoeraag-bot metoeraag-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

Comment

Summary

⚠️ Limited-confidence review: check failed: bun run lint.

Posted 2 validated inline finding(s) after final filtering.

Findings
  • [P2] programs/dynamic-bonding-curve/src/instructions/initialize_pool/ix_initialize_virtual_pool_with_token2022_transfer_hook.rs:142 This path creates the pool without rejecting a Token-2022 quote mint that itself has an active transfer hook. The new plumbing only carries TransferHookBase / TransferHookBaseReferral, and every quote-side transfer in the diff still passes None for transfer-hook accounts (swap, protocol-fee claims, trading-fee claims, surplus/migration-fee withdrawals, etc.). If configs can point at a transfer-hook-enabled quote mint, pool creation will succeed but normal operations will fail at runtime with MissingRemainingAccountForTransferHook. Please either reject quote mints with active hooks here (or at config creation time), or add quote-side hook account support everywhere quote transfers happen.
  • [P2] programs/dynamic-bonding-curve/src/instructions/swap/ix_swap.rs:333 For transfer hook pools, only EvtSwap2WithTransferHook is emitted, but the legacy EvtSwap event is no longer emitted at all. Non-transfer-hook pools still emit both EvtSwap and EvtSwap2. This means downstream consumers that listen for EvtSwap on transfer hook pools will silently stop receiving events. Consider also emitting an equivalent legacy event or documenting this breaking change clearly.
Potential concerns (lower confidence)
  • [P1] programs/dynamic-bonding-curve/src/event/mod.rs:191 Two events were removed: EvtPartnerWithdrawMigrationFee and EvtClaimProtocolLiquidityMigrationFee. If any downstream indexers, event parsers, or off-chain systems depend on these event types, this is a breaking change. The changelog doesn't mention these removals. Were these events unused, or is this intentional? If intentional, please document it in the changelog.
Repo checks

Repo Checks

  • LLM checks planner: added package install step before running JS commands.

  • cargo fetch --locked: ok

  • bun install --frozen-lockfile: ok

bun install v1.3.2 (b131639c)

+ @anchor-lang/borsh@1.0.0
+ @solana/spl-token@0.4.13
+ @solana/spl-token-metadata@0.1.6
+ @solana/web3.js@1.98.4
+ @types/bn.js@5.1.6
+ @types/chai@4.3.20
+ @types/mocha@9.1.1
+ chai@4.5.0
+ decimal.js@10.5.0
+ litesvm@0.1.0
+ mocha@9.2.2
+ prettier@2.8.8
+ ts-mocha@10.1.0
+ tsx@4.20.3
+ typescript@5.9.3
+ @anchor-lang/core@1.0.0
+ @metaplex-foundation/mpl-token-metadata@3.4.0

207 packages installed [12.09s]
  • cargo check --workspace: ok
   = note: this warning originates in the attribute macro `event_cpi` (in Nightly builds, run with -Z macro-backtrace for more info)

warning: use of deprecated function `dynamic_bonding_curve::claim_protocol_fee`: Use claim_protocol_fee2 through protocol_fee program instead
  --> programs/dynamic-bonding-curve/src/lib.rs:60:12
   |
60 |     pub fn claim_protocol_fee<'info>(
   |            ^^^^^^^^^^^^^^^^^^

warning: use of deprecated function `dynamic_bonding_curve::zap_protocol_fee`: Use claim_protocol_fee2 through protocol_fee program instead
  --> programs/dynamic-bonding-curve/src/lib.rs:80:12
   |
80 |     pub fn zap_protocol_fee<'info>(
   |            ^^^^^^^^^^^^^^^^

warning: use of deprecated struct `instructions::migration::dynamic_amm_v2::migration_damm_v2_create_metadata::MigrationDammV2CreateMetadataCtx`
   --> programs/dynamic-bonding-curve/src/lib.rs:306:29
    |
306 |         ctx: Context<'info, MigrationDammV2CreateMetadataCtx<'info>>,
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: use of deprecated function `dynamic_bonding_curve::migration_damm_v2_create_metadata`: It's unneeded. Will be removed in next release version
   --> programs/dynamic-bonding-curve/src/lib.rs:305:12
    |
305 |     pub fn migration_damm_v2_create_metadata<'info>(
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: use of deprecated field `instructions::migration::dynamic_amm_v2::migrate_damm_v2_initialize_pool::MigrateDammV2Ctx::migration_metadata`
  --> programs/dynamic-bonding-curve/src/instructions/migration/dynamic_amm_v2/migrate_damm_v2_initialize_pool.rs:48:9
   |
48 |     pub migration_metadata: UncheckedAccount<'info>,
   |         ^^^^^^^^^^^^^^^^^^

warning: `dynamic-bonding-curve` (lib) generated 27 warnings (2 duplicates)
    Checking dynamic-bonding-curve-sdk v0.1.0 (/data/workdir/2962945/MeteoraAg/dynamic-bonding-curve/dynamic-bonding-curve-sdk)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 23.55s
  • bun run lint: failed
Checking formatting...

$ prettier */*.js "*/**/*{.js,.ts}" --check
[warn] tests/backwards_compatibility/utils.ts
[warn] tests/claim_protocol_liquidity_migration_fee.tests.ts
[warn] tests/instructions/creatorInstructions.ts
[warn] tests/instructions/dammV2Migration.ts
[warn] tests/migrate_damm_v2_dynamic_config.tests.ts
[warn] tests/utils/idl/damm_v2.ts
[warn] tests/utils/idl/dynamic_vault.ts
[warn] tests/utils/idl/locker.ts
[warn] tests/zap_protocol_fee.tests.ts
[warn] Code style issues found in 9 files. Forgot to run Prettier?
error: script "lint" exited with code 1

PoolError::InvalidTransferHookProgram
);

let activation_point = process_initialize_virtual_pool_with_token2022(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 This path creates the pool without rejecting a Token-2022 quote mint that itself has an active transfer hook. The new plumbing only carries TransferHookBase / TransferHookBaseReferral, and every quote-side transfer in the diff still passes None for transfer-hook accounts (swap, protocol-fee claims, trading-fee claims, surplus/migration-fee withdrawals, etc.). If configs can point at a transfer-hook-enabled quote mint, pool creation will succeed but normal operations will fail at runtime with MissingRemainingAccountForTransferHook. Please either reject quote mints with active hooks here (or at config creation time), or add quote-side hook account support everywhere quote transfers happen.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At config creation we reject quote mint with transfer hook with is_supported_quote_mint

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification — since config creation already rejects quote mints with active transfer hooks via is_supported_quote_mint, this instruction inherits that invariant and the concern here doesn’t apply.

migration_threshold: config.migration_quote_threshold,
current_timestamp,
});
if pool.is_transfer_hook_pool()? {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 For transfer hook pools, only EvtSwap2WithTransferHook is emitted, but the legacy EvtSwap event is no longer emitted at all. Non-transfer-hook pools still emit both EvtSwap and EvtSwap2. This means downstream consumers that listen for EvtSwap on transfer hook pools will silently stop receiving events. Consider also emitting an equivalent legacy event or documenting this breaking change clearly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvtSwap is considered legacy. So we omit it for the transfer hook path

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood that EvtSwap is intentionally legacy here. The remaining concern is compatibility: transfer-hook pools will stop emitting it while other pools still do, so please at least document this as a breaking change for downstream indexers/consumers.

Comment on lines +346 to +355
emit_cpi!(EvtSwap {
pool: ctx.accounts.pool.key(),
config: ctx.accounts.config.key(),
trade_direction: trade_direction.into(),
has_referral,
params: swap_in_parameters,
swap_result,
amount_in: swap_result_2.included_fee_input_amount,
current_timestamp,
});
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this Evt is considered legacy, so I didn't add a WithTransferHook variant

Comment on lines 299 to 307
#[deprecated(
since = "0.1.7",
note = "It's unneeded. Will be removed in next release version"
)]
pub fn migration_damm_v2_create_metadata<'info>(
ctx: Context<'info, MigrationDammV2CreateMetadataCtx<'info>>,
) -> Result<()> {
instructions::handle_migration_damm_v2_create_metadata(ctx)
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we remove this endpoint? I'll have to change the backwards compatibility test

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