transfer-hook.ts 5.1 KB

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