Что такое комиссии за перевод?
Расширение минта TransferFeeConfig Token Extensions Program применяет
комиссию к каждому переводу для этого минта.
Во время перевода:
- Переведённая сумма уменьшается на настроенную комиссию
- Удержанная комиссия отслеживается в целевом аккаунте токена
- Удержанные комиссии могут быть выведены напрямую из аккаунтов токенов или собраны в минт
withdraw_withheld_authorityможет переместить собранные комиссии в аккаунт токена получателя комиссий
Расширения аккаунта токена
Когда аккаунт токена инициализируется для минта с TransferFeeConfig,
аккаунт токена инициализируется с TransferFeeAmount. Когда аккаунт
токена создаётся через Associated Token
Program,
вычисляется необходимый размер аккаунта, и аккаунт токена создаётся с
требуемым размером и необходимыми для освобождения от rent ламортами.
Как создать минт с комиссией за перевод
Чтобы создать минт с комиссией за перевод:
- Вычислите размер аккаунта минта и необходимый rent для минта и расширения
TransferFeeConfig. - Создайте аккаунт минта с помощью
CreateAccount, инициализируйтеTransferFeeConfigи инициализируйте минт с помощьюInitializeMint. - Создайте аккаунты токенов для минта.
TransferFeeAmountавтоматически включается для аккаунтов токенов. - Используйте
TransferCheckedWithFeeдля перевода токенов и проверки ожидаемой комиссии.TransferCheckedтакже работает и автоматически удерживает настроенную комиссию на целевом аккаунте токена. - Используйте
WithdrawWithheldTokensFromAccountsдля перемещения удержанных комиссий напрямую из аккаунтов токенов в аккаунт токена получателя комиссий, подписанныйwithdraw_withheld_authorityминта. - Используйте не требующую разрешений
HarvestWithheldTokensToMintдля перемещения удержанных комиссий из аккаунтов токенов в аккаунт минта, затем используйтеWithdrawWithheldTokensFromMintдля вывода удержанных комиссий аккаунта минта в аккаунт токена получателя комиссий, подписанныйwithdraw_withheld_authorityминта. - Используйте
SetTransferFeeдля обновления конфигурации следующей комиссии за перевод, которая вступит в силу через две эпохи.
Рассчитать размер аккаунта
Рассчитайте размер mint account для базового минта плюс расширение
TransferFeeConfig. Это размер, используемый в CreateAccount.
Рассчитать rent
Рассчитайте rent, используя размер, необходимый для минта плюс расширение
TransferFeeConfig.
Создать mint account
Создайте mint account с рассчитанным пространством и lamport.
Инициализировать TransferFeeConfig
Инициализируйте расширение TransferFeeConfig на минте.
Инициализировать минт
Инициализируйте минт с помощью InitializeMint в той же транзакции.
Создать token account и выпустить токены
Создайте token account для источника, получателей и получателя комиссии, затем выпустите токены на token account источника.
Перевод с TransferCheckedWithFee
Переведите токены с помощью TransferCheckedWithFee и проверьте ожидаемую
комиссию.
Перевод с TransferChecked
Переведите токены с помощью TransferChecked. Настроенная комиссия
по-прежнему удерживается на целевом token account.
Вывод удержанных комиссий из token accounts
Используйте WithdrawWithheldTokensFromAccounts для перемещения удержанных
комиссий непосредственно из token accounts на token account получателя комиссии.
Сбор удержанных комиссий в mint
Используйте HarvestWithheldTokensToMint для перемещения удержанных
комиссий из token accounts в аккумулятор удержанных средств mint account.
Вывод удержанных комиссий из mint
Используйте WithdrawWithheldTokensFromMint для перемещения собранных
комиссий из mint account на token account получателя комиссии.
Обновление следующей комиссии за перевод
Используйте SetTransferFee для обновления конфигурации следующей комиссии
за перевод.
Порядок инструкций
InitializeTransferFeeConfig должна идти перед InitializeMint.
CreateAccount, InitializeTransferFeeConfig и InitializeMint
должны быть включены в одну транзакцию.
Справочник исходного кода
| Элемент | Описание | Источник |
|---|---|---|
TransferFeeConfig | Расширение mint, которое хранит полномочия на сборы, накопленную удержанную сумму и старую и новую конфигурации комиссий за перевод. | Источник |
TransferFee | Структура конфигурации сборов, хранящаяся в TransferFeeConfig, которая определяет epoch вступления в силу, максимальный сбор и сбор в базисных пунктах. | Источник |
TransferFeeAmount | Расширение token account, которое хранит сумму, удержанную во время переводов для аккаунта токенов. | Источник |
TransferFeeInstruction::InitializeTransferFeeConfig | Инструкция, которая инициализирует настройки комиссии за перевод перед InitializeMint. | Источник |
TransferFeeInstruction::TransferCheckedWithFee | Инструкция перевода, которая проверяет соответствие предоставленной вызывающей стороной комиссии текущей конфигурации комиссии за перевод mint. | Источник |
TransferFeeInstruction::WithdrawWithheldTokensFromAccounts | Инструкция, которая перемещает удержанные сборы напрямую из аккаунтов токенов в аккаунт токенов получателя сборов, подписанная withdraw_withheld_authority mint. | Источник |
TransferFeeInstruction::HarvestWithheldTokensToMint | Инструкция без требования разрешений, которая перемещает удержанные сборы из аккаунтов токенов в накопитель удержанных сборов mint account. | Источник |
TransferFeeInstruction::WithdrawWithheldTokensFromMint | Инструкция, которая выводит удержанные сборы mint account в аккаунт токенов получателя сборов, подписанная withdraw_withheld_authority mint. | Источник |
TransferFeeInstruction::SetTransferFee | Инструкция, которая обновляет новую конфигурацию комиссии за перевод, вступающую в силу через два epoch. | Источник |
process_initialize_transfer_fee_config | Логика процессора, которая инициализирует расширение TransferFeeConfig на неинициализированном mint и устанавливает старую и новую конфигурации комиссии за перевод в начальную комиссию. | Источник |
process_withdraw_withheld_tokens_from_accounts | Логика процессора, которая проверяет withdraw_withheld_authority и перемещает удержанные сборы из аккаунтов токенов в целевой аккаунт токенов. | Источник |
process_harvest_withheld_tokens_to_mint | Логика процессора, которая собирает удержанные сборы из аккаунтов токенов в накопитель удержанных сборов mint account. | Источник |
process_withdraw_withheld_tokens_from_mint | Логика процессора, которая проверяет withdraw_withheld_authority и перемещает удержанную сумму mint account в целевой аккаунт токенов. | Источник |
process_set_transfer_fee | Логика процессора, которая проверяет полномочия конфигурации комиссии за перевод и обновляет новую конфигурацию комиссии за перевод для вступления в силу через два epoch. | Источник |
get_required_init_account_extensions | Используется для автоматического добавления расширений token account при инициализации аккаунтов токенов на основе расширений, включенных на mint. Для mint с TransferFeeConfig добавляет TransferFeeAmount. | Источник |
Typescript
В приведенном ниже примере Kit используются явные инструкции Token-2022.
Устаревшие примеры с использованием @solana/web3.js и @solana/spl-token
включены для справки.
Kit
import {lamports,createClient,generateKeyPairSigner,unwrapOption} from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { getCreateAccountInstruction } from "@solana-program/system";import {extension,fetchMint,fetchToken,findAssociatedTokenPda,getCreateAssociatedTokenInstructionAsync,getHarvestWithheldTokensToMintInstruction,getInitializeMintInstruction,getInitializeTransferFeeConfigInstruction,getMintSize,getMintToCheckedInstruction,getSetTransferFeeInstruction,getTransferCheckedWithFeeInstruction,getWithdrawWithheldTokensFromAccountsInstruction,getWithdrawWithheldTokensFromMintInstruction,isExtension,TOKEN_2022_PROGRAM_ADDRESS} from "@solana-program/token-2022";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const recipientA = await generateKeyPairSigner();const recipientB = await generateKeyPairSigner();const feeReceiver = await generateKeyPairSigner();const transferFeeConfigExtension = extension("TransferFeeConfig", {transferFeeConfigAuthority: client.payer.address,withdrawWithheldAuthority: client.payer.address,withheldAmount: 0n,olderTransferFee: {epoch: 0n,maximumFee: 10n,transferFeeBasisPoints: 150},newerTransferFee: {epoch: 0n,maximumFee: 10n,transferFeeBasisPoints: 150}});const mintSpace = BigInt(getMintSize([transferFeeConfigExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer, // Account funding account creation.newAccount: mint, // New mint account to create.lamports: mintRent, // Lamports funding the mint account rent.space: mintSpace, // Account size in bytes for the mint plus TransferFeeConfig.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.}),getInitializeTransferFeeConfigInstruction({mint: mint.address, // Mint account that stores the TransferFeeConfig extension.transferFeeConfigAuthority: client.payer.address, // Authority allowed to update the transfer fee later.withdrawWithheldAuthority: client.payer.address, // Value stored in the mint's `withdraw_withheld_authority` field.transferFeeBasisPoints: 150, // Transfer fee in basis points.maximumFee: 10n // Maximum fee charged on each transfer.}),getInitializeMintInstruction({mint: mint.address, // Mint account to initialize.decimals: 2, // Number of decimals for the token.mintAuthority: client.payer.address, // Authority allowed to mint new tokens.freezeAuthority: client.payer.address // Authority allowed to freeze token accounts.})]);const [sourceToken] = await findAssociatedTokenPda({mint: mint.address,owner: client.payer.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [destinationAToken] = await findAssociatedTokenPda({mint: mint.address,owner: recipientA.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [destinationBToken] = await findAssociatedTokenPda({mint: mint.address,owner: recipientB.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [feeReceiverToken] = await findAssociatedTokenPda({mint: mint.address,owner: feeReceiver.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});await client.sendTransaction([await getCreateAssociatedTokenInstructionAsync({payer: client.payer,mint: mint.address,owner: client.payer.address}),await getCreateAssociatedTokenInstructionAsync({payer: client.payer,mint: mint.address,owner: recipientA.address}),await getCreateAssociatedTokenInstructionAsync({payer: client.payer,mint: mint.address,owner: recipientB.address}),await getCreateAssociatedTokenInstructionAsync({payer: client.payer,mint: mint.address,owner: feeReceiver.address}),getMintToCheckedInstruction({mint: mint.address,token: sourceToken,mintAuthority: client.payer,amount: 1_000n,decimals: 2})]);await client.sendTransaction([getTransferCheckedWithFeeInstruction({source: sourceToken, // Token account sending the transfer.mint: mint.address, // Mint with the transfer fee configuration.destination: destinationAToken, // Token account receiving the transfer.authority: client.payer, // Signer approving the transfer.amount: 200n, // Token amount in base units.decimals: 2, // Decimals defined on the mint.fee: 3n // Expected transfer fee for this transfer.})]);await client.sendTransaction([getWithdrawWithheldTokensFromAccountsInstruction({mint: mint.address, // Mint with the transfer fee configuration.feeReceiver: feeReceiverToken, // Token account receiving the withdrawn fees.withdrawWithheldAuthority: client.payer, // Signer matching the mint's `withdraw_withheld_authority`.numTokenAccounts: 1, // Number of token accounts listed in `sources`.sources: [destinationAToken] // Token accounts to withdraw withheld fees from.})]);await client.sendTransaction([getTransferCheckedWithFeeInstruction({source: sourceToken, // Token account sending the transfer.mint: mint.address, // Mint with the transfer fee configuration.destination: destinationBToken, // Token account receiving the transfer.authority: client.payer, // Signer approving the transfer.amount: 200n, // Token amount in base units.decimals: 2, // Decimals defined on the mint.fee: 3n // Expected transfer fee for this transfer.})]);await client.sendTransaction([getHarvestWithheldTokensToMintInstruction({mint: mint.address, // Mint that collects harvested withheld fees.sources: [destinationBToken] // Token accounts to harvest withheld fees from.})]);await client.sendTransaction([getWithdrawWithheldTokensFromMintInstruction({mint: mint.address, // Mint storing harvested withheld fees.feeReceiver: feeReceiverToken, // Token account receiving withdrawn fees.withdrawWithheldAuthority: client.payer // Signer matching the mint's `withdraw_withheld_authority`.}),getSetTransferFeeInstruction({mint: mint.address, // Mint whose next transfer fee configuration is updated.transferFeeConfigAuthority: client.payer, // Signer authorized to update the transfer fee later.transferFeeBasisPoints: 250, // New transfer fee in basis points.maximumFee: 25n // New maximum fee for the next transfer fee configuration.})]);const destinationAAccount = await fetchToken(client.rpc, destinationAToken);const destinationBAccount = await fetchToken(client.rpc, destinationBToken);const feeReceiverAccount = await fetchToken(client.rpc, feeReceiverToken);const mintAccount = await fetchMint(client.rpc, mint.address);const transferFeeConfig = (unwrapOption(mintAccount.data.extensions) ?? []).find((item) => isExtension("TransferFeeConfig", item));console.log("Mint Address:", mint.address);console.dir({destinationA: {amount: destinationAAccount.data.amount,extensions: destinationAAccount.data.extensions},destinationB: {amount: destinationBAccount.data.amount,extensions: destinationBAccount.data.extensions},feeReceiver: {amount: feeReceiverAccount.data.amount,extensions: feeReceiverAccount.data.extensions},transferFeeConfig},{ depth: null });
Web3.js
import {Connection,Keypair,LAMPORTS_PER_SOL,sendAndConfirmTransaction,SystemProgram,Transaction} from "@solana/web3.js";import {ASSOCIATED_TOKEN_PROGRAM_ID,ExtensionType,createAssociatedTokenAccountInstruction,createHarvestWithheldTokensToMintInstruction,createInitializeMintInstruction,createInitializeTransferFeeConfigInstruction,createMintToCheckedInstruction,createSetTransferFeeInstruction,createTransferCheckedWithFeeInstruction,createWithdrawWithheldTokensFromAccountsInstruction,createWithdrawWithheldTokensFromMintInstruction,getAccount,getAssociatedTokenAddressSync,getMint,getMintLen,getTransferFeeAmount,getTransferFeeConfig,TOKEN_2022_PROGRAM_ID} from "@solana/spl-token";const connection = new Connection("http://localhost:8899", "confirmed");const latestBlockhash = await connection.getLatestBlockhash();const feePayer = Keypair.generate();const recipientA = Keypair.generate();const recipientB = Keypair.generate();const feeReceiver = Keypair.generate();const airdropSignature = await connection.requestAirdrop(feePayer.publicKey,5 * LAMPORTS_PER_SOL);await connection.confirmTransaction({blockhash: latestBlockhash.blockhash,lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,signature: airdropSignature});const mint = Keypair.generate();const transferFeeConfigExtensions = [ExtensionType.TransferFeeConfig];const mintLen = getMintLen(transferFeeConfigExtensions);const mintRent = await connection.getMinimumBalanceForRentExemption(mintLen);const sourceToken = getAssociatedTokenAddressSync(mint.publicKey,feePayer.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);const destinationAToken = getAssociatedTokenAddressSync(mint.publicKey,recipientA.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);const destinationBToken = getAssociatedTokenAddressSync(mint.publicKey,recipientB.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);const feeReceiverToken = getAssociatedTokenAddressSync(mint.publicKey,feeReceiver.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);await sendAndConfirmTransaction(connection,new Transaction().add(SystemProgram.createAccount({fromPubkey: feePayer.publicKey, // Account funding account creation.newAccountPubkey: mint.publicKey, // New mint account to create.lamports: mintRent, // Lamports funding the mint account rent.space: mintLen, // Account size in bytes for the mint plus TransferFeeConfig.programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.}),createInitializeTransferFeeConfigInstruction(mint.publicKey, // Mint account that stores the TransferFeeConfig extension.feePayer.publicKey, // Authority allowed to update the transfer fee later.feePayer.publicKey, // Value stored in the mint's `withdraw_withheld_authority` field.150, // Transfer fee in basis points.10n, // Maximum fee charged on each transfer.TOKEN_2022_PROGRAM_ID // Token program that owns the mint.),createInitializeMintInstruction(mint.publicKey, // Mint account to initialize.2, // Number of decimals for the token.feePayer.publicKey, // Authority allowed to mint new tokens.feePayer.publicKey, // Authority allowed to freeze token accounts.TOKEN_2022_PROGRAM_ID // Program that owns the mint account.)),[feePayer, mint],{ commitment: "confirmed" });await sendAndConfirmTransaction(connection,new Transaction().add(createAssociatedTokenAccountInstruction(feePayer.publicKey,sourceToken,feePayer.publicKey,mint.publicKey,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID),createAssociatedTokenAccountInstruction(feePayer.publicKey,destinationAToken,recipientA.publicKey,mint.publicKey,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID),createAssociatedTokenAccountInstruction(feePayer.publicKey,destinationBToken,recipientB.publicKey,mint.publicKey,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID),createAssociatedTokenAccountInstruction(feePayer.publicKey,feeReceiverToken,feeReceiver.publicKey,mint.publicKey,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID),createMintToCheckedInstruction(mint.publicKey,sourceToken,feePayer.publicKey,1_000,2,[],TOKEN_2022_PROGRAM_ID)),[feePayer],{ commitment: "confirmed" });await sendAndConfirmTransaction(connection,new Transaction().add(createTransferCheckedWithFeeInstruction(sourceToken, // Token account sending the transfer.mint.publicKey, // Mint with the transfer fee configuration.destinationAToken, // Token account receiving the transfer.feePayer.publicKey, // Signer approving the transfer.200n, // Token amount in base units.2, // Decimals defined on the mint.3n, // Expected transfer fee for this transfer.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that processes the transfer.)),[feePayer],{ commitment: "confirmed" });await sendAndConfirmTransaction(connection,new Transaction().add(createWithdrawWithheldTokensFromAccountsInstruction(mint.publicKey, // Mint with the transfer fee configuration.feeReceiverToken, // Token account receiving the withdrawn fees.feePayer.publicKey, // Signer matching the mint's `withdraw_withheld_authority`.[], // Additional multisig signers.[destinationAToken], // Token accounts to withdraw withheld fees from.TOKEN_2022_PROGRAM_ID // Token program that processes the withdraw.)),[feePayer],{ commitment: "confirmed" });await sendAndConfirmTransaction(connection,new Transaction().add(createTransferCheckedWithFeeInstruction(sourceToken, // Token account sending the transfer.mint.publicKey, // Mint with the transfer fee configuration.destinationBToken, // Token account receiving the transfer.feePayer.publicKey, // Signer approving the transfer.200n, // Token amount in base units.2, // Decimals defined on the mint.3n, // Expected transfer fee for this transfer.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that processes the transfer.)),[feePayer],{ commitment: "confirmed" });await sendAndConfirmTransaction(connection,new Transaction().add(createHarvestWithheldTokensToMintInstruction(mint.publicKey, // Mint that collects harvested withheld fees.[destinationBToken], // Token accounts to harvest withheld fees from.TOKEN_2022_PROGRAM_ID // Token program that processes the harvest.)),[feePayer],{ commitment: "confirmed" });await sendAndConfirmTransaction(connection,new Transaction().add(createWithdrawWithheldTokensFromMintInstruction(mint.publicKey, // Mint storing harvested withheld fees.feeReceiverToken, // Token account receiving withdrawn fees.feePayer.publicKey, // Signer matching the mint's `withdraw_withheld_authority`.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that processes the withdraw.),createSetTransferFeeInstruction(mint.publicKey, // Mint whose next transfer fee configuration is updated.feePayer.publicKey, // Authority allowed to update the transfer fee later.[], // Additional multisig signers.250, // New transfer fee in basis points.25n, // New maximum fee for the next transfer fee configuration.TOKEN_2022_PROGRAM_ID // Token program that owns the mint.)),[feePayer],{ commitment: "confirmed" });const destinationAAccount = await getAccount(connection,destinationAToken,"confirmed",TOKEN_2022_PROGRAM_ID);const destinationBAccount = await getAccount(connection,destinationBToken,"confirmed",TOKEN_2022_PROGRAM_ID);const feeReceiverAccount = await getAccount(connection,feeReceiverToken,"confirmed",TOKEN_2022_PROGRAM_ID);const mintAccount = await getMint(connection,mint.publicKey,"confirmed",TOKEN_2022_PROGRAM_ID);console.log("Mint Address:", mint.publicKey.toBase58());console.log("Destination A Amount:", destinationAAccount.amount.toString());console.log("Destination A Transfer Fee Amount:",getTransferFeeAmount(destinationAAccount));console.log("Destination B Amount:", destinationBAccount.amount.toString());console.log("Destination B Transfer Fee Amount:",getTransferFeeAmount(destinationBAccount));console.log("Fee Receiver Amount:", feeReceiverAccount.amount.toString());console.log("Fee Receiver Transfer Fee Amount:",getTransferFeeAmount(feeReceiverAccount));console.log("Transfer Fee Config:", getTransferFeeConfig(mintAccount));
Rust
use anyhow::Result;use solana_client::nonblocking::rpc_client::RpcClient;use solana_commitment_config::CommitmentConfig;use solana_sdk::{signature::{Keypair, Signer},transaction::Transaction,};use solana_system_interface::instruction::create_account;use spl_associated_token_account_interface::{address::get_associated_token_address_with_program_id,instruction::create_associated_token_account,};use spl_token_2022_interface::{extension::{transfer_fee::{instruction::{harvest_withheld_tokens_to_mint, initialize_transfer_fee_config,set_transfer_fee, transfer_checked_with_fee,withdraw_withheld_tokens_from_accounts, withdraw_withheld_tokens_from_mint,},TransferFeeAmount, TransferFeeConfig,},BaseStateWithExtensions, ExtensionType, StateWithExtensions,},instruction::{initialize_mint, mint_to_checked},state::{Account, Mint},ID as TOKEN_2022_PROGRAM_ID,};#[tokio::main]async fn main() -> Result<()> {let client = RpcClient::new_with_commitment(String::from("http://localhost:8899"),CommitmentConfig::confirmed(),);let fee_payer = Keypair::new();let recipient_a = Keypair::new();let recipient_b = Keypair::new();let fee_receiver = Keypair::new();let decimals = 2;let airdrop_signature = client.request_airdrop(&fee_payer.pubkey(), 5_000_000_000).await?;loop {let confirmed = client.confirm_transaction(&airdrop_signature).await?;if confirmed {break;}}let mint = Keypair::new();let mint_space =ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::TransferFeeConfig])?;let mint_rent = client.get_minimum_balance_for_rent_exemption(mint_space).await?;let mint_blockhash = client.get_latest_blockhash().await?;let mint_transaction = Transaction::new_signed_with_payer(&[create_account(&fee_payer.pubkey(), // Account funding account creation.&mint.pubkey(), // New mint account to create.mint_rent, // Lamports funding the mint account rent.mint_space as u64, // Account size in bytes for the mint plus TransferFeeConfig.&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.),initialize_transfer_fee_config(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.&mint.pubkey(), // Mint account that stores the TransferFeeConfig extension.Some(&fee_payer.pubkey()), // Authority allowed to update the transfer fee later.Some(&fee_payer.pubkey()), // Value stored in the mint's `withdraw_withheld_authority` field.150, // Transfer fee in basis points.10, // Maximum fee charged on each transfer.)?,initialize_mint(&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.&mint.pubkey(), // Mint account to initialize.&fee_payer.pubkey(), // Authority allowed to mint new tokens.Some(&fee_payer.pubkey()), // Authority allowed to freeze token accounts.decimals, // Number of decimals for the token.)?,],Some(&fee_payer.pubkey()),&[&fee_payer, &mint],mint_blockhash,);client.send_and_confirm_transaction(&mint_transaction).await?;let source_token_address = get_associated_token_address_with_program_id(&fee_payer.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let destination_a_token_address = get_associated_token_address_with_program_id(&recipient_a.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let destination_b_token_address = get_associated_token_address_with_program_id(&recipient_b.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let fee_receiver_token_address = get_associated_token_address_with_program_id(&fee_receiver.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let setup_blockhash = client.get_latest_blockhash().await?;let setup_transaction = Transaction::new_signed_with_payer(&[create_associated_token_account(&fee_payer.pubkey(),&fee_payer.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,),create_associated_token_account(&fee_payer.pubkey(),&recipient_a.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,),create_associated_token_account(&fee_payer.pubkey(),&recipient_b.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,),create_associated_token_account(&fee_payer.pubkey(),&fee_receiver.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,),mint_to_checked(&TOKEN_2022_PROGRAM_ID,&mint.pubkey(),&source_token_address,&fee_payer.pubkey(),&[],1_000,decimals,)?,],Some(&fee_payer.pubkey()),&[&fee_payer],setup_blockhash,);client.send_and_confirm_transaction(&setup_transaction).await?;let first_transfer_blockhash = client.get_latest_blockhash().await?;let first_transfer_transaction = Transaction::new_signed_with_payer(&[transfer_checked_with_fee(&TOKEN_2022_PROGRAM_ID, // Token program that processes the transfer.&source_token_address, // Token account sending the transfer.&mint.pubkey(), // Mint with the transfer fee configuration.&destination_a_token_address, // Token account receiving the transfer.&fee_payer.pubkey(), // Signer approving the transfer.&[], // Additional multisig signers.200, // Token amount in base units.decimals, // Decimals defined on the mint.3, // Expected transfer fee for this transfer.)?,],Some(&fee_payer.pubkey()),&[&fee_payer],first_transfer_blockhash,);client.send_and_confirm_transaction(&first_transfer_transaction).await?;let withdraw_accounts_blockhash = client.get_latest_blockhash().await?;let withdraw_accounts_transaction = Transaction::new_signed_with_payer(&[withdraw_withheld_tokens_from_accounts(&TOKEN_2022_PROGRAM_ID, // Token program that processes the withdraw.&mint.pubkey(), // Mint with the transfer fee configuration.&fee_receiver_token_address, // Token account receiving withdrawn fees.&fee_payer.pubkey(), // Signer matching the mint's `withdraw_withheld_authority`.&[], // Additional multisig signers.&[&destination_a_token_address], // Token accounts to withdraw withheld fees from.)?,],Some(&fee_payer.pubkey()),&[&fee_payer],withdraw_accounts_blockhash,);client.send_and_confirm_transaction(&withdraw_accounts_transaction).await?;let second_transfer_blockhash = client.get_latest_blockhash().await?;let second_transfer_transaction = Transaction::new_signed_with_payer(&[transfer_checked_with_fee(&TOKEN_2022_PROGRAM_ID, // Token program that processes the transfer.&source_token_address, // Token account sending the transfer.&mint.pubkey(), // Mint with the transfer fee configuration.&destination_b_token_address, // Token account receiving the transfer.&fee_payer.pubkey(), // Signer approving the transfer.&[], // Additional multisig signers.200, // Token amount in base units.decimals, // Decimals defined on the mint.3, // Expected transfer fee for this transfer.)?,],Some(&fee_payer.pubkey()),&[&fee_payer],second_transfer_blockhash,);client.send_and_confirm_transaction(&second_transfer_transaction).await?;let harvest_blockhash = client.get_latest_blockhash().await?;let harvest_transaction = Transaction::new_signed_with_payer(&[harvest_withheld_tokens_to_mint(&TOKEN_2022_PROGRAM_ID, // Token program that processes the harvest.&mint.pubkey(), // Mint that collects harvested withheld fees.&[&destination_b_token_address], // Token accounts to harvest withheld fees from.)?,],Some(&fee_payer.pubkey()),&[&fee_payer],harvest_blockhash,);client.send_and_confirm_transaction(&harvest_transaction).await?;let finalize_blockhash = client.get_latest_blockhash().await?;let finalize_transaction = Transaction::new_signed_with_payer(&[withdraw_withheld_tokens_from_mint(&TOKEN_2022_PROGRAM_ID, // Token program that processes the withdraw.&mint.pubkey(), // Mint storing harvested withheld fees.&fee_receiver_token_address, // Token account receiving withdrawn fees.&fee_payer.pubkey(), // Signer matching the mint's `withdraw_withheld_authority`.&[], // Additional multisig signers.)?,set_transfer_fee(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.&mint.pubkey(), // Mint whose next transfer fee configuration is updated.&fee_payer.pubkey(), // Authority allowed to update the transfer fee later.&[], // Additional multisig signers.250, // New transfer fee in basis points.25, // New maximum fee for the next transfer fee configuration.)?,],Some(&fee_payer.pubkey()),&[&fee_payer],finalize_blockhash,);client.send_and_confirm_transaction(&finalize_transaction).await?;let destination_a_account = client.get_account(&destination_a_token_address).await?;let destination_a_state = StateWithExtensions::<Account>::unpack(&destination_a_account.data)?;let destination_b_account = client.get_account(&destination_b_token_address).await?;let destination_b_state = StateWithExtensions::<Account>::unpack(&destination_b_account.data)?;let fee_receiver_account = client.get_account(&fee_receiver_token_address).await?;let fee_receiver_state = StateWithExtensions::<Account>::unpack(&fee_receiver_account.data)?;let mint_account = client.get_account(&mint.pubkey()).await?;let mint_state = StateWithExtensions::<Mint>::unpack(&mint_account.data)?;println!("Mint: {}", mint.pubkey());println!("Destination A Balance: {}", destination_a_state.base.amount);println!("Destination A Transfer Fee Amount: {:#?}",destination_a_state.get_extension::<TransferFeeAmount>()?);println!("Destination B Balance: {}", destination_b_state.base.amount);println!("Destination B Transfer Fee Amount: {:#?}",destination_b_state.get_extension::<TransferFeeAmount>()?);println!("Fee Receiver Balance: {}", fee_receiver_state.base.amount);println!("Fee Receiver Transfer Fee Amount: {:#?}",fee_receiver_state.get_extension::<TransferFeeAmount>()?);println!("Transfer Fee Config: {:#?}",mint_state.get_extension::<TransferFeeConfig>()?);Ok(())}
Is this page helpful?