123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- import * as anchor from "@coral-xyz/anchor";
- import { Program } from "@coral-xyz/anchor";
- import { TransferHook } from "../target/types/transfer_hook";
- import {
- PublicKey,
- SystemProgram,
- Transaction,
- sendAndConfirmTransaction,
- Keypair,
- } from "@solana/web3.js";
- import {
- ExtensionType,
- TOKEN_2022_PROGRAM_ID,
- getMintLen,
- createInitializeMintInstruction,
- createInitializeTransferHookInstruction,
- ASSOCIATED_TOKEN_PROGRAM_ID,
- createAssociatedTokenAccountInstruction,
- createMintToInstruction,
- getAssociatedTokenAddressSync,
- createApproveInstruction,
- createSyncNativeInstruction,
- NATIVE_MINT,
- TOKEN_PROGRAM_ID,
- getAccount,
- getOrCreateAssociatedTokenAccount,
- createTransferCheckedWithTransferHookInstruction,
- getMint,
- getTransferHook,
- getExtraAccountMetaAddress,
- getExtraAccountMetas,
- } from "@solana/spl-token";
- import assert from "assert";
- 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
- );
- // PDA delegate to transfer wSOL tokens from sender
- const [delegatePDA] = PublicKey.findProgramAddressSync(
- [Buffer.from("delegate")],
- program.programId
- );
- // Sender wSOL token account address
- const senderWSolTokenAccount = getAssociatedTokenAddressSync(
- NATIVE_MINT, // mint
- wallet.publicKey // owner
- );
- // Delegate PDA wSOL token account address, to receive wSOL tokens from sender
- const delegateWSolTokenAccount = getAssociatedTokenAddressSync(
- NATIVE_MINT, // mint
- delegatePDA, // owner
- true // allowOwnerOffCurve
- );
- // Create the two WSol token accounts as part of setup
- before(async () => {
- // WSol Token Account for sender
- await getOrCreateAssociatedTokenAccount(
- connection,
- wallet.payer,
- NATIVE_MINT,
- wallet.publicKey
- );
- // WSol Token Account for delegate PDA
- await getOrCreateAssociatedTokenAccount(
- connection,
- wallet.payer,
- NATIVE_MINT,
- delegatePDA,
- true
- );
- });
- 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]
- );
- 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({
- payer: wallet.publicKey,
- extraAccountMetaList: extraAccountMetaListPDA,
- mint: mint.publicKey,
- wsolMint: NATIVE_MINT,
- counterAccount: counterPDA,
- tokenProgram: TOKEN_PROGRAM_ID,
- associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
- })
- .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 bigIntAmount = BigInt(amount);
- // Instruction for sender to fund their WSol token account
- const solTransferInstruction = SystemProgram.transfer({
- fromPubkey: wallet.publicKey,
- toPubkey: senderWSolTokenAccount,
- lamports: amount,
- });
- // Approve delegate PDA to transfer WSol tokens from sender WSol token account
- const approveInstruction = createApproveInstruction(
- senderWSolTokenAccount,
- delegatePDA,
- wallet.publicKey,
- amount,
- [],
- TOKEN_PROGRAM_ID
- );
- // Sync sender WSol token account
- const syncWrappedSolInstruction = createSyncNativeInstruction(
- senderWSolTokenAccount
- );
- const mintInfo = await getMint(connection, mint.publicKey, "confirmed", TOKEN_2022_PROGRAM_ID);
- const transferHook = getTransferHook(mintInfo);
- if (transferHook != null) {
- console.log("Transfer hook not found" + JSON.stringify(transferHook));
- }
- const extraAccountsAccount = getExtraAccountMetaAddress(mint.publicKey, transferHook.programId);
- const extraAccountsInfo = await connection.getAccountInfo(extraAccountsAccount, "confirmed");
- const extraAccountMetas = getExtraAccountMetas(extraAccountsInfo);
- for (const extraAccountMeta of extraAccountMetas) {
- console.log("Extra account meta: " + JSON.stringify(extraAccountMeta));
- }
- // Standard token transfer instruction
- const transferInstruction = await createTransferCheckedWithTransferHookInstruction(
- connection,
- sourceTokenAccount,
- mint.publicKey,
- destinationTokenAccount,
- wallet.publicKey,
- bigIntAmount,
- decimals,
- [],
- "confirmed",
- TOKEN_2022_PROGRAM_ID
- );
- console.log("Pushed keys:", JSON.stringify(transferInstruction.keys));
- const transaction = new Transaction().add(
- solTransferInstruction,
- syncWrappedSolInstruction,
- approveInstruction,
- transferInstruction
- );
-
- const txSig = await sendAndConfirmTransaction(
- connection,
- transaction,
- [wallet.payer],
- { skipPreflight: true }
- );
- console.log("Transfer Signature:", txSig);
- const tokenAccount = await getAccount(connection, delegateWSolTokenAccount);
-
- assert.equal(Number(tokenAccount.amount), amount / 2);
- });
- });
|