Taxas de transferência

O Que São Taxas de Transferência?

A extensão de mint TransferFeeConfig do Token Extensions Program aplica uma taxa a cada transferência para esse mint.

No momento da transferência:

  • O valor transferido é reduzido pela taxa configurada
  • A taxa retida é rastreada na token account de destino
  • As taxas retidas podem ser retiradas diretamente das contas de token ou coletadas no mint
  • O withdraw_withheld_authority pode mover as taxas coletadas para uma token account receptora de taxas

Extensões de Token Account

Quando uma token account é inicializada para um mint com TransferFeeConfig, a token account é inicializada com TransferFeeAmount. Quando a token account é criada através do Associated Token Program, o tamanho necessário da conta é calculado e a token account é criada com o tamanho necessário e lamports isentos de rent.

Como Criar um Mint com Taxa de Transferência

Para criar um mint com taxa de transferência:

  1. Calcule o tamanho da mint account e o rent necessário para o mint e a extensão TransferFeeConfig.
  2. Crie a mint account com CreateAccount, inicialize TransferFeeConfig e inicialize o mint com InitializeMint.
  3. Crie contas de token para o mint. TransferFeeAmount é automaticamente habilitado para as contas de token.
  4. Use TransferCheckedWithFee para transferir tokens e verificar a taxa esperada. TransferChecked também funciona e retém automaticamente a taxa configurada na token account de destino.
  5. Use WithdrawWithheldTokensFromAccounts para mover as taxas retidas diretamente das contas de token para uma token account receptora de taxas, assinado pelo withdraw_withheld_authority do mint.
  6. Use HarvestWithheldTokensToMint sem permissão para mover as taxas retidas das contas de token para a mint account e, em seguida, use WithdrawWithheldTokensFromMint para retirar as taxas retidas da mint account para uma token account receptora de taxas, assinado pelo withdraw_withheld_authority do mint.
  7. Use SetTransferFee para atualizar a próxima configuração de taxa de transferência, que entra em vigor a partir de duas epochs depois.

Calcular o tamanho da conta

Calcule o tamanho da conta mint para o mint base mais a extensão TransferFeeConfig. Este é o tamanho usado em CreateAccount.

Calcular o rent

Calcule o rent usando o tamanho necessário para o mint mais a extensão TransferFeeConfig.

Criar a mint account

Crie a mint account com o espaço e lamports calculados.

Inicializar TransferFeeConfig

Inicialize a extensão TransferFeeConfig no mint.

Inicializar o mint

Inicialize o mint com InitializeMint na mesma transação.

Criar token accounts e cunhar tokens

Crie token accounts para a origem, destinatários e receptor de taxas, e depois cunhe tokens para a token account de origem.

Transferir com TransferCheckedWithFee

Transfira tokens com TransferCheckedWithFee e verifique a taxa esperada.

Transferir com TransferChecked

Transfira tokens com TransferChecked. A taxa configurada ainda é retida na token account de destino.

Retirar taxas retidas das token accounts

Use WithdrawWithheldTokensFromAccounts para mover taxas retidas diretamente das token accounts para a token account receptora de taxas.

Coletar taxas retidas para o mint

Use HarvestWithheldTokensToMint para mover taxas retidas das token accounts para o acumulador de retenção da mint account.

Retirar taxas retidas do mint

Use WithdrawWithheldTokensFromMint para mover taxas coletadas da mint account para a token account receptora de taxas.

Atualizar a próxima taxa de transferência

Use SetTransferFee para atualizar a próxima configuração de taxa de transferência.

Calcular o tamanho da conta

Calcule o tamanho da conta mint para o mint base mais a extensão TransferFeeConfig. Este é o tamanho usado em CreateAccount.

Calcular o rent

Calcule o rent usando o tamanho necessário para o mint mais a extensão TransferFeeConfig.

Criar a mint account

Crie a mint account com o espaço e lamports calculados.

Inicializar TransferFeeConfig

Inicialize a extensão TransferFeeConfig no mint.

Inicializar o mint

Inicialize o mint com InitializeMint na mesma transação.

Criar token accounts e cunhar tokens

Crie token accounts para a origem, destinatários e receptor de taxas, e depois cunhe tokens para a token account de origem.

Transferir com TransferCheckedWithFee

Transfira tokens com TransferCheckedWithFee e verifique a taxa esperada.

Transferir com TransferChecked

Transfira tokens com TransferChecked. A taxa configurada ainda é retida na token account de destino.

Retirar taxas retidas das token accounts

Use WithdrawWithheldTokensFromAccounts para mover taxas retidas diretamente das token accounts para a token account receptora de taxas.

Coletar taxas retidas para o mint

Use HarvestWithheldTokensToMint para mover taxas retidas das token accounts para o acumulador de retenção da mint account.

Retirar taxas retidas do mint

Use WithdrawWithheldTokensFromMint para mover taxas coletadas da mint account para a token account receptora de taxas.

Atualizar a próxima taxa de transferência

Use SetTransferFee para atualizar a próxima configuração de taxa de transferência.

Example
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 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]));

Ordem das Instruções

InitializeTransferFeeConfig deve vir antes de InitializeMint. CreateAccount, InitializeTransferFeeConfig e InitializeMint devem ser incluídos na mesma transação.

Referência de Origem

ItemDescriçãoOrigem
TransferFeeConfigExtensão de mint que armazena as autoridades de taxa, o valor retido acumulado e as configurações de taxa de transferência antiga e mais recente.Origem
TransferFeeEstrutura de configuração de taxa armazenada em TransferFeeConfig que define o epoch em que a taxa entra em vigor, a taxa máxima e a taxa em pontos base.Origem
TransferFeeAmountExtensão de token account que armazena o valor retido durante transferências para um token account.Origem
TransferFeeInstruction::InitializeTransferFeeConfigInstrução que inicializa as configurações de taxa de transferência antes de InitializeMint.Origem
TransferFeeInstruction::TransferCheckedWithFeeInstrução de transferência que verifica se a taxa fornecida pelo chamador corresponde à configuração de taxa de transferência atual do mint.Origem
TransferFeeInstruction::WithdrawWithheldTokensFromAccountsInstrução que move taxas retidas diretamente de token accounts para um token account receptor de taxas, assinada pelo withdraw_withheld_authority do mint.Origem
TransferFeeInstruction::HarvestWithheldTokensToMintInstrução sem permissão que move taxas retidas de token accounts para o acumulador de retenção do mint account.Origem
TransferFeeInstruction::WithdrawWithheldTokensFromMintInstrução que retira as taxas retidas do mint account para um token account receptor de taxas, assinada pelo withdraw_withheld_authority do mint.Origem
TransferFeeInstruction::SetTransferFeeInstrução que atualiza a configuração de taxa de transferência mais recente, que entra em vigor a partir de dois epochs depois.Origem
process_initialize_transfer_fee_configLógica do processador que inicializa a extensão TransferFeeConfig em um mint não inicializado e define as configurações de taxa de transferência antiga e mais recente para a taxa inicial.Origem
process_withdraw_withheld_tokens_from_accountsLógica do processador que valida withdraw_withheld_authority e move taxas retidas de token accounts para um token account de destino.Origem
process_harvest_withheld_tokens_to_mintLógica do processador que coleta taxas retidas de token accounts para o acumulador de retenção do mint account.Origem
process_withdraw_withheld_tokens_from_mintLógica do processador que valida withdraw_withheld_authority e move o valor retido do mint account para um token account de destino.Origem
process_set_transfer_feeLógica do processador que valida a autoridade de configuração de taxa de transferência e atualiza a configuração de taxa de transferência mais recente para entrar em vigor a partir de dois epochs depois.Origem
get_required_init_account_extensionsUsado para adicionar automaticamente extensões de token account quando token accounts são inicializados com base nas extensões habilitadas no mint. Para mints TransferFeeConfig, adiciona TransferFeeAmount.Origem

Kit

Estes exemplos usam instruções explícitas do Token-2022.

Instructions
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 }
);
Console
Click to execute the code.

Web3.js v3

Estes exemplos usam a API v3 baseada em classes do @solana/web3.js com @solana-program instruction builders.

Instructions
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
sendAndConfirmTransaction,
SystemProgram,
Transaction
} from "@solana/web3.js";
import { unwrapOption } from "@solana/kit";
import {
findAssociatedTokenPda,
getCreateAssociatedTokenInstruction,
getHarvestWithheldTokensToMintInstruction,
getInitializeMint2Instruction,
getInitializeTransferFeeConfigInstruction,
getMintDecoder,
getMintSize,
getMintToCheckedInstruction,
getSetTransferFeeInstruction,
getTokenDecoder,
getTransferCheckedWithFeeInstruction,
getWithdrawWithheldTokensFromAccountsInstruction,
getWithdrawWithheldTokensFromMintInstruction,
TOKEN_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";
const connection = new Connection("http://localhost:8899", "confirmed");
const latestBlockhash = await connection.getLatestBlockhash();
const feePayer = await Keypair.generate();
const recipientA = await Keypair.generate();
const recipientB = await Keypair.generate();
const feeReceiver = await 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 = await Keypair.generate();
const transferFee = {
epoch: 0n,
maximumFee: 10n,
transferFeeBasisPoints: 150
};
const mintLen = getMintSize([
{
__kind: "TransferFeeConfig",
transferFeeConfigAuthority: feePayer.publicKey.toBase58(),
withdrawWithheldAuthority: feePayer.publicKey.toBase58(),
withheldAmount: 0n,
olderTransferFee: transferFee,
newerTransferFee: transferFee
}
]);
const mintRent = await connection.getMinimumBalanceForRentExemption(mintLen);
const deriveAta = async (owner: PublicKey) => {
const [ata] = await findAssociatedTokenPda({
mint: mint.publicKey.toBase58(),
owner: owner.toBase58(),
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
return ata;
};
const sourceToken = await deriveAta(feePayer.publicKey);
const destinationAToken = await deriveAta(recipientA.publicKey);
const destinationBToken = await deriveAta(recipientB.publicKey);
const feeReceiverToken = await deriveAta(feeReceiver.publicKey);
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: new PublicKey(TOKEN_2022_PROGRAM_ADDRESS) // Program that owns the mint account.
}),
getInitializeTransferFeeConfigInstruction({
mint: mint.publicKey, // Mint account that stores the TransferFeeConfig extension.
transferFeeConfigAuthority: feePayer.publicKey.toBase58(), // Authority allowed to update the transfer fee later.
withdrawWithheldAuthority: feePayer.publicKey.toBase58(), // 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.
}),
getInitializeMint2Instruction({
mint: mint.publicKey, // Mint account to initialize.
decimals: 2, // Number of decimals for the token.
mintAuthority: feePayer.publicKey.toBase58(), // Authority allowed to mint new tokens.
freezeAuthority: feePayer.publicKey.toBase58() // Authority allowed to freeze token accounts.
})
),
[feePayer, mint],
{ commitment: "confirmed" }
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
getCreateAssociatedTokenInstruction({
payer: feePayer,
ata: sourceToken,
owner: feePayer.publicKey,
mint: mint.publicKey
}),
getCreateAssociatedTokenInstruction({
payer: feePayer,
ata: destinationAToken,
owner: recipientA.publicKey,
mint: mint.publicKey
}),
getCreateAssociatedTokenInstruction({
payer: feePayer,
ata: destinationBToken,
owner: recipientB.publicKey,
mint: mint.publicKey
}),
getCreateAssociatedTokenInstruction({
payer: feePayer,
ata: feeReceiverToken,
owner: feeReceiver.publicKey,
mint: mint.publicKey
}),
getMintToCheckedInstruction({
mint: mint.publicKey,
token: sourceToken,
mintAuthority: feePayer,
amount: 1_000,
decimals: 2
})
),
[feePayer],
{ commitment: "confirmed" }
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
getTransferCheckedWithFeeInstruction({
source: sourceToken, // Token account sending the transfer.
mint: mint.publicKey, // Mint with the transfer fee configuration.
destination: destinationAToken, // Token account receiving the transfer.
authority: feePayer, // 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.
})
),
[feePayer],
{ commitment: "confirmed" }
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
getWithdrawWithheldTokensFromAccountsInstruction({
mint: mint.publicKey, // Mint with the transfer fee configuration.
feeReceiver: feeReceiverToken, // Token account receiving the withdrawn fees.
withdrawWithheldAuthority: feePayer, // Signer matching the mint's `withdraw_withheld_authority`.
sources: [destinationAToken], // Token accounts to withdraw withheld fees from.
numTokenAccounts: 1 // Number of token accounts to withdraw from.
})
),
[feePayer],
{ commitment: "confirmed" }
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
getTransferCheckedWithFeeInstruction({
source: sourceToken, // Token account sending the transfer.
mint: mint.publicKey, // Mint with the transfer fee configuration.
destination: destinationBToken, // Token account receiving the transfer.
authority: feePayer, // 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.
})
),
[feePayer],
{ commitment: "confirmed" }
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
getHarvestWithheldTokensToMintInstruction({
mint: mint.publicKey, // Mint that collects harvested withheld fees.
sources: [destinationBToken] // Token accounts to harvest withheld fees from.
})
),
[feePayer],
{ commitment: "confirmed" }
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
getWithdrawWithheldTokensFromMintInstruction({
mint: mint.publicKey, // Mint storing harvested withheld fees.
feeReceiver: feeReceiverToken, // Token account receiving withdrawn fees.
withdrawWithheldAuthority: feePayer // Signer matching the mint's `withdraw_withheld_authority`.
}),
getSetTransferFeeInstruction({
mint: mint.publicKey, // Mint whose next transfer fee configuration is updated.
transferFeeConfigAuthority: feePayer, // Authority allowed 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.
})
),
[feePayer],
{ commitment: "confirmed" }
);
const readTokenAccount = async (address: string) => {
const info = await connection.getAccountInfo(
new PublicKey(address),
"confirmed"
);
return getTokenDecoder().decode(info!.data);
};
type TokenAccount = Awaited<ReturnType<typeof readTokenAccount>>;
const withheldAmountOf = (account: TokenAccount) =>
(unwrapOption(account.extensions) ?? []).find(
(extension) => extension.__kind === "TransferFeeAmount"
);
const destinationAAccount = await readTokenAccount(destinationAToken);
const destinationBAccount = await readTokenAccount(destinationBToken);
const feeReceiverAccount = await readTokenAccount(feeReceiverToken);
const mintAccountInfo = await connection.getAccountInfo(
mint.publicKey,
"confirmed"
);
const mintAccount = getMintDecoder().decode(mintAccountInfo!.data);
console.log("Mint Address:", mint.publicKey.toBase58());
console.log("Destination A Amount:", destinationAAccount.amount.toString());
console.log(
"Destination A Transfer Fee Amount:",
withheldAmountOf(destinationAAccount)
);
console.log("Destination B Amount:", destinationBAccount.amount.toString());
console.log(
"Destination B Transfer Fee Amount:",
withheldAmountOf(destinationBAccount)
);
console.log("Fee Receiver Amount:", feeReceiverAccount.amount.toString());
console.log(
"Fee Receiver Transfer Fee Amount:",
withheldAmountOf(feeReceiverAccount)
);
console.log(
"Transfer Fee Config:",
(unwrapOption(mintAccount.extensions) ?? []).find(
(extension) => extension.__kind === "TransferFeeConfig"
)
);
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,
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(())
}
Console
Click to execute the code.

Web3.js (legado)

Estes exemplos usam o @solana/web3.js legado com @solana/spl-token.

Instructions
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));
Console
Click to execute the code.

Is this page helpful?

Índice

Editar Página
© 2026 Fundação Solana. Todos os direitos reservados.