缩放 UI 数量集成指南

在 Solana 上支持缩放 UI 数量扩展

背景

Scaled UI Amount 扩展允许代币发行方指定一个乘数,用于计算用户代币余额的 UI 数量。这使得发行方能够创建可调整供应的代币,并实现类似股票拆分的功能。与利息代币扩展类似,此扩展提供的是纯粹的视觉效果 UI 数量,这意味着团队需要额外的工作来提供良好的用户体验。所有底层计算和转账都使用程序中的原始数量进行。

资源:

简而言之

  • 终端用户应尽可能使用 UIAmount(原始数量 * 乘数)来查看代币价格、代币余额和代币数量
  • dApp 和服务提供商应使用原始数量和非缩放价格进行所有计算,并在边缘为用户转换
  • 历史价格数据需要同时提供缩放和非缩放数量,以便于集成
  • 历史乘数值需要可访问,以确保准确的历史数据

术语定义

  • 乘数:用于 UI 数量计算的可更新静态乘数
  • UIAmount:乘数 * 原始数量(又称:缩放数量)
  • 原始数量:数量(又称:非缩放数量)

当前余额

当前显示数量

  • 每当向终端用户显示使用 Scaled UI Amount 扩展的代币数量时,应使用以下方法之一:
    • UIAmount/UIAmountString(首选
    • 手动计算原始数量 * 乘数
    • 我们建议根据代币的小数位数截断此值。
      • 示例:如果 yUSD 有 6 位小数,且用户的 UIAmount 为 1.123456789,则应显示“1.123456”

如何获取这些数据:

  • 对于用户的实时余额,您可以通过调用 getTokenAccountBalance 或 getAccountInfo 获取上述金额的更新信息。
  • 如果您需要知道任意金额的 UI Amount,您可以通过调用 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"
}
} */

更新当前金额

由于发行方可以随时更新乘数,您可以考虑偶尔轮询以保持账户余额的更新。发行方不太可能每天更新此乘数超过一次。如果为未来日期设置了乘数,您可以在此更新时间自动轮询。

交易中的 Token 金额(转账/交换等)

  • 用户应输入被解释为缩放后的“UIAmount”的金额。需要处理此金额的应用程序应将其转换为交易所需的原始 token 金额。
    • 如果存在舍入问题,应向下舍入,并优先留下少量 dust,而不是冒着交易失败的风险。
    • 要进行此转换,您可以使用 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 */
  • 当用户请求使用“最大”或“全部”余额进行操作时,应用程序应使用总的原始金额。这确保不会留下 dust。
    • 可选:您可以考虑在使用“最大”时自动关闭账户,以退还用户的存储押金。

Token 价格

  • Token 价格应尽可能以缩放后的价格显示。
  • 如果您是价格数据提供商,例如 oracle,您应同时提供缩放和未缩放的价格。
    • 尽可能提供一个 SDK/API,以抽象掉缩放 UI 金额扩展的复杂性。

当前乘数

  • 可以随时通过调用 getAccountInfo 从代币铸造中读取当前乘数。此外,如果设置了未来的乘数,这些信息也可以从代币铸造中获取。我们建议不要显示此乘数,因为这可能会让用户体验感到困惑。
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 金额扩展的钱包和应用程序将通过将非缩放价格与原始金额相乘来显示活动的正确总价格。
  • 然而,它们会显示非缩放价格,这可能会导致一些用户困惑。
  • 我们希望这能鼓励团队更新他们的 dapp,以兼容使用缩放 UI 金额扩展的代币,并乐于在此过程中提供支持。

每个平台的推荐集成优先级

通用要求

要求描述优先级
支持使用 UiAmount 的用户操作当应用程序中启用了 UiAmount 时,所有用户操作都应以 UiAmount 输入。如果应用程序中未显示 UiAmount,则应使用原始金额,直到应用程序更新为止。P0

钱包

要求描述优先级
显示缩放余额显示缩放金额(uiAmount)作为主要余额。P0
支持代币转账终端用户应使用其缩放余额(原始金额 * 余额)输入转账金额。P0
显示现货价格为用户显示缩放的现货价格P0
交易历史元数据尽可能在每次转账中显示缩放金额(UIAmount)。P1
在交易历史中显示倍数更新当倍数更新发生时,在用户的交易历史中显示此事件,包括获得的金额。P2
显示价格历史图表在价格图表中反映缩放价格P1
新手引导/工具提示提供工具提示或新手引导,教育用户关于使用缩放 uiAmount 扩展的代币。P2

浏览器

要求描述优先级
代币详情页面增强显示元数据,例如总缩放市值和当前倍数。P0
显示余额的缩放值显示当前余额的缩放值(UiAmount)。P0
显示交易的缩放余额显示历史交易中转账金额的缩放余额(UiAmount)。P0
显示交易的缩放价格显示历史交易的缩放价格。P1
正确解析并显示倍数更新交易正确显示倍数更新的详细信息。P2

市场数据聚合器(例如:CoinGecko)

要求描述优先级
支持缩放数据的 API 更新扩展 API 功能以包括随时间变化的乘数更新以及缩放价格数据。P0
考虑缩放调整的总供应量在显示总供应量和总市值时,需考虑缩放后的余额。P0
历史价格追踪提供使用缩放价格随时间变化的历史价格图表。P1
历史乘数追踪提供利息代币的乘数更新历史标记。P2
教育内容或说明包括简短的描述或工具提示,解释缩放代币的工作原理。P2

价格数据提供商

要求描述优先级
缩放和非缩放价格数据提供缩放和非缩放价格的数据。P0
历史乘数数据提供包含历史乘数变化的 API。P0
历史价格数据提供基于缩放和非缩放金额的历史价格数据的 API。P0

去中心化交易所(DEXes)

要求描述优先级
显示重新基准化的代币余额在用户界面上显示用于交易或流动性提供的缩放余额。(后端仍可使用原始金额)P0
支持代币操作终端用户应使用其 UiAmount 余额(乘数 * 原始金额)输入操作金额。P0
价格数据适配在任何使用价格数据展示当前价格的地方,向终端用户提供缩放价格。P1
显示价格历史图表在价格图表中反映缩放价格。P1

Is this page helpful?