transfer-hook.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import * as anchor from "@coral-xyz/anchor";
  2. import type { Program } from "@coral-xyz/anchor";
  3. import {
  4. ASSOCIATED_TOKEN_PROGRAM_ID,
  5. TOKEN_2022_PROGRAM_ID,
  6. createAssociatedTokenAccountInstruction,
  7. createMintToInstruction,
  8. createTransferCheckedWithTransferHookInstruction,
  9. getAssociatedTokenAddressSync,
  10. } from "@solana/spl-token";
  11. import {
  12. Keypair,
  13. Transaction,
  14. sendAndConfirmTransaction,
  15. SendTransactionError,
  16. } from "@solana/web3.js";
  17. import type { TransferHook } from "../target/types/transfer_hook";
  18. import { expect } from "chai";
  19. import chai from "chai";
  20. import chaiAsPromised from "chai-as-promised";
  21. chai.use(chaiAsPromised);
  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 = 2;
  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 with Transfer Hook Extension", async () => {
  50. const transactionSignature = await program.methods
  51. .initialize(decimals)
  52. .accounts({ mintAccount: mint.publicKey })
  53. .signers([mint])
  54. .rpc({ skipPreflight: true });
  55. console.log("Your transaction signature", transactionSignature);
  56. });
  57. // Create the two token accounts for the transfer-hook enabled mint
  58. // Fund the sender token account with 100 tokens
  59. it("Create Token Accounts and Mint Tokens", async () => {
  60. // 100 tokens
  61. const amount = 100 * 10 ** decimals;
  62. const transaction = new Transaction().add(
  63. createAssociatedTokenAccountInstruction(
  64. wallet.publicKey,
  65. sourceTokenAccount,
  66. wallet.publicKey,
  67. mint.publicKey,
  68. TOKEN_2022_PROGRAM_ID,
  69. ASSOCIATED_TOKEN_PROGRAM_ID
  70. ),
  71. createAssociatedTokenAccountInstruction(
  72. wallet.publicKey,
  73. destinationTokenAccount,
  74. recipient.publicKey,
  75. mint.publicKey,
  76. TOKEN_2022_PROGRAM_ID,
  77. ASSOCIATED_TOKEN_PROGRAM_ID
  78. ),
  79. createMintToInstruction(
  80. mint.publicKey,
  81. sourceTokenAccount,
  82. wallet.publicKey,
  83. amount,
  84. [],
  85. TOKEN_2022_PROGRAM_ID
  86. )
  87. );
  88. const txSig = await sendAndConfirmTransaction(
  89. connection,
  90. transaction,
  91. [wallet.payer],
  92. { skipPreflight: true }
  93. );
  94. console.log(`Transaction Signature: ${txSig}`);
  95. });
  96. // Account to store extra accounts required by the transfer hook instruction
  97. it("Create ExtraAccountMetaList Account", async () => {
  98. const initializeExtraAccountMetaListInstruction = await program.methods
  99. .initializeExtraAccountMetaList()
  100. .accounts({
  101. mint: mint.publicKey,
  102. })
  103. .instruction();
  104. const transaction = new Transaction().add(
  105. initializeExtraAccountMetaListInstruction
  106. );
  107. const txSig = await sendAndConfirmTransaction(
  108. provider.connection,
  109. transaction,
  110. [wallet.payer],
  111. { skipPreflight: true, commitment: "confirmed" }
  112. );
  113. console.log("Transaction Signature:", txSig);
  114. });
  115. it("Transfer Hook with Extra Account Meta", async () => {
  116. // 1 tokens
  117. const amount = 1 * 10 ** decimals;
  118. const bigIntAmount = BigInt(amount);
  119. // Standard token transfer instruction
  120. const transferInstruction =
  121. await createTransferCheckedWithTransferHookInstruction(
  122. connection,
  123. sourceTokenAccount,
  124. mint.publicKey,
  125. destinationTokenAccount,
  126. wallet.publicKey,
  127. bigIntAmount,
  128. decimals,
  129. [],
  130. "confirmed",
  131. TOKEN_2022_PROGRAM_ID
  132. );
  133. const transaction = new Transaction().add(transferInstruction);
  134. const txSig = await sendAndConfirmTransaction(
  135. connection,
  136. transaction,
  137. [wallet.payer],
  138. { skipPreflight: true }
  139. );
  140. console.log("Transfer Signature:", txSig);
  141. });
  142. it("Try call transfer hook without transfer", async () => {
  143. const transferHookIx = await program.methods
  144. .transferHook(new anchor.BN(1))
  145. .accounts({
  146. sourceToken: sourceTokenAccount,
  147. mint: mint.publicKey,
  148. destinationToken: destinationTokenAccount,
  149. owner: wallet.publicKey,
  150. })
  151. .instruction();
  152. const transaction = new Transaction().add(transferHookIx);
  153. const sendPromise = sendAndConfirmTransaction(
  154. connection,
  155. transaction,
  156. [wallet.payer],
  157. { skipPreflight: false }
  158. );
  159. await expect(sendPromise).to.eventually.be.rejectedWith(
  160. SendTransactionError,
  161. program.idl.errors[0].msg
  162. );
  163. });
  164. });