Χρεώσεις Μεταφοράς
Πώς να υλοποιήσετε το TransferFeeExtension
Το
TransferFeeExtension
επιτρέπει την αυτόματη συλλογή χρεώσεων σε κάθε μεταφορά token. Οι χρεώσεις
συσσωρεύονται στους λογαριασμούς token του παραλήπτη και μπορούν να αποσυρθούν
από την αρχή απόσυρσης που έχει οριστεί στον mint account.
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?