transfer-hook.ts 6.0 KB

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