Czym jest mint z oprocentowaniem?
Rozszerzenie InterestBearingConfig w Token Extensions Program umożliwia
mintowi przechowywanie rocznej stopy procentowej bezpośrednio w łańcuchu.
Tokeny oprocentowane nie dodają więcej tokenów do kont tokenowych z upływem czasu. Kwota tokenów przechowywana na każdym koncie tokenowym pozostaje taka sama, dopóki instrukcja programu tokenów jej nie zmieni, na przykład poprzez mintowanie, transfer lub spalanie.
W miarę upływu czasu, obliczona kwota UI z odsetkami może rosnąć, mimo że kwota tokenów i podaż tokenów pozostają takie same.
Jak obliczane są historyczne zmiany stopy
InterestBearingConfigprzechowujeinitialization_timestamp,pre_update_average_rate,last_update_timestamporazcurrent_rate.amount_to_ui_amountoblicza kwotę UI w dwóch krokach: najpierw oblicza odsetki od inicjalizacji dolast_update_timestampużywającpre_update_average_rate, a następnie oblicza odsetki odlast_update_timestampdo bieżącego znacznika czasu używająccurrent_rate.- Gdy
UpdateRatejest wykonywane,process_update_ratewywołujetime_weighted_average_ratei aktualizujepre_update_average_ratedo nowej średniej historycznej ważonej czasem, po czym zapisuje nowylast_update_timestampicurrent_rate. - Po zmianach stopy w czasie, wcześniejsze zmiany są reprezentowane przez
pre_update_average_rate, natomiast okres odlast_update_timestampdo bieżącego czasu jest obliczany z użyciemcurrent_rate.
Jak tworzyć i używać tokenów oprocentowanych
Aby utworzyć i używać tokenów oprocentowanych:
- Oblicz rozmiar konta mint oraz wymagany rent dla minta i rozszerzenia
InterestBearingConfig. - Utwórz konto mint za pomocą
CreateAccount, zainicjalizujInterestBearingConfigi zainicjalizuj mint za pomocąInitializeMint. - Mintuj tokeny w zwykły sposób.
- Użyj
UpdateRate, aby zmieniać bieżącą stopę minta w czasie. - Konwertuj kwoty tokenów na kwotę UI z odsetkami za pomocą
AmountToUiAmountlub metod pomocniczych, które pobierają konto mint i sysvar zegara, a następnie obliczają kwotę UI bez wysyłania transakcji.
Konwersja kwoty UI offchain i onchain
Ścieżka pomocnika offchain pobiera konto mint i sysvar zegara, a następnie
oblicza kwotę UI lokalnie bez wysyłania transakcji. Ścieżka onchain
wykorzystuje AmountToUiAmount, która działa w Token Program i zwraca
kwotę UI w danych zwrotnych transakcji. AmountToUiAmount można wysłać w
transakcji lub zasymulować.
Oblicz rozmiar konta
Oblicz rozmiar konta mint dla bazowego konta mint wraz z rozszerzeniem
InterestBearingConfig. Jest to rozmiar używany w CreateAccount.
Oblicz rent
Oblicz rent, używając rozmiaru potrzebnego dla konta mint wraz z rozszerzeniem
InterestBearingConfig.
Utwórz konto mint
Utwórz konto mint z obliczoną przestrzenią i lamportami.
Zainicjalizuj InterestBearingConfig
Zainicjalizuj rozszerzenie InterestBearingConfig na koncie mint.
Zainicjalizuj konto mint
Zainicjalizuj konto mint za pomocą InitializeMint w tej samej transakcji.
Utwórz token account i wybij tokeny
Utwórz token account dla konta mint, a następnie wybij tokeny na to token account.
Oblicz kwotę UI za pomocą pomocnika offchain
Pobierz konto mint i sysvar zegara, a następnie oblicz kwotę UI bez wysyłania transakcji.
Kolejność instrukcji
InterestBearingMintInstruction::Initialize musi wystąpić przed
InitializeMint. CreateAccount,
InterestBearingMintInstruction::Initialize i InitializeMint muszą
być zawarte w tej samej transakcji.
Źródło referencyjne
| Element | Opis | Źródło |
|---|---|---|
InterestBearingConfig | Rozszerzenie mint, które przechowuje organ stopy procentowej, znaczniki czasowe oraz bieżące i historyczne stawki. | Źródło |
InterestBearingMintInstruction::Initialize | Instrukcja, która inicjalizuje konfigurację oprocentowania przed InitializeMint. | Źródło |
InterestBearingMintInstruction::UpdateRate | Instrukcja, która zmienia bieżącą stopę procentową mint. | Źródło |
AmountToUiAmount | Instrukcja, która zwraca bieżący ciąg kwoty UI dla kwoty tokenów przy użyciu aktywnej konfiguracji mint. | Źródło |
InterestBearingConfig::amount_to_ui_amount | Pomocnik, który konwertuje kwotę tokenów na kwotę UI z oprocentowaniem dla znacznika czasowego. | Źródło |
process_initialize | Logika procesora, która inicjalizuje InterestBearingConfig i rejestruje początkowe znaczniki czasowe i stawkę. | Źródło |
process_update_rate | Logika procesora, która aktualizuje bieżącą stawkę, przelicza średnią ważoną czasowo stawkę i rejestruje znacznik czasowy aktualizacji. | Źródło |
process_amount_to_ui_amount | Logika procesora, która zwraca ciąg kwoty UI przy użyciu aktywnej konfiguracji oprocentowania mint. | Źródło |
Typescript
Poniższy przykład Kit wykorzystuje bezpośrednio wygenerowane instrukcje.
Starsze przykłady używające @solana/web3.js i @solana/spl-token są dołączone
dla odniesienia.
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?