Transfer-Fee
Wie man die TransferFeeExtension implementiert
Die
TransferFeeExtension
ermöglicht die automatische Gebührenerhebung bei jeder Token-Übertragung. Die
Fee sammeln sich in den Empfänger-Token-Konten und können von der
Abhebungsberechtigung, die im mint account festgelegt ist, abgehoben werden.
Typescript
import { getCreateAccountInstruction } from "@solana-program/system";import {extension,getInitializeAccountInstruction,getInitializeMintInstruction,getInitializeTransferFeeConfigInstruction,getMintSize,getTokenSize,TOKEN_2022_PROGRAM_ADDRESS} from "@solana-program/token-2022";import {airdropFactory,appendTransactionMessageInstructions,createSolanaRpc,createSolanaRpcSubscriptions,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners} from "@solana/kit";// Create Connection, local validator in this exampleconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate the authority for the mint (also acts as fee payer)const authority = await generateKeyPairSigner();// Fund authority/fee payerawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: authority.address,lamports: lamports(5_000_000_000n), // 5 SOLcommitment: "confirmed"});// Generate keypair to use as address of mintconst mint = await generateKeyPairSigner();// And a transfer fee config extension.const transferFees = {epoch: 0n,maximumFee: 1_000_000n,transferFeeBasisPoints: 150 // 1.5%};const transferFeeConfigExtension = extension("TransferFeeConfig", {transferFeeConfigAuthority: authority.address,withdrawWithheldAuthority: authority.address,withheldAmount: 0n,newerTransferFee: transferFees,// Used for transitioning configs. Starts by being the same as newerTransferFee.olderTransferFee: transferFees});// Get mint account size with transfer fee extensionconst space = BigInt(getMintSize([transferFeeConfigExtension]));// Get minimum balance for rent exemptionconst rent = await rpc.getMinimumBalanceForRentExemption(space).send();// Get latest blockhash to include in transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Instruction to create new account for mint (token program)// Invokes the system programconst createMintAccountInstruction = getCreateAccountInstruction({payer: authority,newAccount: mint,lamports: rent,space,programAddress: TOKEN_2022_PROGRAM_ADDRESS});// Instruction to initialize transfer fee config extensionconst initializeTransferFeeConfigInstruction =getInitializeTransferFeeConfigInstruction({mint: mint.address,transferFeeConfigAuthority: authority.address,withdrawWithheldAuthority: authority.address,transferFeeBasisPoints: 100, // 1% feemaximumFee: 1_000_000n // Maximum fee of 1 token});// Instruction to initialize mint account data// Invokes the token22 programconst initializeMintInstruction = getInitializeMintInstruction({mint: mint.address,decimals: 6,mintAuthority: authority.address,freezeAuthority: authority.address});// Generate keypair to use as address of token accountconst tokenAccount = await generateKeyPairSigner();// get token account sizeconst tokenAccountLen = BigInt(getTokenSize([transferFeeConfigExtension]));// Get minimum balance for rent exemptionconst tokenAccountRent = await rpc.getMinimumBalanceForRentExemption(tokenAccountLen).send();// Instruction to create new account for the token account// Invokes the system programconst createTokenAccountInstruction = getCreateAccountInstruction({payer: authority,newAccount: tokenAccount,lamports: tokenAccountRent,space: tokenAccountLen,programAddress: TOKEN_2022_PROGRAM_ADDRESS});// Instruction to initialize the created token accountconst initializeTokenAccountInstruction = getInitializeAccountInstruction({account: tokenAccount.address,mint: mint.address,owner: authority.address});console.log("token account", tokenAccount.address); // ! debugconst instructions = [createMintAccountInstruction,initializeTransferFeeConfigInstruction,initializeMintInstruction,createTokenAccountInstruction,initializeTokenAccountInstruction];// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(authority, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions(instructions, tx));// Sign transaction message with all required signersconst signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Send and confirm transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed", skipPreflight: true });// Get transaction signatureconst transactionSignature = getSignatureFromTransaction(signedTransaction);console.log("Mint Address with Transfer Fees:", mint.address.toString());console.log("Token Account:", tokenAccount.address.toString());console.log("Transfer Fee: 1.5% (150 basis points)");console.log("Maximum Fee: 1 token");console.log("Withdraw Authority:", authority.address.toString());console.log("Transaction Signature:", transactionSignature);
Console
Click to execute the code.
Rust
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,withdraw_withheld_tokens_from_mint,},TransferFeeAmount, TransferFeeConfig,},BaseStateWithExtensions, ExtensionType, StateWithExtensions,},instruction::{initialize_mint, mint_to, transfer_checked},state::{Account, Mint},ID as TOKEN_2022_PROGRAM_ID,};#[tokio::main]async fn main() -> Result<()> {// Create connection to local validatorlet client = RpcClient::new_with_commitment(String::from("http://localhost:8899"),CommitmentConfig::confirmed(),);let latest_blockhash = client.get_latest_blockhash().await?;// Generate a new keypair for the fee payerlet fee_payer = Keypair::new();// Airdrop 5 SOL to fee payerlet 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;}}// Generate keypair for the mintlet mint = Keypair::new();// Calculate space for mint with transfer fee extensionlet mint_space =ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::TransferFeeConfig])?;let mint_rent = client.get_minimum_balance_for_rent_exemption(mint_space).await?;// Instruction to create new account for mint (token22)let create_mint_account_instruction = create_account(&fee_payer.pubkey(), // payer&mint.pubkey(), // new account (mint)mint_rent, // lamportsmint_space as u64, // space&TOKEN_2022_PROGRAM_ID, // program id);// Instruction to initialize transfer fee config extensionlet initialize_transfer_fee_config_instruction = initialize_transfer_fee_config(&TOKEN_2022_PROGRAM_ID, // program_id&mint.pubkey(), // mintSome(&fee_payer.pubkey()), // transfer_fee_config_authoritySome(&fee_payer.pubkey()), // withdraw_withheld_authority100, // transfer_fee_basis_points (1% = 100 basis points)1_000_000, // maximum_fee (1 token with 6 decimals))?;// Instruction to initialize mint account datalet initialize_mint_instruction = initialize_mint(&TOKEN_2022_PROGRAM_ID, // program id&mint.pubkey(), // mint&fee_payer.pubkey(), // mint authoritySome(&fee_payer.pubkey()), // freeze authority6, // decimals)?;// Construct transaction with all instructionslet transaction = Transaction::new_signed_with_payer(&[create_mint_account_instruction,initialize_transfer_fee_config_instruction,initialize_mint_instruction,],Some(&fee_payer.pubkey()),&[&fee_payer, &mint],latest_blockhash,);client.send_and_confirm_transaction(&transaction).await?;println!("Mint Address: {}", mint.pubkey());// Fetch mint accountlet mint_account = client.get_account(&mint.pubkey()).await?;// Deserialize the mint account with extensionslet mint_state = StateWithExtensions::<Mint>::unpack(&mint_account.data)?;// Get all extension types enabled on this mintlet extension_types = mint_state.get_extension_types()?;println!("\nExtensions enabled: {:?}", extension_types);// Deserialize the TransferFeeConfig extension datalet transfer_fee_config = mint_state.get_extension::<TransferFeeConfig>()?;println!("\n{:#?}", transfer_fee_config);// Create two associated token accountslet owner1 = Keypair::new();let owner2 = Keypair::new();let ata1 = get_associated_token_address_with_program_id(&owner1.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let ata2 = get_associated_token_address_with_program_id(&owner2.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let create_ata1_instruction = create_associated_token_account(&fee_payer.pubkey(),&owner1.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let create_ata2_instruction = create_associated_token_account(&fee_payer.pubkey(),&owner2.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);// Mint tokens to the first accountlet mint_amount = 10_000_000; // 10 tokens with 6 decimalslet mint_to_instruction = mint_to(&TOKEN_2022_PROGRAM_ID,&mint.pubkey(),&ata1,&fee_payer.pubkey(),&[],mint_amount,)?;// Transfer tokens from ata1 to ata2 (will incur transfer fee)let transfer_amount = 5_000_000; // 5 tokens with 6 decimalslet transfer_instruction = transfer_checked(&TOKEN_2022_PROGRAM_ID,&ata1,&mint.pubkey(),&ata2,&owner1.pubkey(),&[],transfer_amount,6,)?;let transaction = Transaction::new_signed_with_payer(&[create_ata1_instruction,create_ata2_instruction,mint_to_instruction,transfer_instruction,],Some(&fee_payer.pubkey()),&[&fee_payer, &owner1],latest_blockhash,);client.send_and_confirm_transaction(&transaction).await?;// Fetch and deserialize the recipient token accountlet ata2_account = client.get_account(&ata2).await?;let ata2_state = StateWithExtensions::<Account>::unpack(&ata2_account.data)?;// Deserialize the TransferFeeAmount extension (withheld amount)let transfer_fee_amount = ata2_state.get_extension::<TransferFeeAmount>()?;println!("\n1. Transferred {} tokens", transfer_amount,);println!(" Recipient Token Account withheld amount: {}",u64::from(transfer_fee_amount.withheld_amount));// Harvest withheld tokens to mintprintln!("\n2. Harvesting from Token Account to Mint...");let harvest_instruction =harvest_withheld_tokens_to_mint(&TOKEN_2022_PROGRAM_ID, &mint.pubkey(), &[&ata2])?;let harvest_transaction = Transaction::new_signed_with_payer(&[harvest_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],latest_blockhash,);client.send_and_confirm_transaction(&harvest_transaction).await?;// Fetch and deserialize ATA2 again to see the withheld amount after harvestlet ata2_account = client.get_account(&ata2).await?;let ata2_state = StateWithExtensions::<Account>::unpack(&ata2_account.data)?;let transfer_fee_amount_after = ata2_state.get_extension::<TransferFeeAmount>()?;println!(" Token Account withheld amount after harvest: {}",u64::from(transfer_fee_amount_after.withheld_amount));// Fetch and deserialize mint to see the withheld amount now stored on mintlet mint_account = client.get_account(&mint.pubkey()).await?;let mint_state = StateWithExtensions::<Mint>::unpack(&mint_account.data)?;let mint_transfer_fee_config = mint_state.get_extension::<TransferFeeConfig>()?;println!(" Mint withheld amount after harvest: {}",u64::from(mint_transfer_fee_config.withheld_amount));// Create a destination token account for the withdraw authority to receive the feeslet withdraw_destination_ata = get_associated_token_address_with_program_id(&fee_payer.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let create_withdraw_ata_instruction = create_associated_token_account(&fee_payer.pubkey(),&fee_payer.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let create_withdraw_ata_transaction = Transaction::new_signed_with_payer(&[create_withdraw_ata_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],latest_blockhash,);client.send_and_confirm_transaction(&create_withdraw_ata_transaction).await?;// Withdraw withheld tokens from mint to the destination token accountprintln!("\n3. Withdrawing from Mint to Token Account...");let withdraw_instruction = withdraw_withheld_tokens_from_mint(&TOKEN_2022_PROGRAM_ID,&mint.pubkey(),&withdraw_destination_ata,&fee_payer.pubkey(), // withdraw_withheld_authority&[],)?;let withdraw_transaction = Transaction::new_signed_with_payer(&[withdraw_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],latest_blockhash,);client.send_and_confirm_transaction(&withdraw_transaction).await?;// Fetch and deserialize mint to see withheld amount after withdrawlet mint_account = client.get_account(&mint.pubkey()).await?;let mint_state = StateWithExtensions::<Mint>::unpack(&mint_account.data)?;let mint_transfer_fee_config_after = mint_state.get_extension::<TransferFeeConfig>()?;println!(" Mint withheld amount after withdraw: {}",u64::from(mint_transfer_fee_config_after.withheld_amount));// Fetch and deserialize destination account to see received feeslet withdraw_destination_account = client.get_account(&withdraw_destination_ata).await?;let withdraw_destination_state =StateWithExtensions::<Account>::unpack(&withdraw_destination_account.data)?;println!(" Withdraw destination account balance: {}",withdraw_destination_state.base.amount);Ok(())}
Console
Click to execute the code.
Is this page helpful?