스케일드 UI 금액 통합 가이드

Solana에서 스케일드 UI 금액 확장 기능 지원하기

배경

스케일드 UI 금액 확장 기능을 통해 토큰 발행자는 사용자의 토큰 잔액의 UI 금액을 계산할 때 사용할 승수를 지정할 수 있습니다. 이를 통해 발행자는 리베이싱 토큰을 생성하고 주식 분할과 같은 기능을 활성화할 수 있습니다. 이 확장 기능은 이자 발생 토큰 확장 기능과 마찬가지로 순전히 시각적인 UI 금액을 제공하므로 팀은 좋은 사용자 경험을 제공하기 위해 추가 작업을 수행해야 합니다. 기본 계산 및 전송은 모두 프로그램에서 원시 금액을 사용하여 이루어집니다.

리소스:

요약

  • 최종 사용자는 가능한 한 토큰 가격, 토큰 잔액 및 토큰 금액에 대해 UIAmount(원시 금액 * 승수)와 상호 작용해야 합니다
  • dApp 및 서비스 제공업체는 모든 계산에 원시 금액과 스케일링되지 않은 가격을 사용하고 최종 단계에서 사용자를 위해 변환해야 합니다
  • 더 쉬운 통합을 위해 스케일링된 금액과 스케일링되지 않은 금액 모두에 대한 과거 가격 피드를 제공해야 합니다
  • 정확한 과거 데이터를 위해 과거 승수 값에 접근할 수 있어야 합니다

용어 정의

  • 승수: UI 금액 계산에 사용되는 정적이지만 업데이트 가능한 승수
  • UIAmount: 승수 * 원시 금액 (일명: 스케일링된 금액)
  • 원시 금액: 금액 (일명: 스케일링되지 않은 금액)

현재 잔액

표시용 현재 금액

  • 스케일드 UI 금액 확장 기능을 사용하는 토큰의 금액을 최종 사용자에게 표시할 때는 다음 중 하나를 사용해야 합니다:
    • UIAmount/UIAmountString (권장)
    • 원시 금액 * 승수의 수동 계산
    • 토큰이 가진 소수점 자릿수에 따라 이 값을 잘라내는 것을 권장합니다.
      • 예: yUSD가 6자리 소수점을 가지고 있고 사용자의 UIAmount가 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"
}
} */

현재 금액 업데이트하기

발행자가 언제든지 승수를 업데이트할 수 있기 때문에 계정 잔액을 최신 상태로 유지하기 위해 주기적으로 폴링하는 것을 고려할 수 있습니다. 발행자는 하루에 한 번 이상 이 승수를 업데이트할 가능성이 낮습니다. 승수가 미래 날짜로 설정된 경우, 이 업데이트 시간에 자동으로 폴링할 수 있습니다

트랜잭션에서의 토큰 금액 (전송 / 스왑 등)

  • 사용자는 조정된 "UIAmount"로 해석될 금액을 입력해야 합니다. 이를 처리하는 앱은 트랜잭션을 위해 원시 토큰 금액으로 변환해야 합니다.
    • 반올림 문제가 있는 경우, 내림하여 트랜잭션 실패 위험보다는 아주 작은 양의 먼지(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 */
  • 앱은 사용자가 "최대" 또는 "전체" 잔액으로 작업을 요청할 때 총 원시 금액을 사용해야 합니다. 이렇게 하면 잔여 먼지가 남지 않습니다.
    • 선택 사항: "최대"가 사용될 때 계정을 자동으로 닫아 사용자에게 저장소 보증금을 환불하는 것을 고려할 수 있습니다.

토큰 가격

  • 토큰 가격은 가능한 한 항상 스케일링된 가격으로 표시되어야 합니다.
  • 오라클과 같은 가격 피드 서비스 제공업체인 경우, 스케일링된 가격과 스케일링되지 않은 가격을 모두 제공해야 합니다.
    • 가능한 한 스케일링된 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 금액 확장을 사용하는 토큰과 호환되도록 dapp을 업데이트하도록 장려하며, 이 과정에서 기꺼이 지원을 제공할 것입니다.

플랫폼별 권장 통합 우선순위

일반 요구사항

요구사항설명우선순위
UiAmount를 사용한 사용자 작업 지원모든 사용자 작업은 앱 전체에서 UiAmount가 활성화된 경우 UiAmount로 입력해야 합니다. 앱에서 UiAmount가 표시되지 않는 경우, 앱이 업데이트될 때까지 원시 금액을 사용해야 합니다.P0

지갑

요구사항설명우선순위
스케일된 잔액 표시스케일된 금액(uiAmount)을 주요 잔액으로 표시합니다.P0
토큰 전송 지원최종 사용자는 스케일된 잔액(원시 금액 × 잔액)으로 전송 금액을 입력해야 합니다.P0
현재 가격 표시사용자에게 스케일된 현재 가격을 표시합니다.P0
거래 내역 메타데이터가능한 모든 전송에 대해 스케일된 금액(UIAmount)을 표시합니다.P1
거래 내역에 승수 업데이트 표시승수 업데이트가 발생하면 사용자의 거래 내역에 이벤트로 표시하고 증가된 금액을 포함합니다.P2
가격 내역 그래프 표시가격 그래프에 스케일된 가격을 반영합니다.P1
온보딩/툴팁스케일된 UI 금액 확장을 사용하는 토큰에 대해 사용자를 교육하기 위한 툴팁이나 온보딩을 제공합니다.P2

익스플로러

요구사항설명우선순위
토큰 상세 페이지 개선총 스케일링된 시가총액 및 현재 승수와 같은 메타데이터 표시P0
잔액에 대한 스케일링된 잔액 표시현재 잔액에 대한 스케일링된 잔액(UiAmount) 표시P0
거래에 대한 스케일링된 잔액 표시과거 거래의 전송 금액에 대한 스케일링된 잔액(UiAmount) 표시P0
거래에 대한 스케일링된 가격 표시이전 거래에 대한 스케일링된 가격 표시P1
승수 업데이트 거래 올바르게 파싱 및 표시승수 업데이트에 대한 세부 정보 올바르게 표시P2

시장 데이터 애그리게이터(예: 코인게코)

요구사항설명우선순위
스케일링된 데이터를 위한 API 업데이트시간에 따른 승수 변화와 스케일링된 가격 피드를 포함하도록 API 기능 확장P0
스케일링 조정이 포함된 총 공급량총 공급량 및 총 시가총액 표시 시 스케일링된 잔액 고려P0
과거 가격 추적시간에 따른 스케일링된 가격을 사용하여 과거 차트 제공P1
과거 승수 추적이자 발생 토큰에 대한 승수 업데이트의 과거 마커 제공P2
교육 콘텐츠 또는 설명스케일링된 토큰의 작동 방식을 설명하는 간단한 설명이나 툴팁 포함P2

가격 피드 제공업체

요구사항설명우선순위
스케일링 및 비스케일링 가격 피드스케일링 및 비스케일링 가격 모두에 대한 가격 피드 제공P0
과거 승수 데이터과거 승수 변경 사항이 포함된 API 제공P0
과거 가격 데이터스케일링 및 비스케일링 금액 모두에 기반한 과거 가격이 포함된 API 제공P0

DEX

요구사항설명우선순위
리베이스된 토큰 잔액 표시UI에서 거래 또는 유동성 제공을 위한 조정된 잔액 표시. (백엔드는 여전히 원시 금액 사용 가능)P0
토큰 작업 지원최종 사용자는 UiAmount 잔액(승수 * 원시 금액)으로 작업 금액을 입력해야 함.P0
가격 피드 적응현재 가격을 표시하는 데 가격 피드가 사용되는 모든 곳에서 최종 사용자에게 조정된 가격 제공.P1
가격 기록 그래프 표시가격 그래프에 조정된 가격 반영P1

Is this page helpful?