transfer-hook.ts 4.9 KB

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