Χρεώσεις Μεταφοράς

Τι Είναι τα Τέλη Μεταφοράς;

Η επέκταση mint TransferFeeConfig του Token Extensions Program εφαρμόζει ένα τέλος σε κάθε μεταφορά για αυτό το mint.

Κατά τη στιγμή της μεταφοράς:

  • Το ποσό που μεταφέρεται μειώνεται κατά το διαμορφωμένο τέλος
  • Το παρακρατημένο τέλος καταγράφεται στον λογαριασμό token προορισμού
  • Τα παρακρατημένα τέλη μπορούν να αποσυρθούν απευθείας από τους λογαριασμούς token ή να συλλεχθούν στο mint
  • Ο withdraw_withheld_authority μπορεί να μεταφέρει τα συλλεγμένα τέλη σε έναν λογαριασμό token παραλήπτη τελών

Επεκτάσεις Λογαριασμού Token

Όταν ένας λογαριασμός token αρχικοποιείται για ένα mint με TransferFeeConfig, ο λογαριασμός token αρχικοποιείται με TransferFeeAmount. Όταν ο λογαριασμός token δημιουργείται μέσω του Associated Token Program, υπολογίζεται το απαιτούμενο μέγεθος λογαριασμού και ο λογαριασμός token δημιουργείται με το απαιτούμενο μέγεθος και τα lamport απαλλαγμένα από rent.

Πώς να Δημιουργήσετε ένα Mint με Τέλη Μεταφοράς

Για να δημιουργήσετε ένα mint με τέλη μεταφοράς:

  1. Υπολογίστε το μέγεθος του λογαριασμού mint και το rent που απαιτείται για το mint και την επέκταση TransferFeeConfig.
  2. Δημιουργήστε τον λογαριασμό mint με CreateAccount, αρχικοποιήστε TransferFeeConfig και αρχικοποιήστε το mint με InitializeMint.
  3. Δημιουργήστε λογαριασμούς token για το mint. Η TransferFeeAmount ενεργοποιείται αυτόματα για τους λογαριασμούς token.
  4. Χρησιμοποιήστε TransferCheckedWithFee για να μεταφέρετε tokens και να επαληθεύσετε το αναμενόμενο τέλος. Η TransferChecked λειτουργεί επίσης και παρακρατεί αυτόματα το διαμορφωμένο τέλος στον λογαριασμό token προορισμού.
  5. Χρησιμοποιήστε WithdrawWithheldTokensFromAccounts για να μεταφέρετε τα παρακρατημένα τέλη απευθείας από τους λογαριασμούς token σε έναν λογαριασμό token παραλήπτη τελών, υπογεγραμμένο από τον withdraw_withheld_authority του mint.
  6. Χρησιμοποιήστε HarvestWithheldTokensToMint χωρίς απαίτηση δικαιωμάτων για να μεταφέρετε τα παρακρατημένα τέλη από τους λογαριασμούς token στον λογαριασμό mint, και στη συνέχεια χρησιμοποιήστε WithdrawWithheldTokensFromMint για να αποσύρετε τα παρακρατημένα τέλη του λογαριασμού mint σε έναν λογαριασμό token παραλήπτη τελών, υπογεγραμμένο από τον withdraw_withheld_authority του mint.
  7. Χρησιμοποιήστε SetTransferFee για να ενημερώσετε την επόμενη διαμόρφωση τελών μεταφοράς, η οποία τίθεται σε ισχύ δύο epochs αργότερα.

Υπολογισμός μεγέθους λογαριασμού

Υπολογίστε το μέγεθος του λογαριασμού mint για το βασικό mint συν την επέκταση TransferFeeConfig. Αυτό είναι το μέγεθος που χρησιμοποιείται στο CreateAccount.

Υπολογισμός rent

Υπολογίστε το rent χρησιμοποιώντας το μέγεθος που απαιτείται για το mint συν την επέκταση TransferFeeConfig.

Δημιουργία λογαριασμού mint

Δημιουργήστε τον λογαριασμό mint με τον υπολογισμένο χώρο και τα lamports.

Αρχικοποίηση TransferFeeConfig

Αρχικοποιήστε την επέκταση TransferFeeConfig στο mint.

Αρχικοποίηση του mint

Αρχικοποιήστε το mint με InitializeMint στην ίδια συναλλαγή.

Δημιουργία λογαριασμών token και έκδοση tokens

Δημιουργήστε λογαριασμούς token για την πηγή, τους παραλήπτες και τον παραλήπτη των τελών, στη συνέχεια εκδώστε tokens στον λογαριασμό token της πηγής.

Μεταφορά με TransferCheckedWithFee

Μεταφέρετε tokens με TransferCheckedWithFee και επαληθεύστε την αναμενόμενη χρέωση.

Μεταφορά με TransferChecked

Μεταφέρετε tokens με TransferChecked. Η διαμορφωμένη χρέωση παρακρατείται ακόμα στον λογαριασμό token προορισμού.

Ανάληψη παρακρατημένων χρεώσεων από λογαριασμούς token

Χρησιμοποιήστε WithdrawWithheldTokensFromAccounts για να μεταφέρετε παρακρατημένες χρεώσεις απευθείας από λογαριασμούς token στον λογαριασμό token του παραλήπτη χρεώσεων.

Συλλογή παρακρατημένων χρεώσεων στο mint

Χρησιμοποιήστε HarvestWithheldTokensToMint για να μεταφέρετε παρακρατημένες χρεώσεις από λογαριασμούς token στον συσσωρευτή παρακράτησης του λογαριασμού mint.

Ανάληψη παρακρατημένων χρεώσεων από το mint

Χρησιμοποιήστε WithdrawWithheldTokensFromMint για να μεταφέρετε συλλεγμένες χρεώσεις από τον λογαριασμό mint στον λογαριασμό token του παραλήπτη χρεώσεων.

Ενημέρωση της επόμενης χρέωσης μεταφοράς

Χρησιμοποιήστε SetTransferFee για να ενημερώσετε τη διαμόρφωση της επόμενης χρέωσης μεταφοράς.

Υπολογισμός μεγέθους λογαριασμού

Υπολογίστε το μέγεθος του λογαριασμού mint για το βασικό mint συν την επέκταση TransferFeeConfig. Αυτό είναι το μέγεθος που χρησιμοποιείται στο CreateAccount.

Υπολογισμός rent

Υπολογίστε το rent χρησιμοποιώντας το μέγεθος που απαιτείται για το mint συν την επέκταση TransferFeeConfig.

Δημιουργία λογαριασμού mint

Δημιουργήστε τον λογαριασμό mint με τον υπολογισμένο χώρο και τα lamports.

Αρχικοποίηση TransferFeeConfig

Αρχικοποιήστε την επέκταση TransferFeeConfig στο mint.

Αρχικοποίηση του mint

Αρχικοποιήστε το mint με InitializeMint στην ίδια συναλλαγή.

Δημιουργία λογαριασμών token και έκδοση tokens

Δημιουργήστε λογαριασμούς token για την πηγή, τους παραλήπτες και τον παραλήπτη των τελών, στη συνέχεια εκδώστε tokens στον λογαριασμό token της πηγής.

Μεταφορά με TransferCheckedWithFee

Μεταφέρετε tokens με TransferCheckedWithFee και επαληθεύστε την αναμενόμενη χρέωση.

Μεταφορά με TransferChecked

Μεταφέρετε tokens με TransferChecked. Η διαμορφωμένη χρέωση παρακρατείται ακόμα στον λογαριασμό token προορισμού.

Ανάληψη παρακρατημένων χρεώσεων από λογαριασμούς token

Χρησιμοποιήστε WithdrawWithheldTokensFromAccounts για να μεταφέρετε παρακρατημένες χρεώσεις απευθείας από λογαριασμούς token στον λογαριασμό token του παραλήπτη χρεώσεων.

Συλλογή παρακρατημένων χρεώσεων στο mint

Χρησιμοποιήστε HarvestWithheldTokensToMint για να μεταφέρετε παρακρατημένες χρεώσεις από λογαριασμούς token στον συσσωρευτή παρακράτησης του λογαριασμού mint.

Ανάληψη παρακρατημένων χρεώσεων από το mint

Χρησιμοποιήστε WithdrawWithheldTokensFromMint για να μεταφέρετε συλλεγμένες χρεώσεις από τον λογαριασμό mint στον λογαριασμό token του παραλήπτη χρεώσεων.

Ενημέρωση της επόμενης χρέωσης μεταφοράς

Χρησιμοποιήστε SetTransferFee για να ενημερώσετε τη διαμόρφωση της επόμενης χρέωσης μεταφοράς.

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

Σειρά Οδηγιών

Το InitializeTransferFeeConfig πρέπει να προηγείται του InitializeMint. Τα CreateAccount, InitializeTransferFeeConfig και InitializeMint πρέπει να συμπεριλαμβάνονται στην ίδια συναλλαγή.

Αναφορά Πηγής

ΣτοιχείοΠεριγραφήΠηγή
TransferFeeConfigΕπέκταση mint που αποθηκεύει τις αρχές τελών, το συσσωρευμένο παρακρατημένο ποσό και τις παλαιότερες και νεότερες διαμορφώσεις τελών μεταφοράς.Πηγή
TransferFeeΔομή διαμόρφωσης τελών που αποθηκεύεται στο TransferFeeConfig και ορίζει την εποχή κατά την οποία ισχύει το τέλος, το μέγιστο τέλος και το τέλος σε μονάδες βάσης.Πηγή
TransferFeeAmountΕπέκταση λογαριασμού token που αποθηκεύει το ποσό που παρακρατήθηκε κατά τη διάρκεια των μεταφορών για έναν λογαριασμό token.Πηγή
TransferFeeInstruction::InitializeTransferFeeConfigΟδηγία που αρχικοποιεί τις ρυθμίσεις τελών μεταφοράς πριν από το InitializeMint.Πηγή
TransferFeeInstruction::TransferCheckedWithFeeΟδηγία μεταφοράς που επαληθεύει ότι το τέλος που παρέχεται από τον καλούντα αντιστοιχεί στην τρέχουσα διαμόρφωση τελών μεταφοράς του mint.Πηγή
TransferFeeInstruction::WithdrawWithheldTokensFromAccountsΟδηγία που μεταφέρει τα παρακρατημένα τέλη απευθείας από τους λογαριασμούς token σε έναν λογαριασμό token παραλήπτη τελών, υπογεγραμμένη από το withdraw_withheld_authority του mint.Πηγή
TransferFeeInstruction::HarvestWithheldTokensToMintΟδηγία χωρίς άδεια που μεταφέρει τα παρακρατημένα τέλη από τους λογαριασμούς token στον συσσωρευτή παρακράτησης του λογαριασμού mint.Πηγή
TransferFeeInstruction::WithdrawWithheldTokensFromMintΟδηγία που αποσύρει τα παρακρατημένα τέλη του λογαριασμού mint σε έναν λογαριασμό token παραλήπτη τελών, υπογεγραμμένη από το withdraw_withheld_authority του mint.Πηγή
TransferFeeInstruction::SetTransferFeeΟδηγία που ενημερώνει τη νεότερη διαμόρφωση τελών μεταφοράς, η οποία τίθεται σε ισχύ δύο εποχές αργότερα.Πηγή
process_initialize_transfer_fee_configΛογική επεξεργαστή που αρχικοποιεί την επέκταση TransferFeeConfig σε ένα μη αρχικοποιημένο mint και ορίζει τις παλαιότερες και νεότερες διαμορφώσεις τελών μεταφοράς στο αρχικό τέλος.Πηγή
process_withdraw_withheld_tokens_from_accountsΛογική επεξεργαστή που επικυρώνει το withdraw_withheld_authority και μεταφέρει τα παρακρατημένα τέλη από τους λογαριασμούς token σε έναν λογαριασμό token προορισμού.Πηγή
process_harvest_withheld_tokens_to_mintΛογική επεξεργαστή που συλλέγει τα παρακρατημένα τέλη από τους λογαριασμούς token στον συσσωρευτή παρακράτησης του λογαριασμού mint.Πηγή
process_withdraw_withheld_tokens_from_mintΛογική επεξεργαστή που επικυρώνει το withdraw_withheld_authority και μεταφέρει το παρακρατημένο ποσό του λογαριασμού mint σε έναν λογαριασμό token προορισμού.Πηγή
process_set_transfer_feeΛογική επεξεργαστή που επικυρώνει την αρχή διαμόρφωσης τελών μεταφοράς και ενημερώνει τη νεότερη διαμόρφωση τελών μεταφοράς ώστε να τεθεί σε ισχύ δύο εποχές αργότερα.Πηγή
get_required_init_account_extensionsΧρησιμοποιείται για την αυτόματη προσθήκη επεκτάσεων λογαριασμού token όταν αρχικοποιούνται οι λογαριασμοί token με βάση τις επεκτάσεις που είναι ενεργοποιημένες στο mint. Για mint TransferFeeConfig, προσθέτει το TransferFeeAmount.Πηγή

Typescript

Το παρακάτω παράδειγμα με Kit χρησιμοποιεί ρητές οδηγίες Token-2022. Παραδείγματα παλαιού τύπου που χρησιμοποιούν @solana/web3.js και @solana/spl-token περιλαμβάνονται ως αναφορά.

Kit

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

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.

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.

Is this page helpful?

Πίνακας Περιεχομένων

Επεξεργασία Σελίδας

Διαχειρίζεται από

© 2026 Ίδρυμα Solana.
Με επιφύλαξη παντός δικαιώματος.
Συνδεθείτε