transfer-hook.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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, {
  94. maxSupportedTransactionVersion: 0,
  95. commitment: "confirmed",
  96. });
  97. console.log(txDetails.meta.logMessages);
  98. console.log(`Transaction Signature: ${txSig}`);
  99. });
  100. // Create the two token accounts for the transfer-hook enabled mint
  101. // Fund the sender token account with 100 tokens
  102. it("Create Token Accounts and Mint Tokens", async () => {
  103. // 100 tokens
  104. const amount = 100 * 10 ** decimals;
  105. const transaction = new Transaction().add(
  106. createAssociatedTokenAccountInstruction(
  107. wallet.publicKey,
  108. sourceTokenAccount,
  109. wallet.publicKey,
  110. mint.publicKey,
  111. TOKEN_2022_PROGRAM_ID,
  112. ASSOCIATED_TOKEN_PROGRAM_ID
  113. ),
  114. createAssociatedTokenAccountInstruction(
  115. wallet.publicKey,
  116. destinationTokenAccount,
  117. recipient.publicKey,
  118. mint.publicKey,
  119. TOKEN_2022_PROGRAM_ID,
  120. ASSOCIATED_TOKEN_PROGRAM_ID
  121. ),
  122. createMintToInstruction(
  123. mint.publicKey,
  124. sourceTokenAccount,
  125. wallet.publicKey,
  126. amount,
  127. [],
  128. TOKEN_2022_PROGRAM_ID
  129. )
  130. );
  131. const txSig = await sendAndConfirmTransaction(
  132. connection,
  133. transaction,
  134. [wallet.payer],
  135. { skipPreflight: true }
  136. );
  137. console.log(`Transaction Signature: ${txSig}`);
  138. });
  139. // Account to store extra accounts required by the transfer hook instruction
  140. it("Create ExtraAccountMetaList Account", async () => {
  141. const initializeExtraAccountMetaListInstruction = await program.methods
  142. .initializeExtraAccountMetaList()
  143. .accounts({
  144. mint: mint.publicKey,
  145. extraAccountMetaList: extraAccountMetaListPDA,
  146. counterAccount: counterPDA,
  147. })
  148. .instruction();
  149. const transaction = new Transaction().add(
  150. initializeExtraAccountMetaListInstruction
  151. );
  152. const txSig = await sendAndConfirmTransaction(
  153. provider.connection,
  154. transaction,
  155. [wallet.payer],
  156. { skipPreflight: true, commitment: "confirmed" }
  157. );
  158. console.log("Transaction Signature:", txSig);
  159. });
  160. it("Transfer Hook with Extra Account Meta", async () => {
  161. // 1 tokens
  162. const amount = 1 * 10 ** decimals;
  163. const amountBigInt = BigInt(amount);
  164. let transferInstructionWithHelper =
  165. await createTransferCheckedWithTransferHookInstruction(
  166. connection,
  167. sourceTokenAccount,
  168. mint.publicKey,
  169. destinationTokenAccount,
  170. wallet.publicKey,
  171. amountBigInt,
  172. decimals,
  173. [],
  174. "confirmed",
  175. TOKEN_2022_PROGRAM_ID
  176. );
  177. console.log("Extra accounts meta: " + extraAccountMetaListPDA);
  178. console.log("Counter PDa: " + counterPDA);
  179. console.log(
  180. "Transfer Instruction: " +
  181. JSON.stringify(transferInstructionWithHelper, null, 2)
  182. );
  183. const transaction = new Transaction().add(transferInstructionWithHelper);
  184. const txSig = await sendAndConfirmTransaction(
  185. connection,
  186. transaction,
  187. [wallet.payer],
  188. { skipPreflight: true }
  189. );
  190. console.log("Transfer Signature:", txSig);
  191. });
  192. });