Τι Είναι ένα Interest Bearing Mint;
Η επέκταση mint InterestBearingConfig του Token Extensions Program
επιτρέπει σε ένα mint να αποθηκεύει ένα ετήσιο επιτόκιο απευθείας onchain.
Τα interest bearing tokens δεν προσθέτουν περισσότερα tokens σε token accounts με την πάροδο του χρόνου. Το ποσό token που αποθηκεύεται σε κάθε token account παραμένει το ίδιο μέχρι μια εντολή του token program να το αλλάξει, όπως η δημιουργία (minting), η μεταφορά ή η καταστροφή (burning).
Καθώς περνάει ο χρόνος, το υπολογιζόμενο ποσό UI με τόκο μπορεί να αυξηθεί παρόλο που το ποσό token και η συνολική προσφορά token παραμένουν τα ίδια.
Πώς Υπολογίζονται οι Ιστορικές Αλλαγές Επιτοκίου
- Το
InterestBearingConfigαποθηκεύει ταinitialization_timestamp,pre_update_average_rate,last_update_timestampκαιcurrent_rate. - Η
amount_to_ui_amountυπολογίζει το ποσό UI σε δύο βήματα: πρώτα υπολογίζει τον τόκο από την αρχικοποίηση μέχρι τοlast_update_timestampχρησιμοποιώντας τοpre_update_average_rate, και στη συνέχεια υπολογίζει τον τόκο από τοlast_update_timestampμέχρι την τρέχουσα χρονική σήμανση χρησιμοποιώντας τοcurrent_rate. - Όταν εκτελείται η
UpdateRate, ηprocess_update_rateκαλεί τηtime_weighted_average_rateκαι ενημερώνει τοpre_update_average_rateσε έναν νέο χρονικά σταθμισμένο ιστορικό μέσο όρο, στη συνέχεια καταγράφει ένα νέοlast_update_timestampκαιcurrent_rate. - Μετά από αλλαγές επιτοκίου με την πάροδο του χρόνου, οι προηγούμενες αλλαγές
αντιπροσωπεύονται από το
pre_update_average_rate, ενώ η περίοδος από τοlast_update_timestampέως την τρέχουσα στιγμή υπολογίζεται με τοcurrent_rate.
Πώς να Δημιουργήσετε και να Χρησιμοποιήσετε Interest Bearing Tokens
Για να δημιουργήσετε και να χρησιμοποιήσετε interest bearing tokens:
- Υπολογίστε το μέγεθος του mint account και το rent που απαιτείται για το mint
και την επέκταση
InterestBearingConfig. - Δημιουργήστε το mint account με
CreateAccount, αρχικοποιήστε τοInterestBearingConfigκαι αρχικοποιήστε το mint με τοInitializeMint. - Δημιουργήστε (mint) tokens ως συνήθως.
- Χρησιμοποιήστε το
UpdateRateγια να αλλάξετε το τρέχον επιτόκιο του mint με την πάροδο του χρόνου. - Μετατρέψτε ποσά token σε ποσό UI με τόκο χρησιμοποιώντας το
AmountToUiAmountή βοηθητικές μεθόδους που ανακτούν το mint account και το clock sysvar, και στη συνέχεια υπολογίζουν το ποσό UI χωρίς να στείλουν transaction.
Μετατροπή Ποσού UI Εκτός και Εντός Αλυσίδας
Η διαδρομή βοηθού εκτός αλυσίδας ανακτά το mint account και το sysvar
ρολογιού, στη συνέχεια υπολογίζει το ποσό UI τοπικά χωρίς να στείλει
συναλλαγή. Η διαδρομή εντός αλυσίδας χρησιμοποιεί AmountToUiAmount, το
οποίο εκτελείται στο Token Program και επιστρέφει το ποσό UI στα δεδομένα
επιστροφής της συναλλαγής. Το AmountToUiAmount μπορεί να σταλεί σε μια
συναλλαγή ή να προσομοιωθεί.
Υπολογισμός μεγέθους λογαριασμού
Υπολογίστε το μέγεθος του mint account για το βασικό mint συν την επέκταση
InterestBearingConfig. Αυτό είναι το μέγεθος που χρησιμοποιείται στο
CreateAccount.
Υπολογισμός rent
Υπολογίστε το rent χρησιμοποιώντας το μέγεθος που απαιτείται για το mint συν την
επέκταση InterestBearingConfig.
Δημιουργία του mint account
Δημιουργήστε το mint account με τον υπολογισμένο χώρο και τα lamport.
Αρχικοποίηση InterestBearingConfig
Αρχικοποιήστε την επέκταση InterestBearingConfig στο mint.
Αρχικοποίηση του mint
Αρχικοποιήστε το mint με InitializeMint στην ίδια συναλλαγή.
Δημιουργία token account και έκδοση tokens
Δημιουργήστε ένα token account για το mint, στη συνέχεια εκδώστε tokens σε αυτό το token account.
Υπολογίστε το ποσό UI με τον offchain βοηθό
Ανακτήστε τον λογαριασμό mint και το sysvar ρολογιού, στη συνέχεια υπολογίστε το ποσό UI χωρίς να στείλετε συναλλαγή.
Σειρά Εντολών
Το InterestBearingMintInstruction::Initialize πρέπει να προηγείται του
InitializeMint. Τα CreateAccount,
InterestBearingMintInstruction::Initialize και InitializeMint
πρέπει να συμπεριλαμβάνονται στην ίδια συναλλαγή.
Αναφορά Πηγής
| Στοιχείο | Περιγραφή | Πηγή |
|---|---|---|
InterestBearingConfig | Επέκταση mint που αποθηκεύει την αρχή επιτοκίου, χρονοσημάνσεις και τρέχοντα και ιστορικά επιτόκια. | Πηγή |
InterestBearingMintInstruction::Initialize | Εντολή που αρχικοποιεί τη διαμόρφωση επιτοκιοφόρου mint πριν από το InitializeMint. | Πηγή |
InterestBearingMintInstruction::UpdateRate | Εντολή που αλλάζει το τρέχον επιτόκιο του mint. | Πηγή |
AmountToUiAmount | Εντολή που επιστρέφει το τρέχον string ποσού UI για ένα ποσό token χρησιμοποιώντας την ενεργή διαμόρφωση του mint. | Πηγή |
InterestBearingConfig::amount_to_ui_amount | Βοηθός που μετατρέπει ένα ποσό token σε ποσό UI με επιτόκιο για μια χρονοσήμανση. | Πηγή |
process_initialize | Λογική επεξεργαστή που αρχικοποιεί το InterestBearingConfig και καταγράφει τις αρχικές χρονοσημάνσεις και το επιτόκιο. | Πηγή |
process_update_rate | Λογική επεξεργαστή που ενημερώνει το τρέχον επιτόκιο, επανυπολογίζει το σταθμισμένο κατά χρόνο μέσο επιτόκιο και καταγράφει τη χρονοσήμανση ενημέρωσης. | Πηγή |
process_amount_to_ui_amount | Λογική επεξεργαστή που επιστρέφει το string ποσού UI χρησιμοποιώντας την ενεργή διαμόρφωση επιτοκιοφόρου του mint. | Πηγή |
Typescript
Το παρακάτω παράδειγμα Kit χρησιμοποιεί απευθείας τις παραγόμενες εντολές.
Παλαιότερα παραδείγματα που χρησιμοποιούν @solana/web3.js και
@solana/spl-token περιλαμβάνονται για αναφορά.
Kit
import {appendTransactionMessageInstructions,createTransactionMessage,generateKeyPairSigner,getBase64EncodedWireTransaction,pipe,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners,unwrapOption} from "@solana/kit";import { createLocalClient } from "@solana/kit-client-rpc";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 createLocalClient();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?