Scaled UI Amount 集成指南

在 Solana 上支持 Scaled UI Amount 扩展

背景

Scaled UI Amount 扩展允许代币发行方指定一个乘数,用于计算用户代币余额的 UI 显示数量。这使得发行方可以创建弹性代币(rebasing tokens),并实现如股票拆分等功能。该扩展与利息型代币扩展类似,提供的是纯粹用于界面显示的数量,这意味着团队需要做一些额外的工作来优化用户体验。底层的计算和转账操作都仍然使用程序中的原始数量进行。

资源:

TL;DR

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

术语定义

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

当前余额

用于显示的当前数量

  • 当你向最终用户展示使用 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 balance 和 mint 信息
$ curl http://localhost:8899 -s -X POST -H "Content-Type: application/json" -d '
{"jsonrpc": "2.0", "id": 1, "method": "getTokenAccountBalance", "params": ["2uuvxpnEKw52aTqNerHiQ3WeSqZriCMNVt8LhWfrkbPk"]}' | jq .
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.2.14",
"slot": 381130751
},
"value": {
"amount": "10000000",
"decimals": 6,
"uiAmount": 20.0,
"uiAmountString": "20"
}
},
"id": 1
}
  • getAccountInfo
    • 返回 account info 和 mint 信息
$ curl http://localhost:8899 -s -X POST -H "Content-Type: application/json" -d '
{"jsonrpc": "2.0", "id": 1, "method": "getAccountInfo", "params": ["2uuvxpnEKw52aTqNerHiQ3WeSqZriCMNVt8LhWfrkbPk", {"encoding": "jsonParsed"}]}' | jq .
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.2.14",
"slot": 381131001
},
"value": {
"data": {
"parsed": {
"info": {
"extensions": [
{
"extension": "immutableOwner"
},
{
"extension": "pausableAccount"
}
],
"isNative": false,
"mint": "BZCd6HfTbb5ZYJ8hQsm8282tG4QzLyeqFR6tdgQA9EAG",
"owner": "G7ARQSUCwNrfvTDUCZvM5xdiRdBJiN3qVw2PryD8Wnib",
"state": "initialized",
"tokenAmount": {
"amount": "10000000",
"decimals": 6,
"uiAmount": 20.0,
"uiAmountString": "20"
}
},
"type": "account"
},
"program": "spl-token-2022",
"space": 174
},
"executable": false,
"lamports": 2101920,
"owner": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"rentEpoch": 18446744073709551615,
"space": 174
}
},
"id": 1
}

更新当前金额

由于发行方可以随时更新 multiplier,你可以考虑定期轮询以保持账户余额的最新。发行方通常一天不会多次更新 multiplier。如果 multiplier 设置了未来的生效时间,可以在该时间点自动轮询更新。

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

  • 用户应输入要被解释为缩放后的 UIAmount 的金额。需要处理该金额的应用应将其转换为原始 token amount 以用于交易。
    • 如果存在舍入问题,建议向下取整,宁可留下极少量 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 */
  • 当用户请求使用其余额的 “max” 或 “all” 进行操作时,应用应使用总原始金额。这可以确保不会留下任何零碎余额。
    • 可选:当使用 “max” 退还用户存储押金时,你可以考虑自动关闭账户。

代币价格

  • 代币价格应尽可能始终以缩放后的价格显示。
  • 如果你是价格数据服务提供商(如 oracle),应同时提供缩放和未缩放的价格。
    • 尽可能提供可抽象缩放 UI 金额扩展复杂性的 SDK/API。

当前乘数

  • 可随时通过调用 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 金额扩展)
  • 我们预计缩放金额会被更频繁地使用,因为这与传统金融领域处理带有拆股的代币相关图表的方式一致。
  • 乘数更新可能包含一个过去的时间戳。我们建议历史数据使用区块时间戳。
  • 请注意,当前生效的乘数可能是“multiplier”或“newMultiplier”,具体取决于当前时间戳以及新乘数的生效时间。

金额的历史数据

  • 如果你想显示过去转账的余额,需要获取当时 slot 的乘数。你也可以在处理交易时保存转账的 UiAmount,以便将来无需再次计算。

向后兼容性

  • 默认情况下,不支持缩放 UI 金额扩展的钱包和应用会通过非缩放价格 * 原始金额来显示活动的正确总价。
  • 但它们会显示未缩放的价格,可能会让用户感到困惑。
  • 我们希望这能鼓励团队升级他们的 dapp,以兼容使用缩放 UI 金额扩展的代币,并且我们很乐意在此过程中提供支持。

各平台推荐集成优先级

通用要求

要求描述优先级
支持使用 UiAmount 的用户操作当应用启用 UiAmount 时,所有用户操作都应以 UiAmount 输入。如果应用未显示 UiAmount,则应继续使用原始金额,直到应用升级。P0

钱包

要求描述优先级
显示缩放余额将缩放金额(uiAmount)作为主要余额显示。P0
支持代币转账终端用户应以其缩放余额(原始金额 * 余额)输入转账金额。P0
显示现货价格为用户显示缩放后的现货价格。P0
交易历史元数据在可能的情况下,为每笔转账显示缩放金额(UIAmount)。P1
在交易历史中显示乘数更新当发生乘数更新时,在用户的交易历史中显示该事件,包括获得的金额。P2
显示价格历史图表在价格图表中反映缩放后的价格。P1
新手引导/工具提示提供工具提示或新手引导,帮助用户了解使用缩放 ui amount 扩展的代币。P2

区块浏览器

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

市场数据聚合平台(如 CoinGecko、Birdeye)

要求描述优先级
缩放数据的 API 更新扩展 API 功能,包含乘数随时间变化以及缩放价格数据。P0
总供应量按缩放调整显示总供应量和总市值时,需考虑缩放余额。P0
历史价格追踪提供基于缩放价格的历史价格走势图。P1
历史乘数追踪为生息代币提供乘数更新的历史标记。注意,乘数更新可能包含过去的时间戳。建议使用区块时间戳而非乘数更新中指示的时间戳作为历史数据。P2
教育内容或说明提供简要描述或工具提示,解释缩放代币的工作原理。P2

价格源提供方

需求描述优先级
缩放与非缩放价格源提供缩放和非缩放价格的价格源。P0
历史乘数数据提供包含历史乘数变更的 API。注意,乘数更新可能包含过去的时间戳。我们建议在历史数据中使用区块时间戳,而不是乘数更新中标注的时间戳。P0
历史价格数据提供基于缩放和非缩放金额的历史价格 API。P0

去中心化交易所(DEXes)

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

中心化交易所(CEXes)

需求描述优先级
跟踪乘数更新跟踪使用缩放 UI 金额扩展的代币的乘数更新。P0
显示重定基代币余额在界面上显示用于交易或流动性提供的缩放余额(后端仍可使用原始金额)。P0
支持代币操作终端用户应以其 UiAmount 余额(乘数 * 原始金额)输入操作金额。P0
历史操作不应重新缩放历史操作(如交易)应以操作当时的准确缩放金额和价格显示。P1
内部跟踪原始余额对链上交易应跟踪原始余额而非缩放余额,这样长期来看更准确且更易管理。P1
价格源适配任何使用价格源显示当前价格的地方,都应向终端用户提供缩放价格。P1
显示价格历史图表在价格图表中反映缩放价格,包括将历史价格按当前乘数重新缩放。P1
缩放成本基准每股成本应按当前乘数进行缩放。P1

Is this page helpful?

管理者

©️ 2026 Solana 基金会版权所有
取得联系