transfer-hook.ts 5.5 KB

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