transfer-hook.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import * as anchor from '@coral-xyz/anchor';
  2. import type { Program } from '@coral-xyz/anchor';
  3. import {
  4. ASSOCIATED_TOKEN_PROGRAM_ID,
  5. ExtensionType,
  6. TOKEN_2022_PROGRAM_ID,
  7. createAssociatedTokenAccountInstruction,
  8. createInitializeMintInstruction,
  9. createInitializeTransferHookInstruction,
  10. createMintToInstruction,
  11. createTransferCheckedWithTransferHookInstruction,
  12. getAssociatedTokenAddressSync,
  13. getMintLen,
  14. } from '@solana/spl-token';
  15. import { Keypair, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
  16. import type { TransferHook } from '../target/types/transfer_hook';
  17. describe('transfer-hook', () => {
  18. // Configure the client to use the local cluster.
  19. const provider = anchor.AnchorProvider.env();
  20. anchor.setProvider(provider);
  21. const program = anchor.workspace.TransferHook as Program<TransferHook>;
  22. const wallet = provider.wallet as anchor.Wallet;
  23. const connection = provider.connection;
  24. // Generate keypair to use as address for the transfer-hook enabled mint
  25. const mint = new Keypair();
  26. const decimals = 9;
  27. // Sender token account address
  28. const sourceTokenAccount = getAssociatedTokenAddressSync(
  29. mint.publicKey,
  30. wallet.publicKey,
  31. false,
  32. TOKEN_2022_PROGRAM_ID,
  33. ASSOCIATED_TOKEN_PROGRAM_ID,
  34. );
  35. // Recipient token account address
  36. const recipient = Keypair.generate();
  37. const destinationTokenAccount = getAssociatedTokenAddressSync(
  38. mint.publicKey,
  39. recipient.publicKey,
  40. false,
  41. TOKEN_2022_PROGRAM_ID,
  42. ASSOCIATED_TOKEN_PROGRAM_ID,
  43. );
  44. // ExtraAccountMetaList address
  45. // Store extra accounts required by the custom transfer hook instruction
  46. const [extraAccountMetaListPDA] = PublicKey.findProgramAddressSync(
  47. [Buffer.from('extra-account-metas'), mint.publicKey.toBuffer()],
  48. program.programId,
  49. );
  50. const [counterPDA] = PublicKey.findProgramAddressSync([Buffer.from('counter')], program.programId);
  51. it('Create Mint Account with Transfer Hook Extension', async () => {
  52. const extensions = [ExtensionType.TransferHook];
  53. const mintLen = getMintLen(extensions);
  54. const lamports = await provider.connection.getMinimumBalanceForRentExemption(mintLen);
  55. const transaction = new Transaction().add(
  56. SystemProgram.createAccount({
  57. fromPubkey: wallet.publicKey,
  58. newAccountPubkey: mint.publicKey,
  59. space: mintLen,
  60. lamports: lamports,
  61. programId: TOKEN_2022_PROGRAM_ID,
  62. }),
  63. createInitializeTransferHookInstruction(
  64. mint.publicKey,
  65. wallet.publicKey,
  66. program.programId, // Transfer Hook Program ID
  67. TOKEN_2022_PROGRAM_ID,
  68. ),
  69. createInitializeMintInstruction(mint.publicKey, decimals, wallet.publicKey, null, TOKEN_2022_PROGRAM_ID),
  70. );
  71. const txSig = await sendAndConfirmTransaction(provider.connection, transaction, [wallet.payer, mint], {
  72. skipPreflight: true,
  73. commitment: 'finalized',
  74. });
  75. const txDetails = await program.provider.connection.getTransaction(txSig, {
  76. maxSupportedTransactionVersion: 0,
  77. commitment: 'confirmed',
  78. });
  79. console.log(txDetails.meta.logMessages);
  80. console.log(`Transaction Signature: ${txSig}`);
  81. });
  82. // Create the two token accounts for the transfer-hook enabled mint
  83. // Fund the sender token account with 100 tokens
  84. it('Create Token Accounts and Mint Tokens', async () => {
  85. // 100 tokens
  86. const amount = 100 * 10 ** decimals;
  87. const transaction = new Transaction().add(
  88. createAssociatedTokenAccountInstruction(
  89. wallet.publicKey,
  90. sourceTokenAccount,
  91. wallet.publicKey,
  92. mint.publicKey,
  93. TOKEN_2022_PROGRAM_ID,
  94. ASSOCIATED_TOKEN_PROGRAM_ID,
  95. ),
  96. createAssociatedTokenAccountInstruction(
  97. wallet.publicKey,
  98. destinationTokenAccount,
  99. recipient.publicKey,
  100. mint.publicKey,
  101. TOKEN_2022_PROGRAM_ID,
  102. ASSOCIATED_TOKEN_PROGRAM_ID,
  103. ),
  104. createMintToInstruction(mint.publicKey, sourceTokenAccount, wallet.publicKey, amount, [], TOKEN_2022_PROGRAM_ID),
  105. );
  106. const txSig = await sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: true });
  107. console.log(`Transaction Signature: ${txSig}`);
  108. });
  109. // Account to store extra accounts required by the transfer hook instruction
  110. it('Create ExtraAccountMetaList Account', async () => {
  111. const initializeExtraAccountMetaListInstruction = await program.methods
  112. .initializeExtraAccountMetaList()
  113. .accounts({
  114. mint: mint.publicKey,
  115. })
  116. .instruction();
  117. const transaction = new Transaction().add(initializeExtraAccountMetaListInstruction);
  118. const txSig = await sendAndConfirmTransaction(provider.connection, transaction, [wallet.payer], { skipPreflight: true, commitment: 'confirmed' });
  119. console.log('Transaction Signature:', txSig);
  120. });
  121. it('Transfer Hook with Extra Account Meta', async () => {
  122. // 1 tokens
  123. const amount = 1 * 10 ** decimals;
  124. const amountBigInt = BigInt(amount);
  125. const transferInstructionWithHelper = await createTransferCheckedWithTransferHookInstruction(
  126. connection,
  127. sourceTokenAccount,
  128. mint.publicKey,
  129. destinationTokenAccount,
  130. wallet.publicKey,
  131. amountBigInt,
  132. decimals,
  133. [],
  134. 'confirmed',
  135. TOKEN_2022_PROGRAM_ID,
  136. );
  137. console.log(`Extra accounts meta: ${extraAccountMetaListPDA}`);
  138. console.log(`Counter PDA: ${counterPDA}`);
  139. console.log(`Transfer Instruction: ${JSON.stringify(transferInstructionWithHelper, null, 2)}`);
  140. const transaction = new Transaction().add(transferInstructionWithHelper);
  141. const txSig = await sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: true });
  142. console.log('Transfer Signature:', txSig);
  143. });
  144. });