transfer-hook.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  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 { Keypair, SendTransactionError, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
  12. import { BN } from 'bn.js';
  13. import { expect } from 'chai';
  14. import chai from 'chai';
  15. import chaiAsPromised from 'chai-as-promised';
  16. import type { TransferHook } from '../target/types/transfer_hook';
  17. chai.use(chaiAsPromised);
  18. describe('transfer-hook', () => {
  19. // Configure the client to use the local cluster.
  20. const provider = anchor.AnchorProvider.env();
  21. anchor.setProvider(provider);
  22. const program = anchor.workspace.TransferHook as Program<TransferHook>;
  23. const wallet = provider.wallet as anchor.Wallet;
  24. const connection = provider.connection;
  25. // Generate keypair to use as address for the transfer-hook enabled mint
  26. const mint = new Keypair();
  27. const decimals = 2;
  28. // Sender token account address
  29. const sourceTokenAccount = getAssociatedTokenAddressSync(
  30. mint.publicKey,
  31. wallet.publicKey,
  32. false,
  33. TOKEN_2022_PROGRAM_ID,
  34. ASSOCIATED_TOKEN_PROGRAM_ID,
  35. );
  36. // Recipient token account address
  37. const recipient = Keypair.generate();
  38. const destinationTokenAccount = getAssociatedTokenAddressSync(
  39. mint.publicKey,
  40. recipient.publicKey,
  41. false,
  42. TOKEN_2022_PROGRAM_ID,
  43. ASSOCIATED_TOKEN_PROGRAM_ID,
  44. );
  45. it('Create Mint with Transfer Hook Extension', async () => {
  46. const transactionSignature = await program.methods
  47. .initialize(decimals)
  48. .accounts({ mintAccount: mint.publicKey })
  49. .signers([mint])
  50. .rpc({ skipPreflight: true });
  51. console.log('Your transaction signature', transactionSignature);
  52. });
  53. // Create the two token accounts for the transfer-hook enabled mint
  54. // Fund the sender token account with 100 tokens
  55. it('Create Token Accounts and Mint Tokens', async () => {
  56. // 100 tokens
  57. const amount = 100 * 10 ** decimals;
  58. const transaction = new Transaction().add(
  59. createAssociatedTokenAccountInstruction(
  60. wallet.publicKey,
  61. sourceTokenAccount,
  62. wallet.publicKey,
  63. mint.publicKey,
  64. TOKEN_2022_PROGRAM_ID,
  65. ASSOCIATED_TOKEN_PROGRAM_ID,
  66. ),
  67. createAssociatedTokenAccountInstruction(
  68. wallet.publicKey,
  69. destinationTokenAccount,
  70. recipient.publicKey,
  71. mint.publicKey,
  72. TOKEN_2022_PROGRAM_ID,
  73. ASSOCIATED_TOKEN_PROGRAM_ID,
  74. ),
  75. createMintToInstruction(mint.publicKey, sourceTokenAccount, wallet.publicKey, amount, [], TOKEN_2022_PROGRAM_ID),
  76. );
  77. const txSig = await sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: true });
  78. console.log(`Transaction Signature: ${txSig}`);
  79. });
  80. // Account to store extra accounts required by the transfer hook instruction
  81. it('Create ExtraAccountMetaList Account', async () => {
  82. const initializeExtraAccountMetaListInstruction = await program.methods
  83. .initializeExtraAccountMetaList()
  84. .accounts({
  85. mint: mint.publicKey,
  86. })
  87. .instruction();
  88. const transaction = new Transaction().add(initializeExtraAccountMetaListInstruction);
  89. const txSig = await sendAndConfirmTransaction(provider.connection, transaction, [wallet.payer], { skipPreflight: true, commitment: 'confirmed' });
  90. console.log('Transaction Signature:', txSig);
  91. });
  92. it('Transfer Hook with Extra Account Meta', async () => {
  93. // 1 tokens
  94. const amount = 1 * 10 ** decimals;
  95. const bigIntAmount = BigInt(amount);
  96. // Standard token transfer instruction
  97. const transferInstruction = await createTransferCheckedWithTransferHookInstruction(
  98. connection,
  99. sourceTokenAccount,
  100. mint.publicKey,
  101. destinationTokenAccount,
  102. wallet.publicKey,
  103. bigIntAmount,
  104. decimals,
  105. [],
  106. 'confirmed',
  107. TOKEN_2022_PROGRAM_ID,
  108. );
  109. const transaction = new Transaction().add(transferInstruction);
  110. const txSig = await sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: true });
  111. console.log('Transfer Signature:', txSig);
  112. });
  113. it('Try call transfer hook without transfer', async () => {
  114. const transferHookIx = await program.methods
  115. .transferHook(new BN(1))
  116. .accounts({
  117. sourceToken: sourceTokenAccount,
  118. mint: mint.publicKey,
  119. destinationToken: destinationTokenAccount,
  120. owner: wallet.publicKey,
  121. })
  122. .instruction();
  123. const transaction = new Transaction().add(transferHookIx);
  124. const sendPromise = sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: false });
  125. await expect(sendPromise).to.eventually.be.rejectedWith(SendTransactionError, program.idl.errors[0].msg);
  126. });
  127. });