41 minutos de lectura
Esta guía está disponible en las versiones de Solana Web3.js (versión heredada 1.x) y Solana Kit (v. 2.x). Selecciona la pestaña correspondiente para ver los fragmentos de código y las instrucciones de la biblioteca que prefieras:
- Solana Web3.js (versión anterior)
- Kit Solana
Resumen
El programa Solana Token-2022 introdujo potentes ampliaciones que mejoran la funcionalidad de los tokens más allá del programa SPL Token original. Una de estas ampliaciones es la «Scaled UI Amount», que permite a los emisores de tokens definir un multiplicador que influye en cómo se muestran los saldos de los tokens a los usuarios sin modificar los importes brutos subyacentes almacenados en la cadena. Esta ampliación permite potentes casos de uso, entre los que se incluyen:
- Divisiones y agrupaciones de acciones
- Tokens que generan rendimiento y en los que se puede ver cómo se acumulan los intereses
- Dividendos y repartos
- Reajuste de los tokens que modifican el suministro total
Para desarrollar con la extensión «Scaled UI Amount», los desarrolladores deben comprender cómo funciona y cómo implementarla en sus aplicaciones. Esta guía te guiará a lo largo del proceso de creación de un token con la extensión «Scaled UI Amount», la emisión de tokens, su transferencia y la actualización del multiplicador de la cantidad en la interfaz de usuario para ver cómo afecta a los saldos mostrados.
En esta guía, crearemos un script de demostración completo que:
- Crea un token Token-2022 con la extensión «Scaled UI Amount»
- Acredeita a un titular
- Transfiere tokens entre cuentas
- Actualiza el multiplicador de la interfaz de usuario
- Muestra cómo varía el importe que se muestra en la interfaz de usuario, mientras que los saldos brutos se mantienen constantes
- Crea y transfiere tokens adicionales para observar los efectos del multiplicador actualizado
El objetivo final será generar una tabla resumen que muestre claramente la relación entre las cantidades brutas y las de la interfaz de usuario en cada paso de la demostración:
=== RESUMEN DE LA DEMOSTRACIÓN ===
┌─────────┬───────────────────────────┬──────────────┬────────────┬─────────────┬────────────┐
│ (índice) │ Paso │ Marca de tiempo │ Multiplicador │ Saldo bruto │ Saldo de la interfaz de usuario │
├─────────┼───────────────────────────┼──────────────┼────────────┼─────────────┼────────────┤
│ 0 │ «Configuración inicial» │ «15:02:16» │ 1 │ «n/a» │ «n/a» │
│ 1 │ «Tras la emisión inicial» │ «15:02:17» │ 1 │ «100000000» │ «100» │
│ 2 │ «Tras la transferencia n.º 1» │ «3:02:18 p. m.» │ 1 │ «90000000» │ «90» │
│ 3 │ «Tras la actualización del multiplicador» │ «3:02:19 p. m.» │ 2 │ «90000000» │ «180» │
│ 4 │ «Tras la segunda emisión» │ «3:02:20 p. m.» │ 2 │ «190000000» │ «380» │
│ 5 │ «Tras la transferencia n.º 2» │ «3:02:21 p. m.» │ 2 │ «180000000» │ «360» │
└─────────┴───────────────────────────┴──────────────┴────────────┴─────────────┴────────────┘
¡Empecemos!
Requisitos previos
Antes de empezar este tutorial, asegúrate de que dispones de:
- Node.js (v22 o posterior)
- Solana CLI v 2.2.x o superior (Nota: si ya tienes una versión anterior, puedes actualizarla con
agave-install init 2.2.14o la versión más reciente, la que sea) - Conocimientos básicos sobre Solana y TypeScript
- Conocimiento del programa Solana Token-2022 y de los conceptos relacionados con los tokens SPL
Comprender la extensión «Scaled UI Amount»
Antes de entrar en detalles sobre la implementación, veamos qué es la extensión «Scaled UI Amount» y cómo funciona. La extensión «Scaled UI Amount» define un multiplicador que se aplica a la cantidad bruta de tokens para determinar la cantidad que se muestra a los usuarios en la interfaz de usuario. Esto permite una economía de tokens flexible sin modificar las cantidades brutas subyacentes.
Entre bastidores
La extensión también permite actualizar el multiplicador en el futuro, lo que da lugar a funciones como aumentos graduales o cambios programados. A continuación se muestra la estructura de la configuración de «Scaled UI Amount»:
pub struct ScaledUiAmountConfig {
pub authority: OptionalNonZeroPubkey,
pub multiplier: PodF64,
pub new_multiplier_effective_timestamp: UnixTimestamp,
pub new_multiplier: PodF64,
}
(Fuente: Programa Solana Token-2022)
La configuración incluye:
autoridad: Clave pública autorizada que puede establecer el importe de la escalaciónmultiplicador: El multiplicador actual aplicado al importe brutomarca_de_tiempo_del_nuevo_multiplicador_efectivo: La fecha y hora en la que entra en vigor el nuevo multiplicadornuevo_multiplicador: El nuevo multiplicador que se aplicará una vez que se alcance la marca de tiempo efectiva
La extensión «Scaled UI Amount» introduce dos nuevas instrucciones:
inicializar: Inicializa la extensión «Scaled UI Amount» para una casa de moneda (src)multiplicador_de_actualización: Actualiza el multiplicador de una moneda (src)
Conceptos clave
La extensión «Scaled UI Amount» introduce varios conceptos clave que es importante comprender:
-
Importe bruto frente a importe de la prestación por desempleo:
- La «cantidad bruta» es el número real de tokens almacenados en la cadena.
- Importe de la prestación por desempleo es lo que ven los usuarios, y se calcula como
Importe bruto × Multiplicador actual
-
Multiplicador:
- Se establece al crear el token
- Puede ser actualizado por la autoridad del token
- Se puede programar para futuras actualizaciones
-
Cambios en el importe de la prestación por desempleo:
- Cuando cambia el multiplicador, el importe que aparece en la interfaz de usuario cambia proporcionalmente.
- Las cantidades brutas no varían
Nuestra demostración mostrará cómo funcionan estos conceptos en la práctica.
Aplicaciones en la vida real
A continuación se muestran algunas aplicaciones prácticas de la extensión «Scaled UI Amount»:
| Caso de uso | Descripción | Enfoque de implementación |
|---|---|---|
| Divisiones y agrupaciones de acciones | División de las acciones existentes en varias acciones o en acciones fraccionarias | Ajustar el multiplicador para que refleje el nuevo número de acciones |
| Tokens que generan rendimiento | Tokens que muestran visualmente cómo se acumulan los intereses con el paso del tiempo | Aumentar gradualmente el multiplicador en función de la tasa de rendimiento |
| Distribución de dividendos | Distribución de dividendos a los titulares de tokens | Ajustar el multiplicador para reflejar el reparto de dividendos |
| Cambios en la denominación | Conversión entre diferentes unidades de medida de un mismo activo | Cambia el multiplicador para que refleje la nueva denominación |
| Reajuste de los tokens | Tokens cuyo suministro total aumenta o disminuye en función de factores externos (como las stablecoins algorítmicas) | Ajustar periódicamente el multiplicador para reflejar los cambios en la oferta |
Repercusiones en los proyectos en curso
La implementación de la extensión «Scaled UI Amount» requiere un análisis minucioso de la arquitectura y la funcionalidad de tu aplicación. Aunque tu caso concreto pueda ser único, a continuación se indican algunas consideraciones comunes:
- Consideraciones sobre el diseño de UI/UX: Las aplicaciones deben actualizarse para mostrar a los usuarios el importe de la interfaz de usuario escalado, al tiempo que se gestionan los importes sin procesar en el backend (se han añadido varios métodos útiles a la biblioteca de programas de tokens SPL, por ejemplo,
amountToUiAmount,uiAmountToAmount)). Esto requiere un diseño cuidadoso de la experiencia de usuario (UX) para evitar la confusión de los usuarios, especialmente durante los cambios de multiplicador. Se recomienda añadir información emergente o indicadores cuando los tokens utilicen esta extensión. - Gestión de datos históricos: Los servicios deberían considerar la posibilidad de indexar tanto los importes brutos como los de la interfaz de usuario, así como los valores históricos de los multiplicadores, para mostrar correctamente el historial de transacciones y los gráficos de precios. Esto es especialmente importante en el caso de los tokens cuyos multiplicadores cambian con frecuencia.
- Procesamiento de transacciones: Al procesar los datos introducidos por los usuarios para transferencias o intercambios, las aplicaciones deben convertir los importes de la interfaz de usuario (lo que introducen los usuarios) en importes sin procesar (los que utilizan los procesadores en cadena) antes de enviar las transacciones. Los problemas de redondeo deben resolverse redondeando siempre a la baja para evitar fallos en las transacciones.
- Integración de fuentes de precios: Los servicios de precios deben proporcionar fuentes de precios tanto escaladas como no escaladas para adaptarse a las diferentes necesidades de los clientes. En el caso de los proveedores de datos de mercado, los cálculos de la oferta total y la capitalización bursátil deben tener en cuenta el multiplicador actual para mostrar información precisa.
Configuración del proyecto
¿Prefieres ir directamente al código? ¡Echa un vistazo a nuestro repositorio de ejemplos en GitHub para ver el código completo de esta guía!
- Solana Web3.js (versión anterior)
- Kit Solana
Antes de empezar, recapitulemos lo que vamos a crear. Vamos a crear un script de demostración sencillo que:
- Crea un token Token-2022 con la extensión «Scaled UI Amount»
- Acredeita a un titular
- Transfiere tokens entre cuentas
- Actualiza el multiplicador de la interfaz de usuario
- Genera y transfiere tokens adicionales para observar los efectos del multiplicador actualizado
- Registra el estado en cada paso y muestra una tabla resumen en la que se refleja la relación entre el multiplicador, el importe bruto y el importe de la prestación por desempleo.
Empecemos por crear la estructura de nuestro proyecto:
mkdir solana-scaled-token-demo && cd solana-scaled-token-demo
Inicializar un nuevo proyecto de Node.js:
npm init -y
Instala las dependencias necesarias:
npm install @solana/web3.js@1 @solana/spl-token
Y otras dependencias de desarrollo:
npm install --save-dev typescript ts-node @types/node
Crear un tsconfig.json archivo:
{
"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"]
}
Actualiza tu package.json scripts:
"scripts": {
"start": "ts-node token-creator.ts",
"build": "tsc"
}
Crea un directorio para almacenar los pares de claves:
mkdir -p keys
Crea nuevos pares de claves de Solana para el pagador, la autoridad de acuñación, el titular del token y la acuñación:
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
Esto debería crear cuatro archivos de pares de claves en el claves directorio.
Antes de empezar, recapitulemos lo que vamos a crear. Vamos a crear un script de demostración sencillo que:
- Crea un token Token-2022 con la extensión «Scaled UI Amount»
- Acredeita a un titular
- Transfiere tokens entre cuentas
- Actualiza el multiplicador de la interfaz de usuario
- Genera y transfiere tokens adicionales para observar los efectos del multiplicador actualizado
- Registra el estado en cada paso y muestra una tabla resumen en la que se refleja la relación entre el multiplicador, el importe bruto y el importe de la prestación por desempleo.
Empecemos por crear la estructura de nuestro proyecto:
mkdir solana-scaled-token-demo && cd solana-scaled-token-demo
Inicializar un nuevo proyecto de Node.js:
npm init -y
Instala las dependencias necesarias:
npm i @solana/kit @solana-program/token-2022 @solana-program/system
Y otras dependencias de desarrollo:
npm install --save-dev typescript ts-node @types/node
Crear un tsconfig.json archivo:
{
"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"]
}
Actualiza tu package.json scripts:
"scripts": {
"start": "ts-node token-creator.ts",
"build": "tsc"
}
Crea un directorio para almacenar los pares de claves:
mkdir -p keys
Crea nuevos pares de claves de Solana para el pagador, la autoridad de acuñación, el titular del token y la acuñación:
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
Esto debería crear cuatro archivos de pares de claves en el claves directorio.
Aplicación
- Solana Web3.js (versión anterior)
- Kit Solana
Vamos a crear nuestro token-creator.ts archivo y compilarlo paso a paso:
Importaciones y configuración
Empieza por las importaciones y la configuración necesarias:
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')
};
Con esto se establece nuestra configuración básica, que incluye:
- Parámetros del token (decimales, multiplicador, nombre, símbolo)
- Importe a acuñar y transferir
- Datos de conexión (para esta demostración utilizaremos nuestro validador de prueba local de Solana)
- Directorio para almacenar pares de claves
Funciones de registro de estado
A continuación, vamos a añadir un sistema de registro de estado para realizar un seguimiento de los cambios a lo largo de nuestra demostración:
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 = Date.now();
// Solana times are in whole seconds (see Clock::get() etc)
if (Number(extensionData.newMultiplierEffectiveTimestamp) * 1000 < 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
})));
}
Analicemos aquí las funciones clave:
getTokenMultiplier: Obtiene el multiplicador actual de una moneda determinada. Utiliza ladescomprimirMintygetScaledUiAmountConfigfunciones auxiliares de @solana/spl-token para obtener los datos de prórroga de la casa de la moneda (multiplicador y marca de tiempo efectiva).getTokenBalance: Obtiene los saldos brutos y de la interfaz de usuario de una cuenta de token determinada. Utiliza lagetTokenAccountBalancemétodo de la biblioteca web3.js de Solana.logStatus: Registra el estado de cada paso, incluyendo el multiplicador actual, el saldo bruto y el saldo de la interfaz de usuario. Además, almacena esta información en eldemoLogsmatriz para su posterior visualización.imprimirTablaResumen: Muestra una tabla resumen de todos los pasos registrados, en la que se muestra la relación entre el multiplicador, el saldo bruto y el saldo de la interfaz de usuario.
Funciones de utilidad
Ahora, añadamos algunas funciones de utilidad para gestionar las confirmaciones de transacciones y la gestión de pares de claves:
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;
}
}
Aquí vamos a crear dos funciones de utilidad:
waitForTransaction- Esperar a que se confirmen las transacciones (con gestión de tiempos de espera)getOrCreateKeypair- Obtener o crear pares de claves y guardarlos en archivos para poder reutilizarlos
Funcionalidad básica
A continuación, vamos a añadir las funciones principales para nuestra demostración. Añade el configuración Añade esta función a tu archivo para gestionar la transferencia directa de SOL a la cuenta del pagador:
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.');
}
}
A continuación, vamos a crear el createScaledToken Función para crear un nuevo token con la extensión «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 extension
const extensions = [ExtensionType.ScaledUiAmountConfig];
const mintLen = getMintLen(extensions);
// Calculate lamports needed for rent-exemption
const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen);
// Create a new token with Token-2022 program & Scaled UI Amount extension
const transaction = new Transaction().add(
// Create account for the mint
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mint.publicKey,
space: mintLen,
lamports: mintLamports,
programId: TOKEN_2022_PROGRAM_ID,
}),
// Initialize Scaled UI Amount extension
createInitializeScaledUiAmountConfigInstruction(
mint.publicKey,
mintAuthority.publicKey,
CONFIG.INITIAL_UI_AMOUNT_MULTIPLIER,
TOKEN_2022_PROGRAM_ID
),
// Initialize the mint
createInitializeMintInstruction(
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;
}
}
Esta función crea y envía una transacción con tres instrucciones clave:
createAccount: Crea una nueva cuenta para la casa de moneda con el espacio y los lamports necesarios en función de las extensiones (en este caso, solo la extensión «Scaled UI Amount»).createInitializeScaledUiAmountConfigInstruction: Inicializa la extensión «Scaled UI Amount» para la casa de la monedacreateInitializeMintInstruction: Inicializa la casa de moneda con los decimales y la autoridad especificados
Puedes encontrar más información sobre estos pasos en la documentación del programa Solana.
Ahora, vamos a añadir un updateScaledUiAmountMultiplier Función para actualizar el multiplicador de la cantidad en la interfaz de usuario:
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;
}
}
Aquí simplemente estamos utilizando el multiplicador de actualización función de la @solana/spl-token biblioteca para actualizar el multiplicador de la casa de la moneda (ten en cuenta que estamos pasando 0 (como marca de tiempo de inicio de nuestro nuevo multiplicador, lo que significa que entrará en vigor de inmediato) y espera a que se confirme la transacción antes de continuar.
A continuación, vamos a añadir un elemento reutilizable transferTokens Función para gestionar la transferencia de tokens entre cuentas. La utilizaremos para mostrar cómo se realiza la transferencia de tokens antes y después de la actualización del multiplicador:
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;
}
}
Esta función se encarga de:
- Configuración de un nuevo monedero de destino y una nueva cuenta de tokens para la transferencia (se trata simplemente de una cuenta temporal para la demostración)
- Creación de una operación de transferencia con el
createTransferInstructionfunción - Enviar la transacción y esperar la confirmación
Función principal de demostración
Ahora, vamos a crear el archivo principal demonstrateScaledToken función paso a paso. En primer lugar, añade una función provisional con tareas pendientes para cada paso:
async function demonstrateScaledToken(): Promise<void> {
try {
console.log(`=== SCALED TOKEN DEMONSTRATION ===`);
console.log(`\n=== Setup ===`);
// TODO Add setup
console.log(`\n=== Step 1: Creating Token Mint ===`);
// TODO Create Token Mint with UI Amount Scaled extension
console.log(`\n=== Step 2: Creating Holder's Token Account ===`);
// TODO Create Holder's Token Account
console.log(`\n=== Step 3: Minting Initial Tokens ===`);
// TODO Mint Initial Tokens to Holder
console.log(`\n=== Step 4: Transferring Tokens ===`);
// TODO Transfer Tokens to another account
console.log(`\n=== Step 5: Updating Scale Multiplier ===`);
// TODO Update Scale Multiplier
console.log(`\n=== Step 6: Minting Additional Tokens ===`);
// TODO Mint Additional Tokens to Holder
console.log(`\n=== Step 7: Transferring Additional Tokens ===`);
// TODO Transfer Tokens to another account
} catch (error) {
console.error('Error in scaled token demonstration:', error);
}
}
Vamos a rellenar cada apartado:
Configuración
Sustituir // TODO Add setup con:
const conexión = new Conexión(CONFIG.CONNECTION_URL, 'confirmed');
const pago = await getOrCreateKeypair(ruta.unir(CONFIG.KEYPAIR_DIR, 'payer.json'), 'payer');
const autoridad de emisión = await getOrCreateKeypair(ruta.unir(CONFIG.KEYPAIR_DIR, 'mint-authority.json'), 'autoridad de emisión');
const casa de la moneda = await getOrCreateKeypair(ruta.unir(CONFIG.KEYPAIR_DIR, 'mint.json'), 'mint');
const contenedor = await getOrCreateKeypair(ruta.unir(CONFIG.KEYPAIR_DIR, 'holder.json'), 'titular del token');
await configuración(conexión, pagador);
En este paso, hacemos lo siguiente:
- Establecer una conexión con la red Solana
- Cargar o crear pares de claves para todas las cuentas que vayamos a necesitar
- Ejecuta nuestro
configuraciónfunción para transferir fondos desde la cuenta del pagador
Paso 1: Creación de la emisión de tokens
Sustituir // TODO Create Token Mint with UI Amount Scaled extension con:
esperar createScaledToken(conexión, pagador, casa de la moneda, autoridad de acuñación);
esperar logStatus(
conexión,
"1. Tras la inicialización del token",
crea.clavePública,
null,
«Token creado con la extensión Scaled UI Amount»
);
Esta sección:
- Crea un nuevo token Token-2022 con la extensión «Scaled UI Amount»
- Registra el estado inicial (ten en cuenta que todavía no hay ninguna cuenta de token)
Paso 2: Creación de la cuenta de tokens del titular
Sustituir // TODO Create Holder's Token Account con:
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"
);
Aquí estamos creando una cuenta de token asociada para el titular utilizando el getOrCreateAssociatedTokenAccount función. Esto nos permitirá acuñar tokens directamente en la cuenta del titular. Ten en cuenta que estamos utilizando el ID de programa Token-2022.
Paso 3: Emisión de los tokens iniciales
Sustituir // TODO Mint Initial Tokens to Holder con:
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`
);
En este paso, hacemos lo siguiente:
- Calcula la cantidad bruta que se va a acuñar (incluidos los decimales)
- Acreditar tokens en la cuenta del titular
- Espera a que se confirme la transacción
- Anota el estado después
Paso 4: Transferencia de tokens
Sustituir // TODO Transfer Tokens to another account con:
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`
);
Esta sección:
- Transfiere tokens del titular a una nueva cuenta (recuerda que nuestra instrucción de transferencia utiliza la constante CONFIG.TRANSFER_AMOUNT para determinar cuántos tokens se deben transferir; utilizaremos la misma cantidad bruta para ambas transferencias y compararemos cómo varían las cantidades que aparecen en la interfaz de usuario).
- Registra el estado tras la transferencia
Paso 5: Actualización del multiplicador de escala
Sustituir // TODO Update Scale Multiplier con:
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`
);
Aquí nosotros:
- Actualiza el multiplicador de la cantidad en la interfaz de usuario utilizando el
updateScaledUiAmountMultiplierfunción - Registra el estado para ver cómo varía el importe que aparece en la interfaz de usuario
Paso 6: Emisión de tokens adicionales
Sustituir // TODO Mint Additional Tokens to Holder con:
const additionalMintSignature = await mintTo(
connection,
payer,
mint.publicKey,
holderTokenAccount.address,
mintAuthority,
initialMintAmount, // Same raw amount as before
[],
{},
TOKEN_2022_PROGRAM_ID
);
await waitForTransaction(connection, additionalMintSignature, 30000, "additional mint");
await logStatus(
connection,
"6. After Mint #2",
mint.publicKey,
holderTokenAccount.address,
`Minted additional ${CONFIG.MINT_AMOUNT} tokens with current multiplier`
);
Esta sección:
- Genera la misma cantidad de materia prima que antes
- Registra el estado para comprobar cómo varía el valor de la interfaz de usuario con el nuevo multiplicador
Paso 7: Transferencia de tokens adicionales
Sustituir // TODO Transfer Tokens to another account con:
await transferTokens(
connection,
payer,
holderTokenAccount.address,
holder,
mint.publicKey
);
await logStatus(
connection,
"7. After Transfer #2",
mint.publicKey,
holderTokenAccount.address,
`Transferred ${CONFIG.TRANSFER_AMOUNT} tokens to another account (with multiplier)`
);
printSummaryTable();
Por último, nosotros:
- Vuelve a transferir tokens utilizando la misma cantidad bruta
- Registra el estado para que podamos ver cómo varía el importe de la interfaz de usuario con respecto a la transferencia anterior.
- Imprimir una tabla resumen con todos los pasos
Incorporación de la llamada a la función principal
Al final de tu archivo, añade:
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));
}
Vamos a crear nuestro token-creator.ts archivo y compilarlo paso a paso:
Importaciones y configuración
Empieza por las importaciones y la configuración necesarias:
import { getCreateAccountInstruction } from '@solana-program/system';
import {
extension,
Extension,
ExtensionArgs,
findAssociatedTokenPda,
getCreateAssociatedTokenIdempotentInstructionAsync,
getInitializeMintInstruction,
getMintSize,
getMintToInstruction,
getPostInitializeInstructionsForMintExtensions,
getPreInitializeInstructionsForMintExtensions,
getUpdateMultiplierScaledUiMintInstruction,
getTransferInstruction,
fetchMint,
TOKEN_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";
import {
airdropFactory,
createSolanaRpc,
createSolanaRpcSubscriptions,
generateKeyPairSigner,
lamports,
Rpc,
sendAndConfirmTransactionFactory,
pipe,
createTransactionMessage,
setTransactionMessageLifetimeUsingBlockhash,
setTransactionMessageFeePayerSigner,
appendTransactionMessageInstructions,
createKeyPairSignerFromBytes,
KeyPairSigner,
SolanaRpcApi,
SolanaRpcSubscriptionsApi,
RpcSubscriptions,
Signature,
Address,
TransactionSigner,
IInstruction,
Commitment,
signTransactionMessageWithSigners,
CompilableTransactionMessage,
TransactionMessageWithBlockhashLifetime,
getSignatureFromTransaction,
} from "@solana/kit"
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>;
}
Con esto se establece nuestra configuración básica, que incluye:
- Parámetros del token (decimales, multiplicador, nombre, símbolo)
- Importe a acuñar y transferir
- Datos de conexión (para esta demostración utilizaremos nuestro validador de prueba local de Solana)
- Directorio para almacenar pares de claves
- Interfaz de cliente para gestionar RPC y suscripciones
Funciones de registro de estado
A continuación, vamos a añadir un sistema de registro de estado para realizar un seguimiento de los cambios a lo largo de nuestra demostración:
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 = Date.now();
// Solana times are in whole seconds (see Clock::get() etc)
if (Number(extensionData.newMultiplierEffectiveTimestamp) * 1000 < 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
})));
}
Analicemos aquí las funciones clave:
getTokenMultiplier: Obtiene el multiplicador actual de una moneda determinada. Utiliza lafetchMintde @solana-program/token-2022 para obtener y analizar los datos de la extensión de la casa de la moneda (multiplicador y marca de tiempo efectiva).- Utilizamos el
getTokenAccountBalanceMétodo para obtener los saldos brutos y de la interfaz de usuario de una cuenta de token determinada. logStatus: Registra el estado de cada paso, incluyendo el multiplicador actual, el saldo bruto y el saldo de la interfaz de usuario. Además, almacena esta información en eldemoLogsmatriz para su posterior visualización.imprimirTablaResumen: Muestra una tabla resumen de todos los pasos registrados, en la que se muestra la relación entre el multiplicador, el saldo bruto y el saldo de la interfaz de usuario.
Funciones de utilidad
Ahora, añadamos algunas funciones de utilidad para gestionar las confirmaciones de transacciones y la gestión de pares de claves:
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;
};
Aquí vamos a crear algunas funciones de utilidad:
getOrCreateKeypairSigner- Obtener o crear pares de claves y guardarlos en archivos para poder reutilizarloscreateDefaultTransaction- Crear una transacción predeterminada con el último hash de bloque y el pagador de la comisiónsignAndSendTransaction- Firmar y enviar la transacción, a la espera de la confirmaciónenviarYConfirmarInstrucciones- Enviar y confirmar un conjunto de instrucciones, devolviendo la firma de la transacción
Funcionalidad básica
A continuación, vamos a añadir las funciones principales para nuestra demostración. Añade el configuración Añade esta función a tu archivo para gestionar la transferencia directa de SOL a la cuenta del pagador:
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');
}
}
A continuación, vamos a crear el createScaledToken Función para crear un nuevo token con la extensión «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;
};
Esta función crea y envía una transacción con tres instrucciones clave:
createAccount: Crea una nueva cuenta para la casa de moneda con el espacio y los lamports necesarios en función de las extensiones (en este caso, solo la extensión «Scaled UI Amount»).getPreInitializeInstructionsForMintExtensions: Inicializa la extensión «Scaled UI Amount» para la casa de la monedagetPostInitializeInstructionsForMintExtensions: Inicializa la casa de moneda con los decimales y la autoridad especificados
Puedes encontrar más información sobre estos pasos en la documentación del programa Solana.
A continuación, vamos a añadir algunas funciones que nos ayudarán con las operaciones básicas con tokens de SPL. Las utilizaremos para acuñar tokens y transferirlos entre cuentas. Añade las siguientes funciones a tu archivo:
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;
}
}
Estas funciones se encargan de:
createAta: Crear una cuenta de tokens asociada para una emisión y un propietario determinados (la necesitaremos para acuñar y transferir tokens)transferTokens: Transferir tokens de una cuenta a otramintTokens: Acuñar tokens en una cuenta de tokens determinada
Ahora, vamos a añadir un multiplicador de actualización Función para actualizar el multiplicador de la cantidad en la interfaz de usuario:
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;
}
}
Aquí simplemente estamos utilizando el getUpdateMultiplierScaledUiMintInstruction función de la @solana-program/token-2022 biblioteca para actualizar el multiplicador de la casa de la moneda (ten en cuenta que estamos pasando 0 (como marca de tiempo de inicio de nuestro nuevo multiplicador, lo que significa que entrará en vigor de inmediato) y espera a que se confirme la transacción antes de continuar.
Función principal de demostración
Ahora, vamos a crear el archivo principal demonstrateScaledToken función paso a paso. En primer lugar, añade una función provisional con tareas pendientes para cada paso:
async function demonstrateScaledToken(): Promise<void> {
try {
console.log(`=== SCALED TOKEN DEMONSTRATION ===`);
console.log(`\n=== Setup ===`);
// TODO Add setup
console.log(`\n=== Step 1: Creating Token Mint ===`);
// TODO Create Token Mint with UI Amount Scaled extension
console.log(`\n=== Step 2: Creating Holder's Token Account ===`);
// TODO Create Holder's Token Account
console.log(`\n=== Step 3: Minting Initial Tokens ===`);
// TODO Mint Initial Tokens to Holder
console.log(`\n=== Step 4: Transferring Tokens ===`);
// TODO Transfer Tokens to another account
console.log(`\n=== Step 5: Updating Scale Multiplier ===`);
// TODO Update Scale Multiplier
console.log(`\n=== Step 6: Minting Additional Tokens ===`);
// TODO Mint Additional Tokens to Holder
console.log(`\n=== Step 7: Transferring Additional Tokens ===`);
// TODO Transfer Tokens to another account
} catch (error) {
console.error('Error in scaled token demonstration:', error);
}
}
Vamos a rellenar cada apartado:
Configuración
Sustituir // TODO Add setup con:
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);
En este paso, hacemos lo siguiente:
- Configurar nuestro cliente para que se conecte a la red de Solana
- Cargar o crear pares de claves para todas las cuentas que vayamos a necesitar
- Ejecuta nuestro
configuraciónfunción para transferir fondos desde la cuenta del pagador
Paso 1: Creación de la emisión de tokens
Sustituir // TODO Create Token Mint with UI Amount Scaled extension con:
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"
);
Esta sección:
- Crea un nuevo token Token-2022 con la extensión «Scaled UI Amount»
- Registra el estado inicial (ten en cuenta que todavía no hay ninguna cuenta de token)
Paso 2: Creación de la cuenta de tokens del titular
Sustituir // TODO Create Holder's Token Account con:
const cuentaDelTokenDelTitular = await createAta(client, pagador, casa de la moneda, titular);
esperar logStatus(
cliente,
"2. Ata creado",
mint.dirección,
cuenta del titular del token,
«Cuenta de tokens del titular creada»
);
Aquí estamos creando una cuenta de token asociada para el titular utilizando nuestro createAta función. Esto nos permitirá emitir tokens directamente en la cuenta del titular.
Paso 3: Emisión de los tokens iniciales
Sustituir // TODO Mint Initial Tokens to Holder con:
esperar mintTokens(cliente, pagador, autoridad de emisión, ceca, cuenta del titular del token, BigInt(CONFIG.MINT_AMOUNT));
await logStatus(
cliente,
"3. Después de Mint n.º 1",
emisión.dirección,
cuenta del titular del token,
«Tokens iniciales acuñados»
);
En este paso, emitimos tokens en la cuenta del titular utilizando nuestro mintTokens función. Además, registramos el estado posteriormente.
Paso 4: Transferencia de tokens
Sustituir // TODO Transfer Tokens to another account con:
esperar transferTokens(cliente, pagador, cuenta del titular del token, titular, mint, BigInt(CONFIG.TRANSFER_AMOUNT));
await logStatus(
cliente,
"4. Tras la transferencia",
casa de la moneda.dirección,
cuenta del titular del token,
«Tokens transferidos»
);
Este paso:
- Transfiere tokens del titular a una nueva cuenta (utilizamos la constante CONFIG.TRANSFER_AMOUNT para determinar cuántos tokens se deben transferir; utilizaremos la misma cantidad bruta para ambas transferencias y compararemos cómo varían las cantidades que aparecen en la interfaz de usuario).
- Registra el estado tras la transferencia
Paso 5: Actualización del multiplicador de escala
Sustituir // TODO Update Scale Multiplier con:
await updateMultiplier(cliente, pagador, casa de la moneda, autoridad de acuñación, CONFIG.MODIFIED_UI_AMOUNT_MULTIPLIER);
await logStatus(
cliente,
"5. Tras la actualización del multiplicador",
acuñar.dirección,
cuenta del titular del token,
«Multiplicador actualizado»
);
Aquí nosotros:
- Actualiza el multiplicador de la cantidad en la interfaz de usuario utilizando el
multiplicador de actualizaciónfunción - Registra el estado para ver cómo varía el importe que aparece en la interfaz de usuario
Paso 6: Emisión de tokens adicionales
Sustituir // TODO Mint Additional Tokens to Holder con:
esperar mintTokens(cliente, pagador, autoridad de emisión, ceca, cuenta del titular del token, BigInt(CONFIG.MINT_AMOUNT));
await logStatus(
cliente,
"6. Después de Mint n.º 2",
emisión.dirección,
cuenta del titular del token,
«Tokens adicionales acuñados»
);
Esta sección:
- Genera la misma cantidad de materia prima que antes
- Registra el estado para comprobar cómo varía el valor de la interfaz de usuario con el nuevo multiplicador
Paso 7: Transferencia de tokens adicionales
Sustituir // TODO Transfer Tokens to another account con:
esperar transferTokens(cliente, pagador, cuenta del titular del token, titular, mint, BigInt(CONFIG.TRANSFER_AMOUNT));
await logStatus(
cliente,
"7. Tras la transferencia n.º 2",
emisión.dirección,
cuenta del titular del token,
«Tokens adicionales transferidos»
);
imprimirTablaResumen();
Por último, nosotros:
- Vuelve a transferir tokens utilizando la misma cantidad bruta
- Registra el estado para que podamos ver cómo varía el importe de la interfaz de usuario con respecto a la transferencia anterior.
- Imprimir una tabla resumen con todos los pasos
Incorporación de la llamada a la función principal
Al final de tu archivo, añade:
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));
}
Ejecución de la demostración
Para ejecutar la demostración:
- Abre una nueva ventana de terminal e inicia un validador de prueba local:
solana-test-validator -r
- A continuación, en la terminal de tu proyecto principal, ejecuta el script:
npm start
Comprender el resultado
Esto es lo que deberías ver en el resultado:
=== RESUMEN DE LA DEMOSTRACIÓN ===
┌─────────┬───────────────────────────┬──────────────┬────────────┬─────────────┬────────────┐
│ (índice) │ Paso │ Marca de tiempo │ Multiplicador │ Saldo bruto │ Saldo de la interfaz de usuario │
├─────────┼───────────────────────────┼──────────────┼────────────┼─────────────┼────────────┤
│ 0 │ «Configuración inicial» │ «3:02:16 p. m.» │ 1 │ «n/a» │ «n/a» │
│ 1 │ «Tras la emisión inicial» │ «15:02:17» │ 1 │ «100000000» │ «100» │
│ 2 │ «Tras la transferencia n.º 1» │ «3:02:18 p. m.» │ 1 │ «90000000» │ «90» │
│ 3 │ «Tras la actualización del multiplicador» │ «3:02:19 p. m.» │ 2 │ «90000000» │ «180» │
│ 4 │ «Tras la segunda emisión» │ «3:02:20 p. m.» │ 2 │ «190000000» │ «380» │
│ 5 │ «Tras la transferencia n.º 2» │ «3:02:21 p. m.» │ 2 │ «180000000» │ «360» │
└─────────┴───────────────────────────┴──────────────┴────────────┴─────────────┴────────────┘
=== DEMOSTRACIÓN FINALIZADA ===
Analicemos con más detalle los puntos clave de la demostración:
- Tras la emisión inicial, el importe en la interfaz de usuario = importe bruto / 10^decimales, ya que nuestro multiplicador es 1,0.
- Tras la transferencia n.º 1, las cantidades «en bruto» y «UI» disminuyen proporcionalmente
- Tras la actualización del multiplicador: el importe bruto no ha variado, pero el importe que aparece en la interfaz de usuario se ha duplicado (por ejemplo, como si se tratara de una división de acciones 2:1).
- Tras la segunda acuñación: la cantidad bruta aumenta como antes, pero la cantidad que aparece en la interfaz de usuario se incrementa en dicha cantidad multiplicada por el nuevo multiplicador (+ 100 * 2,0).
- Tras la transferencia n.º 2, la cantidad inicial disminuye en la misma cantidad que antes, pero la cantidad de la interfaz de usuario ahora se reduce en esa cantidad multiplicada por el nuevo multiplicador (-10 * 2,0).
¡Es hora de crecer!
¡Enhorabuena! Has implementado con éxito la extensión «Scaled UI Amount» en un programa Token-2022 de Solana. Ahora dispones de una demostración funcional que muestra cómo crear un token, acuñar y transferir tokens, y actualizar el multiplicador de la cantidad en la interfaz de usuario.
La extensión «Scaled UI Amount» ofrece a los emisores de tokens un potente mecanismo para controlar cómo se muestran los saldos a los usuarios sin modificar los importes brutos subyacentes. Esto abre nuevas posibilidades para crear modelos económicos innovadores para los tokens en Solana.
Puntos clave:
- La extensión «Scaled UI Amount» de Token-2022 permite aplicaciones avanzadas de la economía de tokens, como divisiones de acciones, dividendos, rendimientos y rebasing.
- Al utilizar multiplicadores, los importes brutos no varían, mientras que los importes de la prestación por desempleo se ajustan en función del multiplicador.
- Las aplicaciones deben gestionar adecuadamente la conversión entre los importes sin procesar y los de la interfaz de usuario.
- Los proveedores de datos históricos deberían plantearse indexar las variaciones del multiplicador para ofrecer datos históricos precisos.
Recursos adicionales
- Ejemplo de código completo
- Descripción general de la extensión «Scaled UI Amount»
- Documentación y ejemplo de la interfaz de línea de comandos
- Resumen general de Solana Token 2022
- Código fuente de la extensión «Scaled UI»
Si tienes alguna duda o necesitas ayuda, únete al servidor de Discord de Quicknode o síguenos en Twitter.
¡Nos encanta recibir comentarios!
Si tienes algún comentario o sugerencia sobre nuevos temas, no dudes en hacérnoslo saber. Nos encantaría saber tu opinión.
