Scaled UI Amount拡張機能とは?
Token Extensions ProgramのScaledUiAmount
mint拡張機能により、ミントはトークン残高の表示UI量に更新可能な乗数を適用できます。
これは、トークンが株式分割や配当などの実世界の資産や金融商品を表す場合に便利です。例えば、株式分割により全保有者の株式数が2倍になります。すべてのtoken
accountの残高を変更することなく、*rsScaledUiAmount*により、ウォレットやアプリケーションはミントの乗数を使用して新しいUI量を表示できます。
この機能は、配当や利回りの分配にも使用できます。
新しいトークンは作成されません。token accountに保存されているトークン量は変わりません。表示されるUI量のみが変更されます。
互換性のない拡張機能
同じミントで_rsScaledUiAmount_と_rsInterestBearingConfig_を有効にしないでください。Token
Extensions Programはその拡張機能の組み合わせを拒否します。
Scaled UI Amountの仕組み
- *rs
ScaledUiAmountConfig*は、更新権限、multiplier、new_multiplier、およびnew_multiplier_effective_timestampを保存します。 new_multiplier_effective_timestampより前は、変換にmultiplierが使用されます。そのタイムスタンプ以降は、変換にnew_multiplierが使用されます。- *rs
UpdateMultiplier*は、新しい乗数を即座に適用するか、将来のUnixタイムスタンプに対してスケジュールできます。 AmountToUiAmount、UiAmountToAmount、および拡張機能の変換ヘルパーは、scaled UI amountミントに対して浮動小数点演算を使用するため、変換が正確に往復することは保証されません。
Scaled UI Amountの使用方法
scaled UI amountを使用するには:
- ミントおよび*rs
ScaledUiAmount*拡張機能に必要なmint accountのサイズとrentを計算します。 - *rs
CreateAccount*でmint accountを作成し、*rsScaledUiAmount*を初期化し、*rsInitializeMint*でミントを初期化します。 - 残高を表示する際やUI量を変換する際は、
AmountToUiAmount、UiAmountToAmount、またはクライアントヘルパーを使用します。 - *rs
UpdateMultiplier*を使用して、表示される乗数を即座に、または将来のUnixタイムスタンプで更新します。
Instruction Order
_rsScaledUiAmountMintInstruction::Initialize_は_rsInitializeMint_よりも前に実行する必要があります。CreateAccount、ScaledUiAmountMintInstruction::Initialize、および_rsInitializeMint_は同じトランザクションに含める必要があります。
ガイド
発行者ガイド
Scaled UI Amountを使用してミントを作成し、時間の経過とともに乗数を更新します。
統合ガイド
表示用にトークン量を変換し、ウォレットやアプリケーションでScaled UI Amountをサポートします。
ソースリファレンス
| 項目 | 説明 | ソース |
|---|---|---|
ScaledUiAmountConfig | 権限、現在の乗数、次の乗数、および有効になるタイムスタンプを保存するミント拡張状態。 | ソース |
ScaledUiAmountMintInstruction::Initialize | *rsInitializeMintの前にミント上でrsScaledUiAmount*を初期化するinstruction。 | ソース |
ScaledUiAmountMintInstruction::UpdateMultiplier | 新しい乗数と有効になるUnixタイムスタンプを設定するinstruction。 | ソース |
amount_to_ui_amount | 現在の乗数、小数点以下桁数、タイムスタンプを使用してトークン量をUI量文字列に変換するヘルパー。 | ソース |
try_ui_amount_into_amount | 現在の乗数、小数点以下桁数、タイムスタンプを使用してUI量文字列をトークン量に変換するヘルパー。 | ソース |
process_initialize | *rsScaledUiAmountConfig*を初期化し、未初期化のミントに初期乗数を保存するプロセッサ。 | ソース |
process_update_multiplier | 権限を検証し、スケジュールされた乗数を更新し、適切な場合に即座に適用するプロセッサ。 | ソース |
process_amount_to_ui_amount | ミントで拡張が有効になっている場合に*rsScaledUiAmountConfig*を使用するToken Program変換パス。 | ソース |
process_ui_amount_to_amount | Scaled UI Amountミント用にUI量をトークン量に変換するToken Program変換パス。 | ソース |
Typescript
以下のKitの例では、生成されたinstructionsを直接使用しています。@solana/web3.jsおよび@solana/spl-tokenを使用したレガシーな例も参考として含まれています。
Kit
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 {amountToUiAmountForMintWithoutSimulation,extension,fetchMint,findAssociatedTokenPda,getCreateAssociatedTokenInstructionAsync,getInitializeMintInstruction,getInitializeScaledUiAmountMintInstruction,getMintSize,getMintToCheckedInstruction,getUpdateMultiplierScaledUiMintInstruction,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 initialMultiplier = 5.0;const updatedMultiplier = 10.0;const tokenAmount = 1_000n;const scaledUiAmountExtension = extension("ScaledUiAmountConfig", {authority: client.payer.address,multiplier: initialMultiplier,newMultiplierEffectiveTimestamp: 0n,newMultiplier: initialMultiplier});const mintSpace = BigInt(getMintSize([scaledUiAmountExtension]));const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();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 ScaledUiAmountConfig.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.}),getInitializeScaledUiAmountMintInstruction({mint: mint.address, // Mint account that stores the ScaledUiAmountConfig extension.authority: client.payer.address, // Authority allowed to update the multiplier later.multiplier: initialMultiplier // Initial multiplier used for displayed UI amounts.}),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.})]);const [tokenAccount] = await findAssociatedTokenPda({mint: mint.address,owner: recipient.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});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.})]);const calculatedUiAmountBeforeUpdate =await amountToUiAmountForMintWithoutSimulation(client.rpc,mint.address,tokenAmount);const updateMultiplierInstruction = getUpdateMultiplierScaledUiMintInstruction({mint: mint.address, // Mint account that stores the ScaledUiAmountConfig extension.authority: client.payer, // Signer authorized to update the multiplier.multiplier: updatedMultiplier, // New multiplier used for displayed UI amounts.effectiveTimestamp: 0n // Unix timestamp when the new multiplier takes effect.});await client.sendTransaction([updateMultiplierInstruction]);const calculatedUiAmountAfterUpdate =await amountToUiAmountForMintWithoutSimulation(client.rpc,mint.address,tokenAmount);const mintAccount = await fetchMint(client.rpc, mint.address);const scaledUiAmountConfig = (unwrapOption(mintAccount.data.extensions) ?? []).find((item) => isExtension("ScaledUiAmountConfig", item));console.log("Mint Address:", mint.address);console.log("Token Account:", tokenAccount);console.log("Calculated UI Amount Before Update:",calculatedUiAmountBeforeUpdate);console.log("Calculated UI Amount After Update:",calculatedUiAmountAfterUpdate);console.log("ScaledUiAmountConfig:", scaledUiAmountConfig);
Web3.js
import {Connection,Keypair,PublicKey,sendAndConfirmTransaction,SystemProgram,Transaction,LAMPORTS_PER_SOL} from "@solana/web3.js";import {ASSOCIATED_TOKEN_PROGRAM_ID,createAssociatedTokenAccountInstruction,createInitializeMintInstruction,createInitializeScaledUiAmountConfigInstruction,createMintToCheckedInstruction,createUpdateMultiplierDataInstruction,ExtensionType,getAssociatedTokenAddressSync,getMint,getMintLen,getScaledUiAmountConfig,TOKEN_2022_PROGRAM_ID} from "@solana/spl-token";async function calculateScaledUiAmount(connection: Connection,mintPublicKey: PublicKey,tokenAmount: bigint) {const mintAccount = await getMint(connection,mintPublicKey,"confirmed",TOKEN_2022_PROGRAM_ID);const scaledUiAmountConfig = getScaledUiAmountConfig(mintAccount);if (!scaledUiAmountConfig) {throw new Error("ScaledUiAmountConfig not found");}const clockAccount = await connection.getParsedAccountInfo(new PublicKey("SysvarC1ock11111111111111111111111111111111"));if (!clockAccount.value ||typeof clockAccount.value.data !== "object" ||!("parsed" in clockAccount.value.data)) {throw new Error("Failed to fetch clock sysvar");}const unixTimestamp = Number(clockAccount.value.data.parsed.info.unixTimestamp);const multiplier =unixTimestamp >=Number(scaledUiAmountConfig.newMultiplierEffectiveTimestamp)? scaledUiAmountConfig.newMultiplier: scaledUiAmountConfig.multiplier;const scaledAmount = Math.trunc(Number(tokenAmount) * multiplier);const calculatedUiAmount = scaledAmount / 10 ** mintAccount.decimals;return {calculatedUiAmount: calculatedUiAmount.toString(),scaledUiAmountConfig};}const connection = new Connection("http://localhost:8899", "confirmed");const feePayer = Keypair.generate();const recipient = Keypair.generate();const initialMultiplier = 5.0;const updatedMultiplier = 10.0;const tokenAmount = 1_000n;const airdropSignature = await connection.requestAirdrop(feePayer.publicKey,2 * LAMPORTS_PER_SOL);await connection.confirmTransaction(airdropSignature, "confirmed");const mint = Keypair.generate();const mintLength = getMintLen([ExtensionType.ScaledUiAmountConfig]);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 ScaledUiAmountConfig.lamports: mintRent, // Lamports funding the mint account rent.programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.});const initializeScaledUiAmountInstruction =createInitializeScaledUiAmountConfigInstruction(mint.publicKey, // Mint account that stores the ScaledUiAmountConfig extension.feePayer.publicKey, // Authority allowed to update the multiplier later.initialMultiplier, // Initial multiplier used for displayed UI amounts.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.);await sendAndConfirmTransaction(connection,new Transaction().add(createMintAccountInstruction,initializeScaledUiAmountInstruction,initializeMintInstruction),[feePayer, mint]);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().add(createTokenAccountInstruction,mintToTokenAccountInstruction),[feePayer]);const { calculatedUiAmount: calculatedUiAmountBeforeUpdate } =await calculateScaledUiAmount(connection, mint.publicKey, tokenAmount);const updateMultiplierInstruction = createUpdateMultiplierDataInstruction(mint.publicKey, // Mint account that stores the ScaledUiAmountConfig extension.feePayer.publicKey, // Signer authorized to update the multiplier.updatedMultiplier, // New multiplier used for displayed UI amounts.0n, // Unix timestamp when the new multiplier takes effect.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that owns the mint.);await sendAndConfirmTransaction(connection,new Transaction().add(updateMultiplierInstruction),[feePayer]);const {calculatedUiAmount: calculatedUiAmountAfterUpdate,scaledUiAmountConfig} = await calculateScaledUiAmount(connection, mint.publicKey, tokenAmount);console.log("Mint Address:", mint.publicKey.toBase58());console.log("Token Account:", tokenAccount.toBase58());console.log("Calculated UI Amount Before Update:",calculatedUiAmountBeforeUpdate);console.log("Calculated UI Amount After Update:",calculatedUiAmountAfterUpdate);console.log("ScaledUiAmountConfig:", scaledUiAmountConfig);
Rust
use anyhow::Result;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::{scaled_ui_amount::{instruction, ScaledUiAmountConfig},BaseStateWithExtensions, ExtensionType, StateWithExtensions,},instruction::{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 initial_multiplier = 5.0;let updated_multiplier = 10.0;let token_amount = 1_000u64;let airdrop_signature = client.request_airdrop(&fee_payer.pubkey(), 2_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::ScaledUiAmount])?;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 ScaledUiAmountConfig.&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.);let initialize_scaled_ui_amount_instruction = instruction::initialize(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.&mint.pubkey(), // Mint account that stores the ScaledUiAmountConfig extension.Some(fee_payer.pubkey()), // Authority allowed to update the multiplier later.initial_multiplier, // Initial multiplier used for displayed UI amounts.)?;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_scaled_ui_amount_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?;let mint_account_before_update = client.get_account(&mint.pubkey()).await?;let mint_state_before_update = StateWithExtensions::<Mint>::unpack(&mint_account_before_update.data)?;let scaled_ui_amount_config_before_update =mint_state_before_update.get_extension::<ScaledUiAmountConfig>()?;let clock_account = client.get_account(&clock::ID).await?;let clock: Clock = clock_account.deserialize_data()?;let calculated_ui_amount_before_update = scaled_ui_amount_config_before_update.amount_to_ui_amount(token_amount,mint_state_before_update.base.decimals,clock.unix_timestamp,).unwrap();let update_multiplier_instruction = instruction::update_multiplier(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint.&mint.pubkey(), // Mint account that stores the ScaledUiAmountConfig extension.&fee_payer.pubkey(), // Signer authorized to update the multiplier.&[], // Additional multisig signers.updated_multiplier, // New multiplier used for displayed UI amounts.0, // Unix timestamp when the new multiplier takes effect.)?;let update_multiplier_transaction = Transaction::new_signed_with_payer(&[update_multiplier_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&update_multiplier_transaction).await?;let mint_account_after_update = client.get_account(&mint.pubkey()).await?;let mint_state_after_update = StateWithExtensions::<Mint>::unpack(&mint_account_after_update.data)?;let scaled_ui_amount_config_after_update =mint_state_after_update.get_extension::<ScaledUiAmountConfig>()?;let calculated_ui_amount_after_update = scaled_ui_amount_config_after_update.amount_to_ui_amount(token_amount,mint_state_after_update.base.decimals,clock.unix_timestamp,).unwrap();println!("Mint Address: {}", mint.pubkey());println!("Token Account: {}", token_account);println!("Calculated UI Amount Before Update: {}",calculated_ui_amount_before_update);println!("Calculated UI Amount After Update: {}",calculated_ui_amount_after_update);println!("ScaledUiAmountConfig: {:#?}", scaled_ui_amount_config_after_update);Ok(())}
Is this page helpful?