123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- import * as anchor from '@coral-xyz/anchor';
- import type { Program } from '@coral-xyz/anchor';
- import {
- ASSOCIATED_TOKEN_PROGRAM_ID,
- ExtensionType,
- TOKEN_2022_PROGRAM_ID,
- createAssociatedTokenAccountInstruction,
- createInitializeMintInstruction,
- createInitializeTransferHookInstruction,
- createMintToInstruction,
- createTransferCheckedWithTransferHookInstruction,
- getAssociatedTokenAddressSync,
- getMintLen,
- } from '@solana/spl-token';
- import { Keypair, PublicKey, SendTransactionError, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
- import { BN } from 'bn.js';
- import { expect } from 'chai';
- import chai from 'chai';
- import chaiAsPromised from 'chai-as-promised';
- import type { TransferHook } from '../target/types/transfer_hook';
- chai.use(chaiAsPromised);
- describe('transfer-hook', () => {
- // Configure the client to use the local cluster.
- const provider = anchor.AnchorProvider.env();
- anchor.setProvider(provider);
- const program = anchor.workspace.TransferHook as Program<TransferHook>;
- const wallet = provider.wallet as anchor.Wallet;
- const connection = provider.connection;
- // Generate keypair to use as address for the transfer-hook enabled mint
- const mint = new Keypair();
- const decimals = 9;
- // Sender token account address
- const sourceTokenAccount = getAssociatedTokenAddressSync(
- mint.publicKey,
- wallet.publicKey,
- false,
- TOKEN_2022_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID,
- );
- // Recipient token account address
- const recipient = Keypair.generate();
- const destinationTokenAccount = getAssociatedTokenAddressSync(
- mint.publicKey,
- recipient.publicKey,
- false,
- TOKEN_2022_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID,
- );
- // ExtraAccountMetaList address
- // Store extra accounts required by the custom transfer hook instruction
- const [extraAccountMetaListPDA] = PublicKey.findProgramAddressSync(
- [Buffer.from('extra-account-metas'), mint.publicKey.toBuffer()],
- program.programId,
- );
- const [counterPDA] = PublicKey.findProgramAddressSync([Buffer.from('counter')], program.programId);
- it('Create Mint Account with Transfer Hook Extension', async () => {
- const extensions = [ExtensionType.TransferHook];
- const mintLen = getMintLen(extensions);
- const lamports = await provider.connection.getMinimumBalanceForRentExemption(mintLen);
- const transaction = new Transaction().add(
- SystemProgram.createAccount({
- fromPubkey: wallet.publicKey,
- newAccountPubkey: mint.publicKey,
- space: mintLen,
- lamports: lamports,
- programId: TOKEN_2022_PROGRAM_ID,
- }),
- createInitializeTransferHookInstruction(
- mint.publicKey,
- wallet.publicKey,
- program.programId, // Transfer Hook Program ID
- TOKEN_2022_PROGRAM_ID,
- ),
- createInitializeMintInstruction(mint.publicKey, decimals, wallet.publicKey, null, TOKEN_2022_PROGRAM_ID),
- );
- const txSig = await sendAndConfirmTransaction(provider.connection, transaction, [wallet.payer, mint], {
- skipPreflight: true,
- commitment: 'finalized',
- });
- const txDetails = await program.provider.connection.getTransaction(txSig, {
- maxSupportedTransactionVersion: 0,
- commitment: 'confirmed',
- });
- console.log(txDetails.meta.logMessages);
- console.log(`Transaction Signature: ${txSig}`);
- });
- // Create the two token accounts for the transfer-hook enabled mint
- // Fund the sender token account with 100 tokens
- it('Create Token Accounts and Mint Tokens', async () => {
- // 100 tokens
- const amount = 100 * 10 ** decimals;
- const transaction = new Transaction().add(
- createAssociatedTokenAccountInstruction(
- wallet.publicKey,
- sourceTokenAccount,
- wallet.publicKey,
- mint.publicKey,
- TOKEN_2022_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID,
- ),
- createAssociatedTokenAccountInstruction(
- wallet.publicKey,
- destinationTokenAccount,
- recipient.publicKey,
- mint.publicKey,
- TOKEN_2022_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID,
- ),
- createMintToInstruction(mint.publicKey, sourceTokenAccount, wallet.publicKey, amount, [], TOKEN_2022_PROGRAM_ID),
- );
- const txSig = await sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: true });
- console.log(`Transaction Signature: ${txSig}`);
- });
- // Account to store extra accounts required by the transfer hook instruction
- it('Create ExtraAccountMetaList Account', async () => {
- const initializeExtraAccountMetaListInstruction = await program.methods
- .initializeExtraAccountMetaList()
- .accounts({
- mint: mint.publicKey,
- })
- .instruction();
- const transaction = new Transaction().add(initializeExtraAccountMetaListInstruction);
- const txSig = await sendAndConfirmTransaction(provider.connection, transaction, [wallet.payer], { skipPreflight: true, commitment: 'confirmed' });
- console.log('Transaction Signature:', txSig);
- });
- it('Transfer Hook with Extra Account Meta', async () => {
- // 1 tokens
- const amount = 1 * 10 ** decimals;
- const amountBigInt = BigInt(amount);
- const transferInstructionWithHelper = await createTransferCheckedWithTransferHookInstruction(
- connection,
- sourceTokenAccount,
- mint.publicKey,
- destinationTokenAccount,
- wallet.publicKey,
- amountBigInt,
- decimals,
- [],
- 'confirmed',
- TOKEN_2022_PROGRAM_ID,
- );
- console.log(`Extra accounts meta: ${extraAccountMetaListPDA}`);
- console.log(`Counter PDA: ${counterPDA}`);
- console.log(`Transfer Instruction: ${JSON.stringify(transferInstructionWithHelper)}`);
- const transaction = new Transaction().add(transferInstructionWithHelper);
- const txSig = await sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: true });
- console.log('Transfer Signature:', txSig);
- });
- it('Try call transfer hook without transfer', async () => {
- const transferHookIx = await program.methods
- .transferHook(new BN(1))
- .accounts({
- sourceToken: sourceTokenAccount,
- mint: mint.publicKey,
- destinationToken: destinationTokenAccount,
- owner: wallet.publicKey,
- })
- .instruction();
- const transaction = new Transaction().add(transferHookIx);
- const sendPromise = sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: false });
- await expect(sendPromise).to.eventually.be.rejectedWith(SendTransactionError, program.idl.errors[1].msg);
- });
- });
|