当前位置: 首页 > news >正文

在Solana上使用 Scaled UI Amount 扩展

本指南提供 Solana Web3.js (Legacy v 1.x) 和 Solana Kit (v 2.x) 版本。选择适当的选项卡以查看你首选库的代码片段和说明:

  • Solana Web3.js (Legacy)
  • Solana Kit

概述


Solana Token-2022 程序 引入了强大的扩展,增强了代币功能,使其超越了原始的 SPL Token 程序。其中一个扩展是 Scaled UI Amount(缩放 UI 金额),它允许代币发行者定义一个乘数,该乘数会影响向用户显示的代币余额,而不会更改链上存储的底层原始金额。此扩展启用了强大的用例,包括:

  • 股票拆分和反向股票拆分
  • 视觉上累积利息的计息代币
  • 股息和分配
  • 调整总供应量的重新定价代币

为了使用 Scaled UI Amount 扩展进行构建,开发人员需要了解它的工作原理以及如何在他们的应用程序中实现它。本指南将引导你完成创建具有 Scaled UI Amount 扩展的代币、铸造代币、转移代币以及更新 UI 金额乘数的过程,以了解它如何影响显示的余额。

在本指南中,我们将构建一个完整的演示脚本,该脚本:

  1. 创建一个具有 Scaled UI Amount 扩展的 Token-2022 代币
  2. 将代币铸造给持有者
  3. 在帐户之间转移代币
  4. 更新 UI 乘数
  5. 显示 UI 金额如何在原始余额保持一致的同时发生变化
  6. 铸造和转移额外的代币以观察更新后的乘数的影响

最终目标是生成一个摘要表,清楚地显示演示的每个步骤中原始金额和 UI 金额之间的关系:

=== DEMONSTRATION SUMMARY ===
┌─────────┬───────────────────────────┬──────────────┬────────────┬─────────────┬────────────┐
│ (index) │ Step                      │ Timestamp    │ Multiplier │ Raw Balance │ UI Balance │
├─────────┼───────────────────────────┼──────────────┼────────────┼─────────────┼────────────┤
│ 0       │ 'Initial Setup'           │ '3:02:16 PM' │ 1          │ 'n/a'       │ 'n/a'      │
│ 1       │ 'After Initial Mint'      │ '3:02:17 PM' │ 1          │ '100000000' │ '100'      │
│ 2       │ 'After Transfer #1'       │ '3:02:18 PM' │ 1          │ '90000000'  │ '90'       │
│ 3       │ 'After Multiplier Update' │ '3:02:19 PM' │ 2          │ '90000000'  │ '180'      │
│ 4       │ 'After Second Mint'       │ '3:02:20 PM' │ 2          │ '190000000' │ '380'      │
│ 5       │ 'After Transfer #2'       │ '3:02:21 PM' │ 2          │ '180000000' │ '360'      │
└─────────┴───────────────────────────┴──────────────┴────────────┴─────────────┴────────────┘

让我们开始吧!

前提条件

在开始本教程之前,请确保你已具备:

  • Node.js (v22 或更高版本)
  • Solana CLI v 2.2.x 或更高版本(注意:如果你已经有较旧的版本,你可以使用 agave-install init 2.2.14 或任何最新版本来更新它)
  • 对 Solana 和 TypeScript 的基本了解
  • 熟​悉 Solana Token-2022 程序 和 SPL Token 概念

了解 Scaled UI Amount 扩展

在深入实施之前,让我们了解什么是 Scaled UI Amount 扩展以及它的工作原理。Scaled UI Amount 扩展定义了一个乘数,该乘数应用于代币的原始金额以确定向用户显示的 UI 金额。这允许灵活的代币经济学,而无需更改底层原始金额。

深入了解

该扩展还允许将来更新乘数,从而实现诸如逐渐增加或计划更改之类的功能。这是 Scaled UI Amount 配置的结构:

pub struct ScaledUiAmountConfig {pub authority: OptionalNonZeroPubkey,pub multiplier: PodF64,pub new_multiplier_effective_timestamp: UnixTimestamp,pub new_multiplier: PodF64,
}

(来源: Solana Token-2022 Program)

该配置包括:

  • authority:可以设置缩放金额的授权公钥
  • multiplier:应用于原始金额的当前乘数
  • new_multiplier_effective_timestamp:新乘数生效的时间戳
  • new_multiplier:一旦达到生效时间戳,将应用的新乘数

Scaled UI Amount 扩展引入了两个新指令:

  • initialize: 初始化 mint 的 Scaled UI Amount 扩展 ( src)
  • update_multiplier:更新 mint 的乘数 ( src)

关键概念

Scaled UI Amount 扩展引入了几个重要的关键概念:

  1. 原始金额 vs. UI 金额:
    • 原始金额是链上存储的代币的实际数量
    • UI 金额是用户看到的,计算公式为“原始金额 × 当前乘数”
  2. 乘数:
    • 创建代币时设置
    • 可以由代币的授权机构更新
    • 可以安排以供将来更新
  3. UI 金额更改:
    • 当乘数更改时,UI 金额会成比例地更改
    • 原始金额保持不变

我们的演示将展示这些概念如何在实践中发挥作用。

实际应用

以下是 Scaled UI Amount 扩展的一些实际应用:

用例描述实施方法
股票拆分和反向股票拆分将现有股份分成多个或部分股份调整乘数以反映新的股份数量
计息代币随着时间的推移在视觉上累积利息的代币根据收益率逐步增加乘数
股息分配向代币持有者分配股息调整乘数以反映股息分配
面额变更在同一资产的不同单位之间转换更改乘数以表示新的面额
重新定价代币根据外部因素(如算法稳定币)增加/减少总供应量的代币定期调整乘数以反映供应变化

对现有项目的影响

实施 Scaled UI Amount 扩展需要仔细考虑应用程序的架构和功能。虽然你的影响可能是独一无二的,但以下是一些常见的考虑因素:

  • UI/UX 设计考虑因素:必须更新应用程序以向用户显示缩放的 UI 金额,同时在后端处理原始金额(SPL 代币程序库中添加了一些有用的方法(例如,amountToUiAmountuiAmountToAmount))。 这需要仔细的 UX 设计,以防止用户混淆,尤其是在乘数更改期间。 考虑在代币使用此扩展时添加工具提示或指示器。
  • 历史数据管理:服务应考虑索引原始金额和 UI 金额,以及历史乘数值,以正确显示交易历史和价格图表。 这对于乘数频繁更改的代币尤其重要。
  • 交易处理:在处理转移或交换的用户输入时,应用程序需要在提交交易之前将 UI 金额(用户输入的金额)转换为原始金额(链上处理器使用的金额)。 舍入问题应通过首选向下舍入来处理,以避免交易失败。
  • 价格馈送集成:价格服务应提供缩放和非缩放的价格馈送,以适应不同的客户端需求。 对于市场数据提供商,总供应量和市值计算必须考虑当前乘数才能显示准确的信息。

项目设置

更喜欢直接跳到代码?查看我们在 GitHub 上的示例存储库,获取本指南的完整代码!

  • Solana Web3.js (Legacy)
  • Solana Kit

在我们开始之前,让我们回顾一下我们将要构建的内容。我们将创建一个简单的演示脚本,该脚本:

  1. 创建一个具有 Scaled UI Amount 扩展的 Token-2022 代币
  2. 将代币铸造给持有者
  3. 在帐户之间转移代币
  4. 更新 UI 乘数
  5. 铸造和转移额外的代币以观察更新后的乘数的影响
  6. 记录每个步骤的状态并打印一个摘要表,演示乘数、原始金额和 UI 金额之间的关系

让我们从创建我们的项目结构开始:

mkdir solana-scaled-token-demo && cd solana-scaled-token-demo

初始化一个新的 Node.js 项目:

npm init -y

安装所需的依赖项:

npm install @solana/web3.js@1 @solana/spl-token

以及其他开发依赖项:

npm install --save-dev typescript ts-node @types/node

创建一个 tsconfig.json 文件:

{"compilerOptions": {"target": "es2020","module": "commonjs","lib": ["es2020"],"declaration": true,"outDir": "./dist","rootDir": "./","strict": true,"esModuleInterop": true,"skipLibCheck": true,"forceConsistentCasingInFileNames": true,"resolveJsonModule": true},"include": ["*.ts"],"exclude": ["node_modules", "dist"]
}

更新你的 package.json 脚本:

"scripts": {"start": "ts-node token-creator.ts","build": "tsc"
}

创建一个用于存储密钥对的目录:

mkdir -p keys

为付款人、mint 授权机构、代币持有者和 mint 创建新的 Solana 密钥对:

solana-keygen new -s --no-bip39-passphrase -o keys/payer.json && \
solana-keygen new -s --no-bip39-passphrase -o keys/mint-authority.json && \
solana-keygen new -s --no-bip39-passphrase -o keys/holder.json && \
solana-keygen new -s --no-bip39-passphrase -o keys/mint.json

这应该在 keys 目录中创建四个密钥对文件。

在我们开始之前,让我们回顾一下我们将要构建的内容。我们将创建一个简单的演示脚本,该脚本:

  1. 创建一个具有 Scaled UI Amount 扩展的 Token-2022 代币
  2. 将代币铸造给持有者
  3. 在帐户之间转移代币
  4. 更新 UI 乘数
  5. 铸造和转移额外的代币以观察更新后的乘数的影响
  6. 记录每个步骤的状态并打印一个摘要表,演示乘数、原始金额和 UI 金额之间的关系

让我们从创建我们的项目结构开始:

mkdir solana-scaled-token-demo && cd solana-scaled-token-demo

初始化一个新的 Node.js 项目:

npm init -y

安装所需的依赖项:

npm i @solana/kit @solana-program/token-2022 @solana-program/system

以及其他开发依赖项:

npm install --save-dev typescript ts-node @types/node

创建一个 tsconfig.json 文件:

{"compilerOptions": {"target": "es2020","module": "commonjs","lib": ["es2020"],"declaration": true,"outDir": "./dist","rootDir": "./","strict": true,"esModuleInterop": true,"skipLibCheck": true,"forceConsistentCasingInFileNames": true,"resolveJsonModule": true},"include": ["*.ts"],"exclude": ["node_modules", "dist"]
}

更新你的 package.json 脚本:

"scripts": {"start": "ts-node token-creator.ts","build": "tsc"
}

创建一个用于存储密钥对的目录:

mkdir -p keys

为付款人、mint 授权机构、代币持有者和 mint 创建新的 Solana 密钥对:

solana-keygen new -s --no-bip39-passphrase -o keys/payer.json && \
solana-keygen new -s --no-bip39-passphrase -o keys/mint-authority.json && \
solana-keygen new -s --no-bip39-passphrase -o keys/holder.json && \
solana-keygen new -s --no-bip39-passphrase -o keys/mint.json

这应该在 keys 目录中创建四个密钥对文件。

实施

  • Solana Web3.js (Legacy)
  • Solana Kit

让我们创建我们的 token-creator.ts 文件并逐步构建它:

导入和配置

从必要的导入和配置开始:

import {Connection,Keypair,LAMPORTS_PER_SOL,PublicKey,SystemProgram,Transaction,sendAndConfirmTransaction
} from '@solana/web3.js';import {ExtensionType,TOKEN_2022_PROGRAM_ID,createInitializeMintInstruction,createInitializeScaledUiAmountConfigInstruction,getMintLen,getOrCreateAssociatedTokenAccount,mintTo,updateMultiplier,getScaledUiAmountConfig,unpackMint,createTransferInstruction
} from '@solana/spl-token';import * as fs from 'fs';
import * as path from 'path';const CONFIG = {DECIMAL_PLACES: 6,INITIAL_UI_AMOUNT_MULTIPLIER: 1.0,MODIFIED_UI_AMOUNT_MULTIPLIER: 2.0,TOKEN_NAME: "Scaled Demo Token",TOKEN_SYMBOL: "SDT",MINT_AMOUNT: 100,TRANSFER_AMOUNT: 10,CONNECTION_URL: 'http://127.0.0.1:8899',KEYPAIR_DIR: path.join(__dirname, 'keys')
};

这设置了我们的基本配置,包括:

  • 代币参数(小数位数、乘数、名称、符号)
  • 要 mint 和转移的金额
  • 连接详细信息(我们将为此演示使用我们的 solana 本地测试验证器)
  • 用于存储密钥对的目录

状态日志记录功能

接下来,让我们添加一个状态日志记录系统来跟踪我们整个演示中的更改:

interface StatusLog {step: string;timestamp: string;multiplier: number;rawBalance: string;uiBalance: string;description: string;
}const demoLogs: StatusLog[] = [];async function getTokenMultiplier(connection: Connection,mintPublicKey: PublicKey
): Promise<number> {const mintInfo = await connection.getAccountInfo(mintPublicKey);if (!mintInfo) {throw new Error(`Mint account not found: ${mintPublicKey.toString()}`);}const unpackedMint = unpackMint(mintPublicKey, mintInfo, TOKEN_2022_PROGRAM_ID);const extensionData = getScaledUiAmountConfig(unpackedMint);if (!extensionData) {return 1.0; // Default if no extension data} else {const currentTime = new Date().getTime();if (Number(extensionData.newMultiplierEffectiveTimestamp) < currentTime) {return extensionData.newMultiplier;} else {return extensionData.multiplier;}}
}async function getTokenBalance(connection: Connection,tokenAccount: PublicKey,
): Promise<{ rawAmount: string, uiAmount: string }> {try {const balanceDetail = await connection.getTokenAccountBalance(tokenAccount);return {rawAmount: balanceDetail.value.amount,uiAmount: balanceDetail.value.uiAmountString || '0'};} catch (error) {return {rawAmount: 'n/a',uiAmount: 'n/a'};}
}async function logStatus(connection: Connection,step: string,mintPublicKey: PublicKey,tokenAccount: PublicKey | null,description: string
): Promise<void> {const now = new Date();const timestamp = now.toLocaleTimeString();const multiplier = await getTokenMultiplier(connection, mintPublicKey);let rawBalance = 'n/a';let uiBalance = 'n/a';if (tokenAccount) {const balance = await getTokenBalance(connection, tokenAccount);rawBalance = balance.rawAmount;uiBalance = balance.uiAmount;}demoLogs.push({step,timestamp,multiplier,rawBalance,uiBalance,description});
}function printSummaryTable(): void {console.log("\n=== DEMONSTRATION SUMMARY ===");console.table(demoLogs.map(log => ({Step: log.step,Timestamp: log.timestamp,Multiplier: log.multiplier,"Raw Balance": log.rawBalance,"UI Balance": log.uiBalance})));
}

让我们分解一下这里的关键功能:

  • getTokenMultiplier:获取给定 mint 的当前乘数。它利用 @solana/spl-token 中的 unpackMint 和 getScaledUiAmountConfig 辅助函数来获取 mint 的扩展数据(乘数和生效时间戳)。
  • getTokenBalance:获取给定代币帐户的原始余额和 UI 余额。它使用 Solana web3.js 库中的 getTokenAccountBalance 方法。
  • logStatus:记录每个步骤的状态,包括当前乘数、原始余额和 UI 余额。它还将此信息存储在 demoLogs 数组中以供稍后显示。
  • printSummaryTable:打印所有记录步骤的摘要表,显示乘数、原始余额和 UI 余额之间的关系。

实用功能

现在,让我们添加一些实用功能来处理事务确认和密钥对管理:

async function waitForTransaction(connection: Connection,signature: string,timeout = 30000,transactionNote: string
): Promise<string> {const startTime = Date.now();return new Promise((resolve, reject) => {(async () => {try {let done = false;while (!done && Date.now() - startTime < timeout) {const status = await connection.getSignatureStatus(signature);if (status?.value?.confirmationStatus === 'confirmed' ||status?.value?.confirmationStatus === 'finalized') {done = true;console.log(` ✅ Transaction ${transactionNote} confirmed: ${signature}`);resolve(signature);} else {await new Promise(resolve => setTimeout(resolve, 1000));}}if (!done) {reject(new Error(` ❌ Transaction confirmation timeout after ${timeout}ms`));}} catch (error) {reject(error);}})();});
}async function getOrCreateKeypair(keyPath: string, label: string): Promise<Keypair> {try {if (fs.existsSync(keyPath)) {const keyData = JSON.parse(fs.readFileSync(keyPath, 'utf-8'));const keypair = Keypair.fromSecretKey(new Uint8Array(keyData));return keypair;} else {const keypair = Keypair.generate();fs.writeFileSync(keyPath, JSON.stringify(Array.from(keypair.secretKey)));return keypair;}} catch (error) {const keypair = Keypair.generate();console.log(`Generated new ${label} keypair as fallback: ${keypair.publicKey.toString()}`);return keypair;}
}

这里我们创建了两个实用函数:

  • waitForTransaction - 等待事务确认 (具有超时处理)
  • getOrCreateKeypair - 获取或创建密钥对,将其存储在文件中以便重复使用

核心功能

接下来,让我们为我们的演示添加核心功能。将 setup 函数添加到你的文件中以处理将 SOL 空投到付款人帐户:

async function setup(connection: Connection, payer: Keypair) {try {const airdropSignature = await connection.requestAirdrop(payer.publicKey,2 * LAMPORTS_PER_SOL);await waitForTransaction(connection, airdropSignature, 30000, "airdrop");} catch (error) {console.error('Error funding payer account:', error);console.log('If you are not using a local validator, you need to fund the payer account manually.');}
}

接下来,让我们创建 createScaledToken 函数以创建一个具有 Scaled UI Amount 扩展的新代币:

async function createScaledToken(connection: Connection, payer: Keypair, mint: Keypair, mintAuthority: Keypair) {try {// Calculate space needed for the mint account with Scaled UI Amount extensionconst extensions = [ExtensionType.ScaledUiAmountConfig];const mintLen = getMintLen(extensions);// Calculate lamports needed for rent-exemptionconst mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen);// Create a new token with Token-2022 program & Scaled UI Amount extensionconst transaction = new Transaction().add(// Create account for the mintSystemProgram.createAccount({fromPubkey: payer.publicKey,newAccountPubkey: mint.publicKey,space: mintLen,lamports: mintLamports,programId: TOKEN_2022_PROGRAM_ID,}),// Initialize Scaled UI Amount extensioncreateInitializeScaledUiAmountConfigInstruction(mint.publicKey,mintAuthority.publicKey,CONFIG.INITIAL_UI_AMOUNT_MULTIPLIER,TOKEN_2022_PROGRAM_ID),// Initialize the mintcreateInitializeMintInstruction(mint.publicKey,CONFIG.DECIMAL_PLACES,mintAuthority.publicKey,mintAuthority.publicKey,TOKEN_2022_PROGRAM_ID));const createMintSignature = await sendAndConfirmTransaction(connection,transaction,[payer, mint],{ commitment: 'confirmed' });console.log(` ✅ Token created! Transaction signature: ${createMintSignature}`);console.log(`    Mint address: ${mint.publicKey.toString()}`);return;} catch (error) {console.error('Error creating token:', error);throw error;}
}

此函数创建并发送一个包含三个关键指令的事务:

  1. createAccount:为 mint 创建一个具有所需空间和 lamports 的新帐户,具体取决于扩展(在本例中,仅为 Scaled UI Amount 扩展)
  2. createInitializeScaledUiAmountConfigInstruction:初始化 mint 的 Scaled UI Amount 扩展
  3. createInitializeMintInstruction:使用指定的小数位数和授权机构初始化 mint

现在,让我们添加一个 updateScaledUiAmountMultiplier 函数来更新 UI 金额乘数:

async function updateScaledUiAmountMultiplier(connection: Connection,mint: Keypair,mintAuthority: Keypair,payer: Keypair,newMultiplier: number,startTimestamp: number = 0 // default, 0, is effective immediately
): Promise<string> {try {const signature = await updateMultiplier(connection,payer,mint.publicKey,mintAuthority,newMultiplier,BigInt(startTimestamp),[payer, mintAuthority],undefined,TOKEN_2022_PROGRAM_ID);await waitForTransaction(connection, signature, 30000, "multiplier update");return signature;} catch (error) {console.error(' Error updating UI amount multiplier:', error);throw error;}
}

在这里,我们只是使用 @solana/spl-token 库中的 updateMultiplier 函数来更新 mint 的乘数(请注意,我们将 0 作为新乘数的开始时间戳传递,这意味着它将立即生效),然后等待事务确认后再继续。

接下来,让我们添加一个可重用的 transferTokens 函数来处理帐户之间的代币转账。我们将使用它来演示在更新乘数之前和之后转移代币:

async function transferTokens(connection: Connection,payer: Keypair,source: PublicKey,sourceOwner: Keypair,mint: PublicKey
): Promise<string> {try {const amount = CONFIG.TRANSFER_AMOUNT * (10 ** CONFIG.DECIMAL_PLACES);const destinationOwner = Keypair.generate();const destinationAccount = await getOrCreateAssociatedTokenAccount(connection,payer,mint,destinationOwner.publicKey,false,'confirmed',{},TOKEN_2022_PROGRAM_ID);const tx = new Transaction().add(createTransferInstruction(source,destinationAccount.address,sourceOwner.publicKey,amount,[sourceOwner],TOKEN_2022_PROGRAM_ID));const transferSignature = await sendAndConfirmTransaction(connection,tx,[payer, sourceOwner],{ commitment: 'confirmed' });console.log(` ✅ Tokens transferred! Transaction signature: ${transferSignature}`);return transferSignature;} catch (error) {console.error(' ❌ Error transferring tokens');throw error;}
}

此函数处理:

  • 为转移设置新的目标钱包和代币帐户(这只是一个用于演示的新抛弃帐户)
  • 使用 createTransferInstruction 函数创建转移事务
  • 发送事务并等待确认

主要演示功能

现在,让我们逐步构建主要的 demonstrateScaledToken 函数。首先,添加一个占位符函数,其中包含每个步骤的 TODO:

async function demonstrateScaledToken(): Promise<void> {try {console.log(`=== SCALED TOKEN DEMONSTRATION ===`);console.log(`\n=== Setup ===`);// TODO Add setupconsole.log(`\n=== Step 1: Creating Token Mint ===`);// TODO Create Token Mint with UI Amount Scaled extensionconsole.log(`\n=== Step 2: Creating Holder's Token Account ===`);// TODO Create Holder's Token Accountconsole.log(`\n=== Step 3: Minting Initial Tokens ===`);// TODO Mint Initial Tokens to Holderconsole.log(`\n=== Step 4: Transferring Tokens ===`);// TODO Transfer Tokens to another accountconsole.log(`\n=== Step 5: Updating Scale Multiplier ===`);// TODO Update Scale Multiplierconsole.log(`\n=== Step 6: Minting Additional Tokens ===`);// TODO Mint Additional Tokens to Holderconsole.log(`\n=== Step 7: Transferring Additional Tokens ===`);// TODO Transfer Tokens to another account} catch (error) {console.error('Error in scaled token demonstration:', error);}
}

让我们填写每个部分:

设置

将 // TODO Add setup 替换为:

    const connection = new Connection(CONFIG.CONNECTION_URL, 'confirmed');const payer = await getOrCreateKeypair(path.join(CONFIG.KEYPAIR_DIR, 'payer.json'), 'payer');const mintAuthority = await getOrCreateKeypair(path.join(CONFIG.KEYPAIR_DIR, 'mint-authority.json'), 'mint authority');const mint = await getOrCreateKeypair(path.join(CONFIG.KEYPAIR_DIR, 'mint.json'), 'mint');const holder = await getOrCreateKeypair(path.join(CONFIG.KEYPAIR_DIR, 'holder.json'), 'token holder');await setup(connection, payer);

在此步骤中,我们:

  1. 创建与 Solana 网络的连接
  2. 加载或创建我们将需要的所有帐户的密钥对
  3. 运行我们的 setup 函数来空投资金给付款人帐户
步骤 1:创建代币 Mint

将 // TODO Create Token Mint with UI Amount Scaled extension 替换为:

    await createScaledToken(connection, payer, mint, mintAuthority);await logStatus(connection,"1. After Token Initialized",mint.publicKey,null,"Token created with Scaled UI Amount extension");

此部分:

  1. 创建一个新的具有 Scaled UI Amount 扩展的 Token-2022 代币
  2. 记录初始状态(请注意,尚无代币帐户)
步骤 2:创建持有者的代币帐户

将 // TODO Create Holder's Token Account 替换为:

    const holderTokenAccount = await getOrCreateAssociatedTokenAccount(connection,payer,mint.publicKey,holder.publicKey,false,'confirmed',{},TOKEN_2022_PROGRAM_ID);console.log(` ✅ Holder's token account created: ${holderTokenAccount.address.toString()}`);await logStatus(connection,"2. After ATA Created",mint.publicKey,holderTokenAccount.address,"Holder's token account created");

在这里,我们使用 getOrCreateAssociatedTokenAccount 函数为持有者创建一个关联代币帐户。 这将允许我们将代币直接铸造到持有者的帐户。 请注意,我们正在使用 Token-2022 程序 ID。

步骤 3:Mint 初始代币

将 // TODO Mint Initial Tokens to Holder 替换为:

    const initialMintAmount = CONFIG.MINT_AMOUNT * (10 ** CONFIG.DECIMAL_PLACES);const mintToSignature = await mintTo(connection,payer,mint.publicKey,holderTokenAccount.address,mintAuthority,initialMintAmount,[],{},TOKEN_2022_PROGRAM_ID);await waitForTransaction(connection, mintToSignature, 30000, "initial mint");await logStatus(connection,"3. After Mint #1",mint.publicKey,holderTokenAccount.address,`Minted ${CONFIG.MINT_AMOUNT} tokens with initial multiplier`);

在此步骤中,我们:

  1. 计算要铸造的原始金额(包括小数位数)
  2. 将代币铸造到持有者的帐户
  3. 等待事务确认
  4. 之后记录状态
步骤 4:转移代币

将 // TODO Transfer Tokens to another account 替换为:

    await transferTokens(connection,payer,holderTokenAccount.address,holder,mint.publicKey);await logStatus(connection,"4. After Transfer #1",mint.publicKey,holderTokenAccount.address,`Transferred ${CONFIG.TRANSFER_AMOUNT} tokens to another account`);

此部分:

  1. 将代币从持有者转移到新帐户(回想一下,我们的转移指令使用 CONFIG.TRANSFER_AMOUNT 常量来确定要转移多少代币 - 我们将对两次转移使用相同的原始金额,并比较 UI 金额如何变化)
  2. 转移后记录状态
步骤 5:更新小数位数乘数

将 // TODO Update Scale Multiplier 替换为:

    await updateScaledUiAmountMultiplier(connection,mint,mintAuthority,payer,CONFIG.MODIFIED_UI_AMOUNT_MULTIPLIER);await logStatus(connection,"5. After Multiplier Update",mint.publicKey,holderTokenAccount.address,`Updated multiplier to ${CONFIG.MODIFIED_UI_AMOUNT_MULTIPLIER}x`);

在这里,我们:

  1. 使用 updateScaledUiAmountMultiplier 函数更新 UI 金额乘数
  2. 记录状态以查看 UI 金额如何变化
步骤 6:Mint 额外的代币

将 // TODO Mint Additional Tokens to Holder 替换为:

    const additionalMintSignature = await mintTo(connection,payer,mint.publicKey,holderTokenAccount.address,mintAuthority,initialMintAmount, // Same raw amount as before[],{},TOKEN_2022_PROGRAM_ID```markdown
import * as fs from 'fs';
import * as path from 'path';const CONFIG = {DECIMAL_PLACES: 6,INITIAL_UI_AMOUNT_MULTIPLIER: 1.0,MODIFIED_UI_AMOUNT_MULTIPLIER: 2.0,TOKEN_NAME: "Scaled Demo Token",TOKEN_SYMBOL: "SDT",MINT_AMOUNT: 100,TRANSFER_AMOUNT: 10,HTTP_CONNECTION_URL: 'http://127.0.0.1:8899',WSS_CONNECTION_URL: 'ws://127.0.0.1:8900',KEYPAIR_DIR: path.join(__dirname, 'keys')
};
const LAMPORTS_PER_SOL = BigInt(1_000_000_000);interface Client {rpc: Rpc<SolanaRpcApi>;rpcSubscriptions: RpcSubscriptions<SolanaRpcSubscriptionsApi>;
}

这设置了我们的基本配置,包括:

  • Token 参数 (decimals, multiplier, name, symbol)
  • 要 mint 和 transfer 的数量
  • 连接详细信息(我们将在此演示中使用我们的 solana 本地测试验证器)
  • 用于存储密钥对的目录
  • 用于处理 RPC 和订阅的 Client 接口

Status Logging Functions

接下来,我们添加一个状态日志记录系统,以跟踪整个演示过程中的更改:

interface StatusLog {step: string;timestamp: string;multiplier: number;rawBalance: string;uiBalance: string;description: string;
}const demoLogs: StatusLog[] = [];async function getTokenMultiplier(client: Client,mintAddress: Address
): Promise<number> {try {const mint = await fetchMint(client.rpc, mintAddress);if (!mint.data.extensions || mint.data.extensions.__option === 'None') {return 1.0; // Default if no extensions}const extensionArray = mint.data.extensions.__option === 'Some' ? mint.data.extensions.value : [];const extensionData = extensionArray.find((ext: Extension) => ext.__kind === 'ScaledUiAmountConfig');if (!extensionData) {return 1.0; // Default if no extension data} else {const currentTime = new Date().getTime();if (Number(extensionData.newMultiplierEffectiveTimestamp) < currentTime) {return extensionData.newMultiplier;} else {return extensionData.multiplier;}}} catch (error) {console.error('Error getting token multiplier:', error);return 1.0; // Default on error}
}async function logStatus(client: Client,step: string,mintAddress: Address,tokenAccount: Address | null,description: string
): Promise<void> {const now = new Date();const timestamp = now.toLocaleTimeString();const multiplier = await getTokenMultiplier(client, mintAddress);let rawBalance = 'n/a';let uiBalance = 'n/a';if (tokenAccount) {const balance = await client.rpc.getTokenAccountBalance(tokenAccount).send();rawBalance = balance.value.amount;uiBalance = balance.value.uiAmountString;}demoLogs.push({step,timestamp,multiplier,rawBalance,uiBalance,description});
}function printSummaryTable(): void {console.log("\n=== DEMONSTRATION SUMMARY ===");console.table(demoLogs.map(log => ({Step: log.step,Timestamp: log.timestamp,Multiplier: log.multiplier,"Raw Balance": log.rawBalance,"UI Balance": log.uiBalance})));
}

让我们分解一下这里的关键函数:

  • getTokenMultiplier: 获取给定 mint 的当前 multiplier。它利用来自 @solana-program/token-2022 的 fetchMint 来获取和解析 mint 的扩展数据(multiplier 和 effective timestamp)。
  • 我们使用 getTokenAccountBalance 方法来获取给定 token account 的 raw 和 UI 余额。
  • logStatus: 记录每个步骤的状态,包括当前 multiplier、raw 余额和 UI 余额。它还将此信息存储在 demoLogs 数组中,以供稍后显示。
  • printSummaryTable: 打印所有记录步骤的摘要表,显示 multiplier、raw 余额和 UI 余额之间的关系。

Utility Functions

现在,让我们添加一些 utility function 来处理交易确认和密钥对管理:

async function getOrCreateKeypairSigner(keyPath: string, label: string): Promise<KeyPairSigner<string>> {try {if (!fs.existsSync(keyPath)) {throw new Error(`Keypair file not found: ${keyPath}`);}const keyData = JSON.parse(fs.readFileSync(keyPath, 'utf-8'));const keypair = await createKeyPairSignerFromBytes(new Uint8Array(keyData));return keypair;} catch (error) {const keypair = await generateKeyPairSigner();console.log(`Generated new ${label} keypair as fallback: ${keypair.address}`);return keypair;}
}export const createDefaultTransaction = async (client: Client,feePayer: TransactionSigner
) => {const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();return pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx));
};
export const signAndSendTransaction = async (client: Client,transactionMessage: CompilableTransactionMessage &TransactionMessageWithBlockhashLifetime,commitment: Commitment = 'confirmed'
) => {const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);const signature = getSignatureFromTransaction(signedTransaction);await sendAndConfirmTransactionFactory(client)(signedTransaction, {commitment,});return signature;
};
export const sendAndConfirmInstructions = async (client: Client,payer: TransactionSigner,instructions: IInstruction[]
) => {const signature = await pipe(await createDefaultTransaction(client, payer),(tx) => appendTransactionMessageInstructions(instructions, tx),(tx) => signAndSendTransaction(client, tx));return signature;
};

这里我们创建了一些 utility function:

  • getOrCreateKeypairSigner - 获取或创建密钥对,将其存储在文件中以便重复使用
  • createDefaultTransaction - 创建具有最新 blockhash 和 fee payer 的默认交易
  • signAndSendTransaction - 签名并发送交易,等待确认
  • sendAndConfirmInstructions - 发送并确认一组指令,返回交易签名

Core Functionality

接下来,让我们添加用于演示的核心函数。将 setup 函数添加到你的文件中,以处理将 SOL 空投到 payer account:

async function setup(client: Client, payer: KeyPairSigner<string>) {try {const airdrop = airdropFactory({ rpc: client.rpc, rpcSubscriptions: client.rpcSubscriptions });const airdropTx: Signature = await airdrop({commitment: 'processed',lamports: lamports(LAMPORTS_PER_SOL),recipientAddress: payer.address});console.log(` ✅ Transaction airdrop confirmed: ${airdropTx}`);} catch (error) {console.error(' ❌ Error funding payer account');}
}

接下来,让我们创建 createScaledToken 函数来创建一个新的 token,其中包含 Scaled UI Amount 扩展:

const getCreateMintInstructions = async (input: {authority: Address;client: Client;decimals?: number;extensions?: ExtensionArgs[];freezeAuthority?: Address;mint: TransactionSigner;payer: TransactionSigner;programAddress?: Address;
}) => {const space = getMintSize(input.extensions);const postInitializeExtensions: Extension['__kind'][] = [\'TokenMetadata',\'TokenGroup',\'TokenGroupMember',\];const spaceWithoutPostInitializeExtensions = input.extensions? getMintSize(input.extensions.filter((e) => !postInitializeExtensions.includes(e.__kind))): space;const rent = await input.client.rpc.getMinimumBalanceForRentExemption(BigInt(space)).send();return [\getCreateAccountInstruction({\payer: input.payer,\newAccount: input.mint,\lamports: rent,\space: spaceWithoutPostInitializeExtensions,\programAddress: input.programAddress ?? TOKEN_2022_PROGRAM_ADDRESS,\}),\getInitializeMintInstruction({\mint: input.mint.address,\decimals: input.decimals ?? 0,\freezeAuthority: input.freezeAuthority,\mintAuthority: input.authority,\}),\];
};const createScaledToken = async (input: Omit<Parameters<typeof getCreateMintInstructions>[0],'authority' | 'mint'> & {authority: TransactionSigner;mint?: TransactionSigner;}
): Promise<Address> => {const mint = input.mint ?? (await generateKeyPairSigner());const [createAccount, initMint] = await getCreateMintInstructions({...input,authority: input.authority.address,mint,});const createMintSignature = await sendAndConfirmInstructions(input.client, input.payer, [\createAccount,\...getPreInitializeInstructionsForMintExtensions(\mint.address,\input.extensions ?? []\),\initMint,\...getPostInitializeInstructionsForMintExtensions(\mint.address,\input.authority,\input.extensions ?? []\),\]);console.log(` ✅ Token created! Transaction signature: ${createMintSignature}`);console.log(`    Mint address: ${mint.address}`);return mint.address;
};

此函数使用三个关键指令创建并发送交易:

  1. createAccount: 基于扩展(在本例中,只是 Scaled UI Amount 扩展)使用所需的 space 和 lamports 为 mint 创建一个新 account
  2. getPreInitializeInstructionsForMintExtensions: 为 mint 初始化 Scaled UI Amount 扩展
  3. getPostInitializeInstructionsForMintExtensions: 使用指定的 decimals 和 authority 初始化 mint

 

接下来,让我们添加一些有助于基本 SPL token 操作的函数。我们将使用它们来 mint token 并在 account 之间 transfer token。将以下函数添加到你的文件中:

async function createAta(client: Client, payer: TransactionSigner, mint: TransactionSigner, owner: TransactionSigner): Promise<Address> {const createAta = await getCreateAssociatedTokenIdempotentInstructionAsync({payer,mint: mint.address,owner: owner.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});await sendAndConfirmInstructions(client, payer, [createAta]);const [ata] = await findAssociatedTokenPda({mint: mint.address,owner: owner.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,});console.log(` ✅ Associated token account created: ${ata}`);return ata;
}async function transferTokens(client: Client, payer: TransactionSigner, source: Address, sourceOwner: TransactionSigner, mint: TransactionSigner, amount: bigint) {try {const destination = await generateKeyPairSigner();const destinationTokenAccount = await createAta(client, payer, mint, destination);const transferInstruction = getTransferInstruction({source: source,destination: destinationTokenAccount,authority: sourceOwner,amount: amount,}, {programAddress: TOKEN_2022_PROGRAM_ADDRESS});const txid = await sendAndConfirmInstructions(client, payer, [transferInstruction]);console.log(` ✅ Transfer transaction confirmed: ${txid}`);return txid;} catch (error) {console.error(' ❌ Error transferring tokens');throw error;}
}async function mintTokens(client: Client, payer: TransactionSigner, mintAuthority: TransactionSigner,mint: TransactionSigner,  tokenAccount: Address, amount: bigint) {try {const mintToInstruction = getMintToInstruction({mint: mint.address,token: tokenAccount,amount,mintAuthority}, {programAddress: TOKEN_2022_PROGRAM_ADDRESS});const txid = await sendAndConfirmInstructions(client, payer, [mintToInstruction]);console.log(` ✅ Mint transaction confirmed: ${txid}`);return txid;} catch (error) {console.error(' ❌ Error minting tokens');throw error;}
}

这些函数处理:

  • createAta: 为给定的 mint 和 owner 创建一个关联的 Token Account(我们需要这个来 mint 和 transfer token)
  • transferTokens: 将 token 从一个 account transfer 到另一个 account
  • mintTokens: 将 token mint 到给定的 token account

现在,让我们添加一个 updateMultiplier 函数来更新 UI amount multiplier:

async function updateMultiplier(client: Client, payer: TransactionSigner, mint: TransactionSigner, mintAuthority: TransactionSigner, newMultiplier: number) {try {const updateMultiplierInstruction = getUpdateMultiplierScaledUiMintInstruction({mint: mint.address,authority: mintAuthority,effectiveTimestamp: BigInt(0),multiplier: newMultiplier,}, {programAddress: TOKEN_2022_PROGRAM_ADDRESS});const txid = await sendAndConfirmInstructions(client, payer, [updateMultiplierInstruction]);console.log(` ✅ Update multiplier transaction confirmed: ${txid}`);return txid;} catch (error) {console.error(' ❌ Error updating multiplier');throw error;}
}

在这里,我们只是使用来自 @solana-program/token-2022 库的 getUpdateMultiplierScaledUiMintInstruction 函数来更新 mint 的 multiplier(请注意,我们正在传递 0 作为新 multiplier 的开始 timestamp,这意味着它将立即生效),并等到交易确认后再继续。

Main Demonstration Function

现在,让我们逐步构建主要的 demonstrateScaledToken 函数。首先,添加一个占位符函数,其中包含每个步骤的 TODO:

async function demonstrateScaledToken(): Promise<void> {try {console.log(`=== SCALED TOKEN DEMONSTRATION ===`);console.log(`\n=== Setup ===`);// TODO Add setupconsole.log(`\n=== Step 1: Creating Token Mint ===`);// TODO Create Token Mint with UI Amount Scaled extensionconsole.log(`\n=== Step 2: Creating Holder's Token Account ===`);// TODO Create Holder's Token Accountconsole.log(`\n=== Step 3: Minting Initial Tokens ===`);// TODO Mint Initial Tokens to Holderconsole.log(`\n=== Step 4: Transferring Tokens ===`);// TODO Transfer Tokens to another accountconsole.log(`\n=== Step 5: Updating Scale Multiplier ===`);// TODO Update Scale Multiplierconsole.log(`\n=== Step 6: Minting Additional Tokens ===`);// TODO Mint Additional Tokens to Holderconsole.log(`\n=== Step 7: Transferring Additional Tokens ===`);// TODO Transfer Tokens to another account} catch (error) {console.error('Error in scaled token demonstration:', error);}
}

让我们填写每个部分:

Setup

将 // TODO Add setup 替换为:

        const client: Client = {rpc: createSolanaRpc(CONFIG.HTTP_CONNECTION_URL),rpcSubscriptions: createSolanaRpcSubscriptions(CONFIG.WSS_CONNECTION_URL)};const payer = await getOrCreateKeypairSigner(path.join(CONFIG.KEYPAIR_DIR, 'payer.json'), 'payer');const mintAuthority = await getOrCreateKeypairSigner(path.join(CONFIG.KEYPAIR_DIR, 'mint-authority.json'), 'mint authority');const mint = await getOrCreateKeypairSigner(path.join(CONFIG.KEYPAIR_DIR, 'mint.json'), 'mint');const holder = await getOrCreateKeypairSigner(path.join(CONFIG.KEYPAIR_DIR, 'holder.json'), 'token holder');await setup(client, payer);

在此步骤中,我们:

  1. 设置我们的 client 以连接到 Solana 网络
  2. 加载或创建我们将需要的所有 account 的密钥对
  3. 运行我们的 setup 函数将资金空投到 payer account
Step 1: Creating Token Mint

将 // TODO Create Token Mint with UI Amount Scaled extension 替换为:

        const mintAddress = await createScaledToken({authority: mintAuthority,client,extensions: [\extension('ScaledUiAmountConfig', {\authority: mintAuthority.address,\multiplier: CONFIG.INITIAL_UI_AMOUNT_MULTIPLIER,\newMultiplierEffectiveTimestamp: BigInt(0),\newMultiplier: CONFIG.INITIAL_UI_AMOUNT_MULTIPLIER,\}),\],payer: payer,mint});await logStatus(client,"1. Token Created",mintAddress,null,"Token created with Scaled UI Amount extension");

本节:

  1. 使用 Scaled UI Amount 扩展创建新的 Token-2022 token
  2. 记录初始状态(请注意,还没有 token account)
Step 2: Creating Holder's Token Account

将 // TODO Create Holder's Token Account 替换为:

        const holderTokenAccount = await createAta(client, payer, mint, holder);await logStatus(client,"2. Ata Created",mint.address,holderTokenAccount,"Holder's token account created");

在这里,我们使用我们的 createAta 函数为 holder 创建一个关联的 Token Account。这将允许我们将 token 直接 mint 到 holder 的 account。

Step 3: Minting Initial Tokens

将 // TODO Mint Initial Tokens to Holder 替换为:

        await mintTokens(client, payer, mintAuthority, mint, holderTokenAccount, BigInt(CONFIG.MINT_AMOUNT));await logStatus(client,"3. After Mint #1",mint.address,holderTokenAccount,"Initial tokens minted");

在此步骤中,我们使用 mintTokens 函数将 token mint 到 holder 的 account。我们还在之后记录状态。

Step 4: Transferring Tokens

将 // TODO Transfer Tokens to another account 替换为:

        await transferTokens(client, payer, holderTokenAccount, holder, mint, BigInt(CONFIG.TRANSFER_AMOUNT));await logStatus(client,"4. After Transfer",mint.address,holderTokenAccount,"Tokens transferred");

此步骤:

  1. 将 token 从 holder transfer 到新 account(我们正在使用 CONFIG.TRANSFER\AMOUNT 常量来确定要 transfer 多少 token - 我们将对两个 transfer 使用相同的 raw amount,并比较 UI amount 的差异)
  2. 在 transfer 后记录状态
Step 5: Updating Scale Multiplier

将 // TODO Update Scale Multiplier 替换为:

        await updateMultiplier(client, payer, mint, mintAuthority, CONFIG.MODIFIED_UI_AMOUNT_MULTIPLIER);await logStatus(client,"5. After Update Multiplier",mint.address,holderTokenAccount,"Multiplier updated");

在这里,我们:

  1. 使用 updateMultiplier 函数更新 UI amount multiplier
  2. 记录状态以查看 UI amount 如何变化
Step 6: Minting Additional Tokens

将 // TODO Mint Additional Tokens to Holder 替换为:

        await mintTokens(client, payer, mintAuthority, mint, holderTokenAccount, BigInt(CONFIG.MINT_AMOUNT));await logStatus(client,"6. After Mint #2",mint.address,holderTokenAccount,"Additional tokens minted");

本节:

  1. Mint 与之前相同的 raw amount
  2. 记录状态以查看 UI amount 在新 multiplier 下有何不同
Step 7: Transferring Additional Tokens

将 // TODO Transfer Tokens to another account 替换为:

        await transferTokens(client, payer, holderTokenAccount, holder, mint, BigInt(CONFIG.TRANSFER_AMOUNT));await logStatus(client,"7. After Transfer #2",mint.address,holderTokenAccount,"Additional tokens transferred");printSummaryTable();

最后,我们:

  1. 再次使用相同的 raw amount transfer token
  2. 记录状态,以便我们可以看到 UI amount 与之前的 transfer 有何不同
  3. 打印所有步骤的摘要表

Adding the Main Function Call

在文件末尾添加:

if (require.main === module) {console.log('Starting the Token-2022 Scaled UI Amount demonstration...');demonstrateScaledToken().then(() => console.log(`=== DEMONSTRATION COMPLETED ===`)).catch(error => console.error('Demonstration failed with error:', error));
}

Running the Demonstration

要运行演示:

  1. 打开一个新终端并启动一个本地测试验证器:
solana-test-validator -r
  1. 然后在你的主项目终端中,运行脚本:
npm start

Understanding the Output

这是你应该在输出中看到的:

=== DEMONSTRATION SUMMARY ===
┌─────────┬───────────────────────────┬──────────────┬────────────┬─────────────┬────────────┐
│ (index) │ Step                      │ Timestamp    │ Multiplier │ Raw Balance │ UI Balance │
├─────────┼───────────────────────────┼──────────────┼────────────┼─────────────┼────────────┤
│ 0       │ 'Initial Setup'           │ '3:02:16 PM' │ 1          │ 'n/a'       │ 'n/a'      │
│ 1       │ 'After Initial Mint'      │ '3:02:17 PM' │ 1          │ '100000000' │ '100'      │
│ 2       │ 'After Transfer #1'       │ '3:02:18 PM' │ 1          │ '90000000'  │ '90'       │
│ 3       │ 'After Multiplier Update' │ '3:02:19 PM' │ 2          │ '90000000'  │ '180'      │
│ 4       │ 'After Second Mint'       │ '3:02:20 PM' │ 2          │ '190000000' │ '380'      │
│ 5       │ 'After Transfer #2'       │ '3:02:21 PM' │ 2          │ '180000000' │ '360'      │
└─────────┴───────────────────────────┴──────────────┴────────────┴─────────────┴────────────┘
=== DEMONSTRATION COMPLETED ===

让我们仔细看看演示中的关键点:

  • After Initial Mint,UI amount = Raw amount / 10^decimals,因为我们的 multiplier 是 1.0
  • After Transfer #1,raw 和 UI amount 成比例地减少
  • After Multiplier Update - Raw amount 未更改,UI amount 翻倍(例如,类似于 2:1 的股票分割)
  • After Second Mint - Raw amount 像以前一样增加,但 UI amount 乘以新 multiplier 增加(+ 100 * 2.0)
  • After Transfer #2,raw amount 减少的量与以前相同,但 UI amount 现在减少的量乘以新 multiplier (-10 * 2.0)

Time to Scale!

恭喜!你已成功在 Solana Token-2022 程序中实现了 Scaled UI Amount 扩展。你现在有一个工作演示,展示了如何创建 token、mint 和 transfer token,以及更新 UI amount multiplier。

Scaled UI Amount 扩展为 token 发行方提供了一种强大的机制来控制余额在用户面前的显示方式,而无需修改底层 raw amount。这为在 Solana 上创建创新 token 经济打开了新的可能性。

主要收获:

  • Token-2022 的 Scaled UI Amount 扩展支持 token 经济的高级应用,如股票分割、股息、收益和 rebasing
  • 使用 multiplier 时,raw amount 保持不变,而 UI amount 随 multiplier 缩放
  • 应用程序应适当处理 raw 和 UI amount 之间的转换
  • 历史数据提供商应考虑索引 multiplier 更改以提供准确的历史数据

本文到此结束,更多相关文章,请, https://t.me/gtokentool

相关文章:

在Solana上使用 Scaled UI Amount 扩展

本指南提供 Solana Web3.js (Legacy v 1.x) 和 Solana Kit (v 2.x) 版本。选择适当的选项卡以查看你首选库的代码片段和说明&#xff1a; Solana Web3.js (Legacy)Solana Kit 概述 Solana Token-2022 程序 引入了强大的扩展&#xff0c;增强了代币功能&#xff0c;使其超越了…...

线性表之数组与栈详解

线性表之数组与栈详解 前言一、数组&#xff08;Array&#xff09;1.1 数组的基本概念1.2 数组的实现与操作1.3 数组的应用场景1.4 数组的优缺点 二、栈&#xff08;Stack&#xff09;2.1 栈的基本概念2.2 栈的实现方式2.3 栈的应用场景 2.4 栈的优缺点 三、数组与栈的对比总结…...

车载以太网驱动智能化:域控架构设计与开发实践

title: 车载以太网驱动专用车智能化&#xff1a;域控架构设计与开发实践 date: 2023-12-01 categories: 新能源汽车 tags: [车载以太网, 电子电气架构, 域控架构, 专用车智能化, SOME/IP, AUTOSAR] 引言&#xff1a;专用车智能化转型的挑战与机遇 专用车作为城市建设与工业运输…...

【Redis】redis用作缓存和分布式锁

文章目录 1. 缓存1.1 Redis作为缓存1.2 缓存更新、淘汰策略1.3 缓存预热、缓存穿透、缓存雪崩和缓存击穿1.3.1 缓存预热&#xff08;preheating&#xff09;1.3.2 缓存穿透&#xff08;penetration&#xff09;1.3.3 缓存雪崩&#xff08;avalanche&#xff09;1.3.4 缓存击穿&…...

深度学习中独热编码(One-Hot Encoding)

文章目录 独热编码独热编码的作用独热编码的优点独热编码的缺点场景选择独热编码&#xff08;PyTorch实现&#xff09;替代方案 实际使用分析&#xff1a;对对象类型使用独热编码为什么使用独热编码是合适的&#xff1f;&#x1f4cc; 场景说明&#xff1a;&#x1f4cc; 为什么…...

如何根据竞价数据判断竞价强度,是否抢筹等

竞价强度判断方法 价格变化幅度 观察开盘价与前一交易日收盘价的差距 &#xff1a;如果一只股票在开盘集合竞价阶段价格大幅高于前一交易日收盘价&#xff0c;说明市场对该股票的预期较为积极&#xff0c;可能有资金在主动抬高价格&#xff0c;这是一种较强的竞价强度表现。例如…...

Codex与LangChain结合的智能代理架构:重塑软件开发的未来

🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言:当代码生成遇见智能决策 想象以下场景: 凌晨三点:你需要紧急修复一个遗留系统的内存泄漏漏洞,但代码注释缺失且逻辑复杂; 产品经理需求变更:要求在24小时内将现有…...

2025年PMP 学习十八 第11章 项目风险管理 (11.5~11.7)

2025年PMP 学习十八 第11章 项目风险管理 &#xff08;11.5~11.7&#xff09; 第11章 项目风险管理 序号过程过程组1规划风险管理规划2识别风险规划3实施定性风险分析规划4实施定量风险分析规划5规划风险应对执行6实施风险应对执行7监控风险监控 文章目录 2025年PMP 学习十八…...

2025年PMP 学习十七 第11章 项目风险管理 (11.1~11.4)

2025年PMP 学习十七 第11章 项目风险管理 &#xff08;11.1~11.4&#xff09; 第11章 项目风险管理 序号过程过程组1规划风险管理规划2识别风险规划3实施定性风险分析规划4实施定量风险分析规划5规划风险应对执行6实施风险应对执行7监控风险监控 文章目录 2025年PMP 学习十七…...

impala

Impala&#xff0c;它是 Cloudera 开发的开源 实时 SQL 查询引擎&#xff0c;专为 Hadoop 设计。与 Presto 类似&#xff0c;Impala 用于交互式分析&#xff0c;但架构和设计理念有所不同。以下是 Impala 的核心特点和工作原理&#xff1a; 一、Impala 核心架构 1. 组件组成 …...

湖北理元理律师事务所:债务优化中的双维支持实践解析

在债务压力与生活质量失衡的社会议题下&#xff0c;法律服务机构的功能边界正在从单一的法律咨询向复合型支持延伸。湖北理元理律师事务所通过“法律心理”双维服务模式&#xff0c;探索债务优化与生活保障的平衡路径&#xff0c;其方法论或为行业提供实践参考。 法律框架&…...

Redis设计与实现——Redis命令参考与高级特性

Redis命令参考 数据类型相关命令 SET&#xff1a;设置键值&#xff0c;支持过期时间、不存在/存在条件。GET&#xff1a;获取键值&#xff0c;若键不存在返回 nil。INCR/DECR&#xff1a;将键的整数值增1/减1&#xff0c;键不存在时初始化为0。MSET/MGET&#xff1a;批量设置…...

TCP/UDP协议原理和区别 笔记

从简单到难吧 区别就是TCP一般用于安全稳定的需求&#xff0c;UDP一般用于不那么需要完全数据的需求&#xff0c;比如说直播&#xff0c;视频等。 再然后就是TPC性能慢于UDP。 再然后我们看TCP的原理&#xff08;三次握手&#xff0c;数据传输&#xff0c;四次挥手&#xff0…...

JavaScript基础-对象的相关概念

在JavaScript中&#xff0c;对象是核心的数据结构之一&#xff0c;几乎所有的高级功能都围绕着对象展开。理解对象的基本概念、创建方法以及操作方式对于掌握JavaScript至关重要。本文将详细介绍JavaScript中对象的相关概念&#xff0c;包括对象的定义、属性的操作、原型链和面…...

如何通过交流沟通实现闭环思考模式不断实现自身强效赋能-250517

感谢一直以来和我交流沟通的朋友们。 闭环思考 文字部分&#xff08;25-05-04&#xff09;这一天是青年节&#xff0c;在这一天与青年朋友交流这个是事先规划好的&#xff1a; “可以猜一猜&#xff0c;博士会被撤销吗&#xff1f;导师会被处理吗&#xff1f;千万不要回复&…...

震荡指标工具

一、引言 - 可视化数字烛台工具是对传统蜡烛图的补充&#xff0c;旨在帮助交易者更有效地进入和退出交易&#xff0c;提高交易利润。 - 通过分析蜡烛图&#xff0c;结合烛台震荡指标&#xff0c;提出了一个辅助视觉工具来辅助交易决策。 二、烛台指标与交易策略 -图表通过平均开…...

【自然语言处理与大模型】大模型(LLM)基础知识④

&#xff08;1&#xff09;微调主要用来干什么&#xff1f; 微调目前最主要用在定制模型的自我认知和改变模型对话风格。模型能力的适配与强化只是辅助。 定制模型的自我认知&#xff1a;通过微调可以调整模型对自我身份、角色功能的重新认知&#xff0c;使其回答更加符合自定义…...

返回码的方案对比和思考

前言 一般我们定义 Restful 接口返回码&#xff0c;常见的就是 const 定义 code &#xff0c; StatusToText() 来实现定义 msg&#xff0c; 在封装1 ~ 2个返回方法&#xff08;success、error&#xff09;就可以实现&#xff0c;只是突然想到这样设计是违反开闭原则的&#xf…...

Flink 的任务槽和槽共享

在 Apache Flink 中&#xff0c;任务槽&#xff08;Task Slot&#xff09; 和 槽共享组&#xff08;Slot Sharing Group&#xff09; 是资源管理和任务调度的关键机制。它们决定了 Flink 如何在集群中分配资源并执行任务。 一、任务槽&#xff08;Task Slot&#xff09; 1. 定…...

实验七 基于Python的数字图像水印算法

一、实验目的 掌握图像水印的应用&#xff1b; 掌握我国版权保护的发展现状&#xff1b; 掌握常见的数字图像水印算法。 二、实验内容 学习内容补充&#xff1a; 数字水印的鲁棒性评价主要采用含水印图像提取出的水印与原始水印的相似程度,使用归一化相关(NC&#xff0c;Nor…...

C语言:在 Win 10 上,gcc 如何编译 gtk 应用程序

在 Windows 10 上使用 g&#xff08;或 gcc&#xff09;编译基于 GTK 的 C 语言程序是完全可行的&#xff0c;且相比 Tcc 更为推荐&#xff0c;因为 g&#xff08;GNU 编译器套件&#xff09;对 GTK 的支持更加完善&#xff0c;配置也更简单。以下是详细步骤和注意事项&#xf…...

Vue.js---watch 的实现原理

4.7 watch 的实现原理 watch本质上就是使用了effect以及options.scheduler 定义watch函数&#xff1a; // watch函数:传入参数source以及回调函数function watch(source , cb) {effect(() > source.foo,{scheduler(){// 回调函数cb()}})}watch接收两个参数分别是source和c…...

Linux_ELF文件

目录 前言&#xff1a; 一、ELF文件的类型 二、ELF文件的组成格式 1. ELF头部(ELF Header) 2. 节头表(Section Header Table) 3. 程序头表(Program Header Table) 4. 节(Sections)与段(Segments) 三、ELF文件从形成到加载轮廓 1、ELF可执行文件形成过程 2、 可执行文…...

磁盘I/O子系统

一、数据写入磁盘流程 当执行向磁盘写入数据操作的时候&#xff0c;会发生如下的一系列基本操作。假设文件数据存在于磁盘扇区上&#xff0c;并且已经被读入到页缓存中。 进程使用write()系统调用写入文件。内核更新映射到文件的page cache。内核线程pdflush负责把页缓存刷入…...

【2025 技术指南】如何创建和配置国际版 Apple ID

想要体验更丰富的应用生态、使用国际版专属服务&#xff0c;或访问更多开发者工具&#xff1f;一个国际版 Apple ID 能帮你实现这些需求。本教程将详细介绍如何注册国际版 Apple ID 并正确配置支付信息&#xff0c;全程操作简单&#xff0c;适合开发者和技术爱好者参考。 一、准…...

C++(20): 文件输入输出库 —— <fstream>

目录 一、 的核心功能 二、核心类及功能 三、核心操作示例 1. 文本文件写入&#xff08;ofstream&#xff09; 2. 文本文件读取&#xff08;ifstream&#xff09; 3. 二进制文件操作&#xff08;fstream&#xff09; 四、文件打开模式 五、文件指针操作 六、错误处理技巧…...

05、基础入门-SpringBoot-HelloWorld

05、基础入门-SpringBoot-HelloWorld ## 一、Spring Boot 简介 **Spring Boot** 是一个用于简化 **Spring** 应用初始搭建和开发的框架&#xff0c;旨在让开发者快速启动项目并减少配置文件。 ### 主要特点 - **简化配置**&#xff1a;采用“约定优于配置”的原则&#xff0c;减…...

深入理解指针(6)

目录 1 sizeof和strlen的对⽐ 1.1 sizeof ​编辑1.2strlen 1.3 sizeof 和 strlen的对⽐ 2 数组和指针笔试题解析 2.1 ⼀维数组 ​编辑 2.2 字符数组 3 指针运算笔试题解析 3.1 题⽬1&#xff1a; ​编辑3.2 题目2 3.3 题目3 3.4 题目4 3.5 题目5 3.6 题目6 3.7…...

力扣HOT100之二叉树:108. 将有序数组转换为二叉搜索树

这道题之前做过&#xff0c;思路又给忘了&#xff0c;这道题用递归做是最简单的。 由于得到的数组是有序的&#xff0c;我们只需要取出中间位置的元素medium作为根节点&#xff0c;然后medium左边的剩余元素组成根节点的左子树&#xff0c;medium右边的剩余元素组成根节点的右子…...

手撕I2C和SPI协议实现

手撕I2C和SPI协议实现 目录 I2C协议原理I2C位操作实现I2C驱动代码编写SPI协议原理SPI位操作实现SPI驱动代码编写 I2C协议原理 I2C&#xff08;Inter-Integrated Circuit&#xff09;是一种串行通信总线&#xff0c;使用两根线&#xff1a;SCL&#xff08;时钟线&#xff09…...

452. Minimum Number of Arrows to Burst Balloons

题目描述 这道题用leetcode官方的解答反而搞复杂了。本题其实就是求重叠区间的交集。先按照区间左端点从小到大排序。然后拿出第一个区间作为【当前区间交集】的初始值。遍历后面的区间看那个区间和【当前交集】是否有重叠。如果有重叠则将【当前交集】和【当前区间】求交集并更…...

React 中,闭包陷阱

文章目录 前言1. 经典闭包陷阱示例过期状态问题 2. 解决方案2.1 正确声明依赖数组2.2 使用 useRef 捕获最新值**2.3 使用函数式更新&#xff08;针对状态更新&#xff09;****2.4 使用 useCallback 冻结闭包** **3. 异步操作中的闭包陷阱****事件监听示例** **4. 自定义 Hooks …...

代码复现5——VLMaps

项目地址 1 Setup # 拉取VLMaps仓库,成功运行后会在主目录生成文件夹vlmapsgit clone https://github.com/vlmaps/vlmaps.git#通过 conda 创建虚拟环境conda create -n vlmaps python=3.8 -yconda activate vlmaps #激活环境cd vlmaps # 切换到项目文件下bash install.ba…...

qt6 c++操作qtableview和yaml

保存qtableview数据到yaml文件从yaml文件读取数据到qtableview qtableview在UI界面拖放。 代码是问chat百度的深度探索。 - name: a1address: db1.dbw10type: int - name: a2address: db1.dbx1.0type: bool写到yaml&#xff0c;写前检查 bool plot1::isRowValid(const QStan…...

使用UniApi调用百度地图API的需要注意的地方

目录 前言 一、百度开放平台 1、功能简介 2、地点搜索服务 3、按行政区划检索API 二、Uniapi集成百度API 1、API集成流程 2、访问接口的定义 3、业务调用集成 三、可能遇到的问题 1、指定输出格式无效 2、返回数据的总数 四、总结 前言 在之前的系列博客中&#xf…...

(9)python开发经验

文章目录 1 os.path.join()拼接路径2 条件变量3 添加临时环境变量 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;Qt开发 &#x1f448;&#x1f449;python开发 &#x1f448; 1 os.path.join()拼接路径 os.path.join() 是 Python 中处理文件路径拼接的核心函…...

windows 10 做服务器 其他电脑无法访问,怎么回事?

一般我们会先打开win10自己的防火墙策略&#xff0c;但是容易忽略 电脑之间 路由器上的防火墙&#xff0c;此时也需要查看一下&#xff0c;可以尝试先关闭路由器防火墙&#xff0c;如果可以了&#xff0c;再 设置路由器上的防火墙规则。 将路由器的上网设置 改成 路由模式 &a…...

mysql中limit深度分页详细剖析【爽文】

目录 一 mysql中limit深度分页 1.1 背景描述 1.2 mysql深度分页很慢原因 1.2.1 mysql的sql执行流程 1.2.2 mysql的深度分页很慢原因 1.3 解决办法 1.3.1 覆盖索引 1.3.2 子查询 1.3.3 标签查询 1.3.4 分区表 一 mysql中limit深度分页 1.1 背景描述 Limit深度分页造…...

【C++ Qt】布局管理器

每日激励&#xff1a;“不设限和自我肯定的心态&#xff1a;I can do all things。 — Stephen Curry” &#x1f914;绪论​&#xff1a; 在Qt开发中&#xff0c;界面布局的合理设计是提升用户体验的关键。早期&#xff0c;开发者常采用绝对定位的方式摆放控件&#xff0c;即通…...

Windows系统永久暂停更新操作步骤

目录 Windows系统永久暂停更新操作步骤 打开运行窗口进入注册表编辑器 导航路径图示 新建并配置DWORD值 新建值操作图示数值设置图示 在系统设置中应用暂停 暂停选项图示 注意事项 打开运行窗口 按下键盘上的 Win键 R 组合键&#xff0c;调出“运行”对话框。 进入组策略编…...

Java IO流进阶实战详解(含文件读写、拷贝、加密、字符集)

本文基于 Java 原生 IO 流&#xff0c;从最基础的字节流到字符流&#xff0c;再到实战案例&#xff08;如文件夹拷贝、文件加密等&#xff09;进行逐步深入讲解。适合有一定 Java 基础、希望掌握文件读写操作的。 一、前言 Java IO&#xff08;输入输出&#xff09;是我们日常…...

JavaScript【7】BOM模型

1.概述&#xff1a; BOM&#xff08;Browser Object Model&#xff0c;浏览器对象模型&#xff09;是 JavaScript 中的一个重要概念&#xff0c;它提供了一系列对象来访问和操作浏览器的功能和信息。与 DOM&#xff08;Document Object Model&#xff09;主要关注文档结构不同&…...

STM32F10xx 参考手册

6. 什么是寄存器 本章参考资料&#xff1a;《STM32F10xx 参考手册》、《STM32F10xx数据手册》、 学习本章时&#xff0c;配合《STM32F10xx 参考手册》“存储器和总线架构”及“通用I/O(GPIO)”章节一起阅读&#xff0c;效果会更佳&#xff0c;特别是涉及到寄存器说明的部分。…...

使用instance着色

本节我们学习使用instance着色器进行着色 //拾取var handler new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);handler.setInputAction(function(movement){console.log(movement);var pickedObject viewer.scene.pick(movement.position);if(Cesium.defined(picke…...

MySQL——4、表的约束

表的约束 1、空属性2、默认值3、列描述4、zerofill5、主键6、自增长7、唯一键8、外键9、综合案例 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数据的合法性&#xff0c;从业务逻辑角度保证数据的正确性…...

Datawhale PyPOTS时间序列5月第3次笔记

下游任务的两阶段(two-stage) 处理 下载数据并预处理的程序&#xff1a; # ------------------------------- # 导入必要的库 # ------------------------------- import numpy as np import torch from benchpots.datasets import preprocess_physionet2012 from pypots.imp…...

初探Reforcement Learning强化学习【QLearning/Sarsa/DQN】

文章目录 一、Q-learning现实理解&#xff1a;举例&#xff1a;回顾&#xff1a; 二、Sarsa和Q-learning的区别 三、Deep Q-NetworkDeep Q-Network是如何工作的&#xff1f;前处理&#xff1a;Convolution NetworksExperience Replay 一、Q-learning 是RL中model-free、value-…...

计算机图形学编程(使用OpenGL和C++)(第2版)学习笔记 12.曲面细分

1. 曲面细分 曲面细分着色器&#xff08;Tessellation Shader&#xff09;是OpenGL 4.0及以上版本引入的一种可编程着色器阶段&#xff0c;用于在GPU上对几何体进行细分&#xff0c;将粗糙的多边形网格自动细分为更平滑、更精细的曲面。它主要用于实现高质量的曲面渲染&#x…...

8天Python从入门到精通【itheima】-14~16

目录 第二章学习内容总体预览&#xff1a; 14节-字面量&#xff1a; 1.学习目标&#xff1a;​编辑 2.Python中6大常用数据类型&#xff1a; 3.实现&#xff1a;整数、浮点数、字符串类型的数据输出 4.字面量的定义&#xff1a; 5.小节总结 15节-注释&#xff1a; 1.le…...

Spring Boot 项目的计算机专业论文参考文献

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…...