Комісії за переказ

Що таке комісії за переказ?

Розширення mint TransferFeeConfig Token Extensions Program застосовує комісію до кожного переказу для цього mint.

Під час переказу:

  • Сума переказу зменшується на налаштовану комісію
  • Утримана комісія відстежується на token account призначення
  • Утримані комісії можна вивести безпосередньо з token accounts або зібрати до mint
  • withdraw_withheld_authority може перемістити зібрані комісії на token account отримувача комісій

Розширення Token Account

Коли token account ініціалізується для mint з TransferFeeConfig, token account ініціалізується з TransferFeeAmount. Коли token account створюється через Associated Token Program, необхідний розмір облікового запису обчислюється, і token account створюється з необхідним розміром та lamports для звільнення від rent.

Як створити mint з комісіями за переказ

Щоб створити mint з комісіями за переказ:

  1. Обчисліть розмір облікового запису mint та необхідний rent для mint та розширення TransferFeeConfig.
  2. Створіть обліковий запис mint за допомогою CreateAccount, ініціалізуйте TransferFeeConfig та ініціалізуйте mint за допомогою InitializeMint.
  3. Створіть token accounts для mint. TransferFeeAmount автоматично активується для token accounts.
  4. Використовуйте TransferCheckedWithFee для переказу токенів та перевірки очікуваної комісії. TransferChecked також працює та автоматично утримує налаштовану комісію на token account призначення.
  5. Використовуйте WithdrawWithheldTokensFromAccounts для переміщення утриманих комісій безпосередньо з token accounts на token account отримувача комісій, підписаний withdraw_withheld_authority mint.
  6. Використовуйте HarvestWithheldTokensToMint без дозволів для переміщення утриманих комісій з token accounts на обліковий запис mint, потім використовуйте WithdrawWithheldTokensFromMint для виведення утриманих комісій облікового запису mint на token account отримувача комісій, підписаний withdraw_withheld_authority mint.
  7. Використовуйте SetTransferFee для оновлення наступної конфігурації комісій за переказ, яка набуває чинності через дві epochs.

Обчислення розміру облікового запису

Обчисліть розмір mint account для базового mint плюс розширення TransferFeeConfig. Це розмір, який використовується в CreateAccount.

Обчислення rent

Обчисліть rent, використовуючи розмір, необхідний для mint плюс розширення TransferFeeConfig.

Створення mint account

Створіть mint account з обчисленим простором і lamport.

Ініціалізація TransferFeeConfig

Ініціалізуйте розширення TransferFeeConfig на mint.

Ініціалізація mint

Ініціалізуйте mint з InitializeMint в тій самій транзакції.

Створення token accounts і карбування токенів

Створіть token accounts для джерела, одержувачів і отримувача комісії, потім викарбуйте токени на token account джерела.

Переказ з TransferCheckedWithFee

Переказуйте токени за допомогою TransferCheckedWithFee та перевірте очікувану комісію.

Переказ з TransferChecked

Переказуйте токени за допомогою TransferChecked. Налаштована комісія все одно утримується на цільовому обліковому записі токенів.

Вилучення утриманих комісій з облікових записів токенів

Використовуйте WithdrawWithheldTokensFromAccounts, щоб перемістити утримані комісії безпосередньо з облікових записів токенів на обліковий запис токенів одержувача комісій.

Збір утриманих комісій на mint

Використовуйте HarvestWithheldTokensToMint, щоб перемістити утримані комісії з облікових записів токенів до акумулятора утриманих комісій облікового запису mint.

Вилучення утриманих комісій з mint

Використовуйте WithdrawWithheldTokensFromMint, щоб перемістити зібрані комісії з облікового запису mint на обліковий запис токенів одержувача комісій.

Оновлення наступної комісії за переказ

Використовуйте SetTransferFee, щоб оновити конфігурацію наступної комісії за переказ.

Обчислення розміру облікового запису

Обчисліть розмір mint account для базового mint плюс розширення TransferFeeConfig. Це розмір, який використовується в CreateAccount.

Обчислення rent

Обчисліть rent, використовуючи розмір, необхідний для mint плюс розширення TransferFeeConfig.

Створення mint account

Створіть mint account з обчисленим простором і lamport.

Ініціалізація TransferFeeConfig

Ініціалізуйте розширення TransferFeeConfig на mint.

Ініціалізація mint

Ініціалізуйте mint з InitializeMint в тій самій транзакції.

Створення token accounts і карбування токенів

Створіть token accounts для джерела, одержувачів і отримувача комісії, потім викарбуйте токени на token account джерела.

Переказ з TransferCheckedWithFee

Переказуйте токени за допомогою TransferCheckedWithFee та перевірте очікувану комісію.

Переказ з TransferChecked

Переказуйте токени за допомогою TransferChecked. Налаштована комісія все одно утримується на цільовому обліковому записі токенів.

Вилучення утриманих комісій з облікових записів токенів

Використовуйте WithdrawWithheldTokensFromAccounts, щоб перемістити утримані комісії безпосередньо з облікових записів токенів на обліковий запис токенів одержувача комісій.

Збір утриманих комісій на mint

Використовуйте HarvestWithheldTokensToMint, щоб перемістити утримані комісії з облікових записів токенів до акумулятора утриманих комісій облікового запису mint.

Вилучення утриманих комісій з mint

Використовуйте WithdrawWithheldTokensFromMint, щоб перемістити зібрані комісії з облікового запису mint на обліковий запис токенів одержувача комісій.

Оновлення наступної комісії за переказ

Використовуйте 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Розширення облікового запису токенів, яке зберігає суму, утриману під час переказів для облікового запису токенів.Джерело
TransferFeeInstruction::InitializeTransferFeeConfigІнструкція, яка ініціалізує налаштування комісії за переказ перед InitializeMint.Джерело
TransferFeeInstruction::TransferCheckedWithFeeІнструкція переказу, яка перевіряє, що надана викликачем комісія відповідає поточній конфігурації комісії за переказ mint.Джерело
TransferFeeInstruction::WithdrawWithheldTokensFromAccountsІнструкція, яка переміщує утримані комісії безпосередньо з облікових записів токенів до облікового запису токенів отримувача комісій, підписана withdraw_withheld_authority mint.Джерело
TransferFeeInstruction::HarvestWithheldTokensToMintІнструкція без дозволу, яка переміщує утримані комісії з облікових записів токенів до акумулятора утриманих коштів облікового запису mint.Джерело
TransferFeeInstruction::WithdrawWithheldTokensFromMintІнструкція, яка виводить утримані комісії облікового запису mint до облікового запису токенів отримувача комісій, підписана withdraw_withheld_authority mint.Джерело
TransferFeeInstruction::SetTransferFeeІнструкція, яка оновлює новішу конфігурацію комісії за переказ, що набуває чинності через дві епохи.Джерело
process_initialize_transfer_fee_configЛогіка процесора, яка ініціалізує розширення TransferFeeConfig на неініціалізованому mint і встановлює як старішу, так і новішу конфігурації комісій за переказ на початкову комісію.Джерело
process_withdraw_withheld_tokens_from_accountsЛогіка процесора, яка перевіряє withdraw_withheld_authority і переміщує утримані комісії з облікових записів токенів до цільового облікового запису токенів.Джерело
process_harvest_withheld_tokens_to_mintЛогіка процесора, яка збирає утримані комісії з облікових записів токенів до акумулятора утриманих коштів облікового запису mint.Джерело
process_withdraw_withheld_tokens_from_mintЛогіка процесора, яка перевіряє withdraw_withheld_authority і переміщує утриману суму облікового запису mint до цільового облікового запису токенів.Джерело
process_set_transfer_feeЛогіка процесора, яка перевіряє авторитет конфігурації комісії за переказ і оновлює новішу конфігурацію комісії за переказ, щоб вона набула чинності через дві епохи.Джерело
get_required_init_account_extensionsВикористовується для автоматичного додавання розширень облікового запису токенів при ініціалізації облікових записів токенів на основі розширень, увімкнених на 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.
Всі права захищені.
Залишайтеся на зв'язку