スケールドUI金額統合ガイド

SolanaでスケールドUI金額拡張機能をサポートする

背景

スケールドUI金額拡張機能により、トークン発行者はユーザーのトークン残高のUI金額を計算する際に使用する乗数を指定できます。これにより、発行者はリベーストークンを作成したり、株式分割などを可能にしたりできます。この拡張機能は、利子付きトークン拡張機能と同様に、純粋に見た目上のUI金額を提供するため、チームは良い体験を提供するために追加作業を行う必要があります。基礎となる計算や送金はすべてプログラム内の生の金額を使用して行われます。

リソース:

要約

  • エンドユーザーは可能な限り、トークン価格、トークン残高、トークン金額にUIAmount(生の金額 × 乗数)を使用するべきです
  • dAppやサービスプロバイダーはすべての計算に生の金額と非スケール価格を使用し、ユーザー向けの表示時に変換するべきです
  • より簡単な統合のために、スケールされた金額と非スケールの金額の両方の履歴価格フィードを提供する必要があります
  • 正確な履歴データのために、過去の乗数値にアクセスできるようにする必要があります

用語の定義

  • 乗数:UI金額計算に使用される静的で更新可能な乗数
  • UI金額:乗数 × 生の金額(別名:スケールされた金額)
  • 生の金額:金額(別名:非スケールの金額)

現在の残高

表示用の現在の金額

  • スケールドUI金額拡張機能を使用するトークンの金額をエンドユーザーに表示する場合は、以下のいずれかを使用するべきです:
    • UIAmount/UIAmountString(推奨
    • 生の金額 × 乗数の手動計算
    • トークンの小数点以下の桁数に基づいてこの値を切り捨てることをお勧めします。
      • 例:yUSDが6桁の小数点を持ち、ユーザーのUI金額が1.123456789の場合、「1.123456」と表示するべきです

このデータの入手先:

  • ユーザーのリアルタイム残高については、getTokenAccountBalanceまたはgetAccountInfoを呼び出すことで上記の金額に関する最新情報を取得できます
  • 任意の金額のUI金額を知る必要がある場合は、 amountToUiAmountForMintWithoutSimulation (web3.js v1)関数を呼び出すか、amountToUiAmountを使用してトランザクションをシミュレートすることでこの計算を取得できます。
    • 注意:amountToUiAmountはトランザクションシミュレーションを必要とするため、残高を持つ有効な手数料支払者も必要です。このため、残高を取得するデフォルトの方法としては使用すべきではありません。

RPC呼び出し

  • getTokenAccountBalance
    • token accountの残高とmint情報を返します
import { address, createSolanaRpc } from "@solana/kit";
const rpc_url = "https://api.devnet.solana.com";
const rpc = createSolanaRpc(rpc_url);
let tokenAddress = address("2uuvxpnEKw52aTqNerHiQ3WeSqZriCMNVt8LhWfrkbPk");
let tokenBalance = await rpc.getTokenAccountBalance(tokenAddress).send();
console.log("Token Balance:", tokenBalance);
/* Token Balance: {
context: { apiVersion: '2.2.14', slot: 381132711n },
value: {
amount: '10000000',
decimals: 6,
uiAmount: 20,
uiAmountString: '20'
}
} */
  • getAccountInfo
    • アカウント情報とmint情報を返します
import { address, createSolanaRpc } from "@solana/kit";
const rpc_url = "https://api.devnet.solana.com";
const rpc = createSolanaRpc(rpc_url);
const publicKey = address("2uuvxpnEKw52aTqNerHiQ3WeSqZriCMNVt8LhWfrkbPk");
const accountInfo = await rpc.getAccountInfo(publicKey).send();
console.log(
"Account Info:",
JSON.stringify(
accountInfo,
(key, value) => (typeof value === "bigint" ? value.toString() : value),
2
)
);
/* Account Info: {
"context": {
"apiVersion": "2.2.14",
"slot": "381133640"
},
"value": {
"data": {
"parsed": {
"info": {
"extensions": [
{
"extension": "immutableOwner"
},
{
"extension": "pausableAccount"
}
],
"isNative": false,
"mint": "BZCd6HfTbb5ZYJ8hQsm8282tG4QzLyeqFR6tdgQA9EAG",
"owner": "G7ARQSUCwNrfvTDUCZvM5xdiRdBJiN3qVw2PryD8Wnib",
"state": "initialized",
"tokenAmount": {
"amount": "10000000",
"decimals": 6,
"uiAmount": 20,
"uiAmountString": "20"
}
},
"type": "account"
},
"program": "spl-token-2022",
"space": "174"
},
"executable": false,
"lamports": "2101920",
"owner": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"rentEpoch": "18446744073709551615",
"space": "174"
}
} */

現在の金額の更新

発行者はいつでも乗数を更新できるため、アカウント残高を最新の状態に保つために定期的にポーリングすることを検討できます。発行者がこの乗数を更新するのは1日に1回以上行うことはほとんどありません。将来の日付に乗数が設定されている場合は、この更新時間に自動的にポーリングすることができます

トランザクション内のトークン金額(転送/スワップなど)

  • ユーザーは、スケーリングされた「UIAmount」として解釈される金額を入力する必要があります。これを処理するアプリケーションは、トランザクション用に生のトークン金額に変換する必要があります。
    • 丸め問題がある場合は、切り捨てて、トランザクションが失敗するリスクよりもわずかな端数を残すことを優先してください
    • この変換を行うには、 uiAmountToAmountForMintWithoutSimulation (web3.js v1)関数を使用するか、amountToUiAmountを使用してトランザクションをシミュレートします。
web3js-uiAmountToAmountForMintWithoutSimulation.ts
import { uiAmountToAmountForMintWithoutSimulation } from "@solana/web3.js";
import { Connection, PublicKey, clusterApiUrl } from "@solana/web3.js";
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const mint = new PublicKey("BZCd6HfTbb5ZYJ8hQsm8282tG4QzLyeqFR6tdgQA9EAG");
const uiAmount = "20.2";
const rawAmount = await uiAmountToAmountForMintWithoutSimulation(
connection as unknown as Connection,
mint,
uiAmount
);
console.log("Raw Amount:", rawAmount);
/* Raw Amount: 20200000 */
  • アプリケーションは、ユーザーが残高の「最大」または「すべて」でアクションを実行するよう要求した場合、合計の未加工金額を使用する必要があります。これにより、ダスト(少額残高)が残らないようにします。
    • オプション:「最大」が使用された場合、ユーザーのストレージデポジットを返金するためにアカウントを自動的に閉じることを検討できます

トークン価格

  • トークン価格は可能な限り常にスケーリングされた価格として表示されるべきです。
  • 価格フィードサービスプロバイダー(オラクルなど)の場合、スケーリングされた価格と未スケーリングの価格の両方を公開する必要があります。
    • 可能な限り、スケーリングされたUI金額拡張の複雑さを抽象化するSDK/APIを提供してください。

現在の乗数

  • 現在の乗数は、いつでもトークンミントから getAccountInfo を呼び出すことで読み取ることができます。さらに、将来の乗数が設定されている場合、この情報もトークンミントから入手できます。UXを混乱させる可能性があるため、この乗数を表示しないことをお勧めします。
import { address, createSolanaRpc } from "@solana/kit";
const rpc_url = "https://api.devnet.solana.com";
const rpc = createSolanaRpc(rpc_url);
const publicKey = address("BZCd6HfTbb5ZYJ8hQsm8282tG4QzLyeqFR6tdgQA9EAG");
const accountInfo = await rpc
.getAccountInfo(publicKey, { encoding: "jsonParsed" })
.send();
const mintData = accountInfo.value?.data as Readonly<{
parsed: {
info?: {
extensions: {
extension: string;
state: object;
}[];
};
type: string;
};
program: string;
space: bigint;
}>;
const scaledUiAmountConfig = mintData.parsed.info?.extensions?.find(
(extension) => extension.extension === "scaledUiAmountConfig"
) as Readonly<{
state: {
newMultiplierEffectiveTimestamp: number;
newMultiplier: number;
multiplier: number;
};
}>;
const currentMultiplier =
scaledUiAmountConfig?.state &&
Date.now() / 1000 >=
scaledUiAmountConfig.state.newMultiplierEffectiveTimestamp
? scaledUiAmountConfig.state.newMultiplier
: scaledUiAmountConfig.state.multiplier;
console.log("Scaled UI Amount Config:", scaledUiAmountConfig);
console.log("Current Multiplier:", currentMultiplier);
/*
Scaled UI Amount Config: {
extension: 'scaledUiAmountConfig',
state: {
authority: 'G7ARQSUCwNrfvTDUCZvM5xdiRdBJiN3qVw2PryD8Wnib',
multiplier: '2',
newMultiplier: '2',
newMultiplierEffectiveTimestamp: 1743000000n
}
}
Current Multiplier: 2
*/

履歴データ

価格フィードの履歴データ

  • 履歴データを提供するサービスは、スケーリングされたUI金額拡張のために、スケーリングされた価格と未スケーリングの価格の両方を保存し、表示する必要があります。
  • スケーリングされた金額は最も頻繁に使用されると予想されます。これは、従来の金融界がストック分割を持つトークンに関連するチャートを扱う方法と一致しています。

金額の履歴データ

  • 過去に転送された残高を表示したい場合は、特定のslotでの乗数にアクセスする必要があります。将来この計算を避けるために、トランザクションを処理する際に転送のUiAmountを保存することもできます。

後方互換性

  • デフォルトでは、スケーリングされたUI金額拡張機能を理解していないウォレットやアプリケーションは、非スケーリング価格×生の金額を乗算することで、アクティビティの正確な合計価格を表示します。
  • ただし、非スケーリング価格が表示されるため、ユーザーに混乱を招く可能性があります。
  • これにより、各チームがスケーリングされたUI金額拡張機能を使用するトークンと互換性を持つようdappsを更新することを奨励し、このプロセス中のサポートを喜んで提供します。

プラットフォーム別の推奨統合優先事項

一般要件

要件説明優先度
UiAmountを使用したユーザーアクションのサポートすべてのユーザーアクションは、アプリ全体でUiAmountが有効になっている場合、UiAmountで入力する必要があります。アプリでUiAmountが表示されていない場合は、アプリが更新されるまで生の金額を使用する必要があります。P0

ウォレット

要件説明優先度
スケーリングされた残高の表示スケーリングされた金額(uiAmount)を主要な残高として表示します。P0
トークン転送のサポートエンドユーザーはスケーリングされた残高(生の金額 × 残高)で転送金額を入力する必要があります。P0
スポット価格の表示ユーザー向けにスケーリングされたスポット価格を表示しますP0
取引履歴メタデータ可能な限り、各転送のスケーリングされた金額(UIAmount)を表示します。P1
取引履歴での乗数更新の表示乗数の更新が発生した場合、獲得した金額を含めてこれをユーザーの取引履歴にイベントとして表示しますP2
価格履歴グラフの表示価格グラフにスケーリングされた価格を反映させますP1
オンボーディング/ツールチップスケーリングされたUI金額拡張機能を使用するトークンについてユーザーを教育するためのツールチップまたはオンボーディングを提供しますP2

エクスプローラー

要件説明優先度
トークン詳細ページの強化総スケール済み時価総額や現在の乗数などのメタデータを表示P0
残高のスケール済み残高表示現在の残高にスケール済み残高(UiAmount)を表示P0
取引のスケール済み残高表示過去の取引の送金額にスケール済み残高(UiAmount)を表示P0
取引のスケール済み価格表示過去の取引のスケール済み価格を表示P1
乗数更新取引の適切な解析と表示乗数更新に関する詳細を適切に表示P2

市場データアグリゲーター(例:CoinGecko)

要件説明優先度
スケール済みデータのAPI更新時間の経過に伴う乗数の変更やスケール済み価格フィードを含むようにAPI機能を拡張P0
スケール調整を含む総供給量総供給量と時価総額を表示する際、スケール済み残高を考慮P0
過去の価格追跡時間の経過に伴うスケール済み価格を使用した過去のチャートを提供P1
過去の乗数追跡利子付きトークンの乗数更新の過去のマーカーを提供P2
教育コンテンツや説明スケール済みトークンの仕組みを説明する簡単な説明やツールチップを含めるP2

価格フィードプロバイダー

要件説明優先度
スケール済みと非スケール価格フィードスケール済みと非スケール両方の価格フィードを提供P0
過去の乗数データ過去の乗数変更を含むAPIを提供P0
過去の価格データスケール済みと非スケール両方の金額に基づく過去の価格を含むAPIを提供P0

DEX(分散型取引所)

要件説明優先度
リベース済みトークン残高の表示UI上で取引や流動性提供のためにスケーリングされた残高を表示する(バックエンドでは生の数量を使用可能)P0
トークンアクションのサポートエンドユーザーはUiAmount残高(乗数 * 生の数量)でアクション数量を入力する必要があるP0
価格フィードの適応現在の価格を表示するために価格フィードが使用される場所では、エンドユーザーにスケーリングされた価格を提供するP1
価格履歴グラフの表示価格グラフにスケーリングされた価格を反映するP1

Is this page helpful?