ما هي عملة تحمل الفائدة؟
يتيح امتداد عملة InterestBearingConfig في برنامج Token Extension تخزين
معدل فائدة سنوي مباشرة على السلسلة.
الرموز التي تحمل فائدة لا تضيف المزيد من الرموز إلى حسابات الرموز بمرور الوقت. يظل مبلغ الرموز المخزن في كل حساب رموز كما هو حتى تقوم تعليمة برنامج الرموز بتغييره، مثل السك أو النقل أو الحرق.
مع مرور الوقت، يمكن أن يزداد مبلغ واجهة المستخدم المحسوب مع الفائدة حتى لو ظل مبلغ الرموز وإجمالي المعروض من الرموز كما هو.
كيف يتم حساب تغييرات المعدل التاريخية
- يخزن
InterestBearingConfiginitialization_timestampوpre_update_average_rateوlast_update_timestampوcurrent_rate. - يحسب
amount_to_ui_amountمبلغ واجهة المستخدم على خطوتين: يحسب أولاً الفائدة من التهيئة إلىlast_update_timestampباستخدامpre_update_average_rate، ثم يحسب الفائدة منlast_update_timestampإلى الطابع الزمني الحالي باستخدامcurrent_rate. - عند تشغيل
UpdateRate، يستدعيprocess_update_ratetime_weighted_average_rateويحدثpre_update_average_rateإلى متوسط تاريخي جديد موزون بالوقت، ثم يسجلlast_update_timestampوcurrent_rateجديدين. - بعد تغيرات المعدل بمرور الوقت، يتم تمثيل التغييرات الأقدم بواسطة
pre_update_average_rate، بينما يتم حساب الفترة منlast_update_timestampإلى الوقت الحالي باستخدامcurrent_rate.
كيفية إنشاء واستخدام الرموز التي تحمل فائدة
لإنشاء واستخدام الرموز التي تحمل فائدة:
- احسب حجم حساب العملة والإيجار المطلوب للعملة وامتداد
InterestBearingConfig. - أنشئ حساب العملة باستخدام
CreateAccount، وهيئInterestBearingConfig، وهيئ العملة باستخدامInitializeMint. - اسك الرموز كالمعتاد.
- استخدم
UpdateRateلتغيير المعدل الحالي للعملة بمرور الوقت. - حول مبالغ الرموز إلى مبلغ واجهة مستخدم مع الفائدة باستخدام
AmountToUiAmountأو الطرق المساعدة التي تجلب حساب العملة ومتغير نظام الساعة، ثم احسب مبلغ واجهة المستخدم دون إرسال معاملة.
تحويل المبلغ بين واجهة المستخدم خارج السلسلة وعلى السلسلة
يقوم مسار المساعد خارج السلسلة بجلب حساب السك وsysvar الخاص بالساعة، ثم يحسب
مبلغ واجهة المستخدم محليًا دون إرسال معاملة. يستخدم المسار على السلسلة
AmountToUiAmount، والذي يعمل في برنامج الرموز ويُرجع مبلغ واجهة المستخدم
في بيانات إرجاع المعاملة. يمكن إرسال AmountToUiAmount في معاملة أو
محاكاته.
حساب حجم الحساب
احسب حجم حساب السك للسك الأساسي بالإضافة إلى امتداد InterestBearingConfig.
هذا هو الحجم المستخدم في CreateAccount.
حساب الإيجار
احسب الإيجار باستخدام الحجم المطلوب للسك بالإضافة إلى امتداد
InterestBearingConfig.
إنشاء حساب السك
أنشئ حساب السك بالمساحة والlamports المحسوبة.
تهيئة InterestBearingConfig
هيّئ امتداد InterestBearingConfig على السك.
تهيئة السك
هيّئ السك باستخدام InitializeMint في نفس المعاملة.
إنشاء حساب رمز وسك الرموز
أنشئ حساب رمز للسك، ثم اسك الرموز إلى حساب الرمز هذا.
احسب مبلغ واجهة المستخدم باستخدام المساعد خارج السلسلة
جلب حساب السك ومتغير النظام للساعة، ثم احسب مبلغ واجهة المستخدم دون إرسال معاملة.
ترتيب التعليمات
يجب أن يأتي InterestBearingMintInstruction::Initialize قبل
InitializeMint. يجب تضمين CreateAccount،
InterestBearingMintInstruction::Initialize، و InitializeMint في
نفس المعاملة.
مرجع المصدر
| العنصر | الوصف | المصدر |
|---|---|---|
InterestBearingConfig | امتداد السك الذي يخزن سلطة معدل الفائدة والطوابع الزمنية والمعدلات الحالية والتاريخية. | المصدر |
InterestBearingMintInstruction::Initialize | تعليمة تهيئ إعدادات السك الحامل للفائدة قبل InitializeMint. | المصدر |
InterestBearingMintInstruction::UpdateRate | تعليمة تغير معدل الفائدة الحالي للسك. | المصدر |
AmountToUiAmount | تعليمة ترجع سلسلة مبلغ واجهة المستخدم الحالي لمبلغ رمز باستخدام إعدادات السك النشطة. | المصدر |
InterestBearingConfig::amount_to_ui_amount | مساعد يحول مبلغ رمز إلى مبلغ واجهة مستخدم مع فائدة لطابع زمني معين. | المصدر |
process_initialize | منطق المعالج الذي يهيئ InterestBearingConfig ويسجل الطوابع الزمنية والمعدل الأولي. | المصدر |
process_update_rate | منطق المعالج الذي يحدث المعدل الحالي ويعيد حساب متوسط المعدل المرجح بالوقت ويسجل طابع التحديث الزمني. | المصدر |
process_amount_to_ui_amount | منطق المعالج الذي يرجع سلسلة مبلغ واجهة المستخدم باستخدام إعدادات السك الحامل للفائدة النشطة. | المصدر |
تايب سكريبت
يستخدم مثال Kit أدناه التعليمات المُنشأة مباشرة. تم تضمين أمثلة قديمة باستخدام
@solana/web3.js و @solana/spl-token للمرجعية.
Kit
import {lamports,createClient,appendTransactionMessageInstructions,createTransactionMessage,generateKeyPairSigner,getBase64EncodedWireTransaction,pipe,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners,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 {amountToUiAmountForMintWithoutSimulation,extension,fetchMint,findAssociatedTokenPda,getAmountToUiAmountInstruction,getCreateAssociatedTokenInstructionAsync,getInitializeInterestBearingMintInstruction,getInitializeMintInstruction,getMintSize,getMintToCheckedInstruction,getUpdateRateInterestBearingMintInstruction,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 recipient = await generateKeyPairSigner();const tokenAmount = 1_000_000_000_000n;const interestBearingExtension = extension("InterestBearingConfig", {rateAuthority: client.payer.address,initializationTimestamp: BigInt(Math.floor(Date.now() / 1000)),lastUpdateTimestamp: BigInt(Math.floor(Date.now() / 1000)),preUpdateAverageRate: 30000,currentRate: 30000});const mintSpace = BigInt(getMintSize([interestBearingExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();const [tokenAccount] = await findAssociatedTokenPda({mint: mint.address,owner: recipient.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});await client.sendTransaction([getCreateAccountInstruction({payer: client.payer, // Account funding the new mint account.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 InterestBearingConfig.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.}),getInitializeInterestBearingMintInstruction({mint: mint.address, // Mint account that stores the InterestBearingConfig extension.rateAuthority: client.payer.address, // Authority allowed to update the interest rate later.rate: 30000 // Interest rate in basis points.}),getInitializeMintInstruction({mint: mint.address, // Mint account to initialize.decimals: 0, // 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.})]);await client.sendTransaction([await getCreateAssociatedTokenInstructionAsync({payer: client.payer, // Account funding the associated token account creation.mint: mint.address, // Mint for the associated token account.owner: recipient.address // Owner of the token account.}),getMintToCheckedInstruction({mint: mint.address, // Mint account that issues the tokens.token: tokenAccount, // Token account receiving the newly minted tokens.mintAuthority: client.payer, // Signer authorized to mint new tokens.amount: tokenAmount, // Token amount in base units.decimals: 0 // Decimals defined on the mint.})]);await new Promise((resolve) => setTimeout(resolve, 2_000));const calculatedUiAmount = await amountToUiAmountForMintWithoutSimulation(client.rpc,mint.address,tokenAmount);const amountToUiInstruction = getAmountToUiAmountInstruction({mint: mint.address, // Mint whose UI amount conversion is being simulated.amount: tokenAmount // Token amount in base units.});const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();const amountToUiMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(client.payer, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([amountToUiInstruction], tx));const signedAmountToUiMessage =await signTransactionMessageWithSigners(amountToUiMessage);const simulation = await client.rpc.simulateTransaction(getBase64EncodedWireTransaction(signedAmountToUiMessage),{encoding: "base64"}).send();const simulatedUiAmount = Buffer.from(simulation.value.returnData?.data?.[0] ?? "","base64").toString("utf8");const updateRateInstruction = getUpdateRateInterestBearingMintInstruction({mint: mint.address, // Mint account that stores the InterestBearingConfig extension.rateAuthority: client.payer, // Signer authorized to update the interest rate.rate: 15000 // New interest rate in basis points.});await client.sendTransaction([updateRateInstruction]);const mintAccount = await fetchMint(client.rpc, mint.address);const interestBearingConfig = (unwrapOption(mintAccount.data.extensions) ?? []).find((item) => isExtension("InterestBearingConfig", item));console.log("Mint Address:", mint.address);console.log("Token Account:", tokenAccount);console.log("Calculated UI Amount:", calculatedUiAmount);console.log("Simulated UI Amount:", simulatedUiAmount);console.log("InterestBearingConfig:", interestBearingConfig);
Web3.js
import {Connection,Keypair,sendAndConfirmTransaction,SystemProgram,Transaction,LAMPORTS_PER_SOL} from "@solana/web3.js";import {ASSOCIATED_TOKEN_PROGRAM_ID,amountToUiAmountForMintWithoutSimulation,createAmountToUiAmountInstruction,createAssociatedTokenAccountInstruction,createInitializeMintInstruction,createInitializeInterestBearingMintInstruction,createMintToCheckedInstruction,createUpdateRateInterestBearingMintInstruction,ExtensionType,getAssociatedTokenAddressSync,getInterestBearingMintConfigState,getMint,getMintLen,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 recipient = Keypair.generate();const tokenAmount = 1_000_000_000_000n;const airdropSignature = await connection.requestAirdrop(feePayer.publicKey,5 * LAMPORTS_PER_SOL);await connection.confirmTransaction({blockhash: latestBlockhash.blockhash,lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,signature: airdropSignature});const extensions = [ExtensionType.InterestBearingConfig];const mint = Keypair.generate();const mintLength = getMintLen(extensions);const mintRent = await connection.getMinimumBalanceForRentExemption(mintLength);const tokenAccount = getAssociatedTokenAddressSync(mint.publicKey,recipient.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);const createMintAccountInstruction = SystemProgram.createAccount({fromPubkey: feePayer.publicKey, // Account funding the new mint account.newAccountPubkey: mint.publicKey, // New mint account to create.space: mintLength, // Account size in bytes for the mint plus InterestBearingConfig.lamports: mintRent, // Lamports funding the mint account rent.programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.});const initializeInterestBearingInstruction =createInitializeInterestBearingMintInstruction(mint.publicKey, // Mint account that stores the InterestBearingConfig extension.feePayer.publicKey, // Authority allowed to update the interest rate later.30000, // Interest rate in basis points.TOKEN_2022_PROGRAM_ID // Token program that owns the mint.);const initializeMintInstruction = createInitializeMintInstruction(mint.publicKey, // Mint account to initialize.0, // 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.);const createTokenAccountInstruction = createAssociatedTokenAccountInstruction(feePayer.publicKey, // Account funding the associated token account creation.tokenAccount, // Associated token account address to create.recipient.publicKey, // Owner of the token account.mint.publicKey, // Mint for the associated token account.TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.ASSOCIATED_TOKEN_PROGRAM_ID // Associated Token Program that creates the account.);const mintToTokenAccountInstruction = createMintToCheckedInstruction(mint.publicKey, // Mint account that issues the tokens.tokenAccount, // Token account receiving the newly minted tokens.feePayer.publicKey, // Signer authorized to mint new tokens.tokenAmount, // Token amount in base units.0, // Decimals defined on the mint.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that owns the mint and token account.);await sendAndConfirmTransaction(connection,new Transaction({feePayer: feePayer.publicKey,blockhash: latestBlockhash.blockhash,lastValidBlockHeight: latestBlockhash.lastValidBlockHeight}).add(createMintAccountInstruction,initializeInterestBearingInstruction,initializeMintInstruction),[feePayer, mint]);await sendAndConfirmTransaction(connection,new Transaction().add(createTokenAccountInstruction,mintToTokenAccountInstruction),[feePayer]);await new Promise((resolve) => setTimeout(resolve, 2_000));const calculatedUiAmount = await amountToUiAmountForMintWithoutSimulation(connection,mint.publicKey,tokenAmount);const amountToUiInstruction = createAmountToUiAmountInstruction(mint.publicKey, // Mint whose UI amount conversion is being simulated.tokenAmount, // Token amount in base units.TOKEN_2022_PROGRAM_ID // Token program that owns the mint.);const amountToUiSimulation = await connection.simulateTransaction(new Transaction().add(amountToUiInstruction),[feePayer],false);const simulatedUiAmount = Buffer.from(amountToUiSimulation.value.returnData?.data?.[0] ?? "","base64").toString("utf8");const updateRateInstruction = createUpdateRateInterestBearingMintInstruction(mint.publicKey, // Mint account that stores the InterestBearingConfig extension.feePayer.publicKey, // Signer authorized to update the interest rate.15000, // New interest rate in basis points.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that owns the mint.);await sendAndConfirmTransaction(connection,new Transaction().add(updateRateInstruction),[feePayer]);const mintAccount = await getMint(connection,mint.publicKey,"confirmed",TOKEN_2022_PROGRAM_ID);const interestBearingConfig = getInterestBearingMintConfigState(mintAccount);console.log("Mint Address:", mint.publicKey.toBase58());console.log("Token Account:", tokenAccount.toBase58());console.log("Calculated UI Amount:", calculatedUiAmount);console.log("Simulated UI Amount:", simulatedUiAmount);console.log("InterestBearingConfig:", interestBearingConfig);
Rust
use anyhow::{anyhow, Result};use base64::prelude::{Engine as _, BASE64_STANDARD};use solana_client::nonblocking::rpc_client::RpcClient;use solana_commitment_config::CommitmentConfig;use solana_sdk::{sysvar::clock::{self, Clock},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::{interest_bearing_mint::{instruction::{initialize as initialize_interest_bearing_instruction, update_rate,},InterestBearingConfig,},BaseStateWithExtensions, ExtensionType, StateWithExtensions,},instruction::{amount_to_ui_amount, initialize_mint, mint_to_checked},state::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 = Keypair::new();let token_amount = 1_000_000_000_000u64;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::InterestBearingConfig])?;let mint_rent = client.get_minimum_balance_for_rent_exemption(mint_space).await?;let create_mint_account_instruction = create_account(&fee_payer.pubkey(), // Account funding the new mint account.&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 InterestBearingConfig.&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.);let initialize_interest_bearing_instruction = initialize_interest_bearing_instruction(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.&mint.pubkey(), // Mint account that stores the InterestBearingConfig extension.Some(fee_payer.pubkey()), // Authority allowed to update the interest rate later.30000, // Interest rate in basis points.)?;let initialize_mint_instruction = 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.0, // Number of decimals for the token.)?;let create_mint_transaction = Transaction::new_signed_with_payer(&[create_mint_account_instruction,initialize_interest_bearing_instruction,initialize_mint_instruction,],Some(&fee_payer.pubkey()),&[&fee_payer, &mint],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&create_mint_transaction).await?;let token_account = get_associated_token_address_with_program_id(&recipient.pubkey(),&mint.pubkey(),&TOKEN_2022_PROGRAM_ID,);let create_token_account_instruction = create_associated_token_account(&fee_payer.pubkey(), // Account funding the associated token account creation.&recipient.pubkey(), // Owner of the token account.&mint.pubkey(), // Mint for the associated token account.&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.);let mint_to_token_account_instruction = mint_to_checked(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint and token account.&mint.pubkey(), // Mint account that issues the tokens.&token_account, // Token account receiving the newly minted tokens.&fee_payer.pubkey(), // Signer authorized to mint new tokens.&[], // Additional multisig signers.token_amount, // Token amount in base units.0, // Decimals defined on the mint.)?;let create_token_account_transaction = Transaction::new_signed_with_payer(&[create_token_account_instruction,mint_to_token_account_instruction,],Some(&fee_payer.pubkey()),&[&fee_payer],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&create_token_account_transaction).await?;tokio::time::sleep(std::time::Duration::from_secs(2)).await;let mint_account = client.get_account(&mint.pubkey()).await?;let mint_state = StateWithExtensions::<Mint>::unpack(&mint_account.data)?;let interest_bearing_config = mint_state.get_extension::<InterestBearingConfig>()?;let clock_account = client.get_account(&clock::ID).await?;let clock: Clock = clock_account.deserialize_data()?;let calculated_ui_amount = interest_bearing_config.amount_to_ui_amount(token_amount, mint_state.base.decimals, clock.unix_timestamp).ok_or_else(|| anyhow!("Failed to calculate UI amount"))?;let amount_to_ui_instruction = amount_to_ui_amount(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.&mint.pubkey(), // Mint whose UI amount conversion is being simulated.token_amount, // Token amount in base units.)?;let amount_to_ui_blockhash = client.get_latest_blockhash().await?;let amount_to_ui_transaction = Transaction::new_signed_with_payer(&[amount_to_ui_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],amount_to_ui_blockhash,);let amount_to_ui_result = client.simulate_transaction(&amount_to_ui_transaction).await?;let simulated_ui_amount = String::from_utf8(BASE64_STANDARD.decode(amount_to_ui_result.value.return_data.ok_or_else(|| anyhow!("Expected AmountToUiAmount return data"))?.data.0,)?,)?;let update_rate_instruction = update_rate(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.&mint.pubkey(), // Mint account that stores the InterestBearingConfig extension.&fee_payer.pubkey(), // Signer authorized to update the interest rate.&[], // Additional multisig signers.15000, // New interest rate in basis points.)?;let update_rate_transaction = Transaction::new_signed_with_payer(&[update_rate_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&update_rate_transaction).await?;let updated_mint_account = client.get_account(&mint.pubkey()).await?;let updated_mint_state = StateWithExtensions::<Mint>::unpack(&updated_mint_account.data)?;let interest_bearing_config = updated_mint_state.get_extension::<InterestBearingConfig>()?;println!("Mint Address: {}", mint.pubkey());println!("Token Account: {}", token_account);println!("Calculated UI Amount: {}", calculated_ui_amount);println!("Simulated UI Amount: {}", simulated_ui_amount);println!("InterestBearingConfig: {:#?}", interest_bearing_config);Ok(())}
Is this page helpful?