Skip to main content

47 min read

This guide is available in Solana Web3.js (Legacy v 1.x) and Solana Kit (v 2.x) versions. Select the appropriate tab to view the code snippets and instructions for your preferred library:

Overview

The Solana Token-2022 Program introduced powerful extensions that enhance token functionality beyond the original SPL Token program. One of these extensions is the Scaled UI Amount, which allows token issuers to define a multiplier that affects how token balances are displayed to users without changing the underlying raw amounts stored on-chain. This extension enables powerful use cases, including:


  • Stock splits and reverse stock splits
  • Yield-bearing tokens that visually accumulate interest
  • Dividends and distributions
  • Rebasing tokens that adjust the total supply

In order to build with the Scaled UI Amount extension, developers need to understand how it works and how to implement it in their applications. This guide will walk you through the process of creating a token with the Scaled UI Amount extension, minting tokens, transferring them, and updating the UI amount multiplier to see how it affects the displayed balances.

In this guide, we'll build a complete demonstration script that:


  1. Creates a Token-2022 token with the Scaled UI Amount extension
  2. Mints tokens to a holder
  3. Transfers tokens between accounts
  4. Updates the UI multiplier
  5. Shows how the UI amount changes while raw balances remain consistent
  6. Mint and transfer additional tokens to observe the effects of the updated multiplier

The end goal will be to generate a summary table that clearly shows the relationship between raw and UI amounts at each step of the demonstration:

=== 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'
└─────────┴───────────────────────────┴──────────────┴────────────┴─────────────┴────────────┘

Let's get started!

Prerequisites

Before starting this tutorial, ensure you have:


Understanding Scaled UI Amount Extension

Before diving into the implementation, let's understand what the Scaled UI Amount extension is and how it works. The Scaled UI Amount extension defines a multiplier that is applied to the raw amount of tokens to determine the UI amount displayed to users. This allows for flexible token economics without changing the underlying raw amounts.

Under the Hood

The extension also allows for future updates to the multiplier, enabling features like gradual increases or scheduled changes. Here's the structure of the Scaled UI Amount configuration:

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

(Source: Solana Token-2022 Program)

The config includes:

  • authority: Authorized public key that can set the scaling amount
  • multiplier: The current multiplier applied to the raw amount
  • new_multiplier_effective_timestamp: The timestamp at which the new multiplier becomes effective
  • new_multiplier: The new multiplier to be applied once the effective timestamp is reached

The Scaled UI Amount extension introduces two new instructions:

  • initialize: Initializes the Scaled UI Amount extension for a mint (src)
  • update_multiplier: Updates the multiplier for a mint (src)

Key Concepts

The Scaled UI Amount extension introduces several key concepts that are important to understand:

  1. Raw Amount vs. UI Amount:

    • Raw Amount is the actual number of tokens stored on-chain
    • UI Amount is what users see, calculated as Raw Amount × Current Multiplier
  2. Multiplier:

    • Set when the token is created
    • Can be updated by the token's authority
    • Can be scheduled for future updates
  3. UI Amount Changes:

    • When the multiplier changes, the UI amount changes proportionally
    • Raw amounts remain unchanged

Our demonstration will show how these concepts work in practice.

Real-World Applications

Here are some practical applications for the Scaled UI Amount extension:

Use CaseDescriptionImplementation Approach
Stock Splits & Reverse Stock SplitsDividing existing shares into multiple or fractional sharesAdjust multiplier to reflect new share count
Yield-Bearing TokensTokens that visually accumulate interest over timeGradually increase multiplier based on yield rate
Dividend DistributionDistributing dividends to token holdersAdjust multiplier to reflect dividend distribution
Denomination ChangesConverting between different units of the same assetChange multiplier to represent the new denomination
Rebasing TokensTokens that increase/decrease total supply based on external factors (like algorithmic stablecoins)Periodically adjust multiplier to reflect supply changes

Implications for Existing Projects

Implementing the Scaled UI Amount extension requires careful consideration of your application's architecture and functionality. Though your implications may be unique, here are some common considerations:


  • UI/UX Design Considerations: Applications must be updated to display the scaled UI amount to users while handling raw amounts in the backend (several helpful methods have been added to the SPL token program library (e.g., amountToUiAmount, uiAmountToAmount)). This requires careful UX design to prevent user confusion, especially during multiplier changes. Consider adding tooltips or indicators when tokens use this extension.
  • Historical Data Management: Services should consider indexing both raw and UI amounts, as well as historical multiplier values, to correctly display transaction history and price charts. This is particularly important for tokens where multipliers change frequently.
  • Transaction Processing: When processing user inputs for transfers or swaps, applications need to convert UI amounts (what users enter) to raw amounts (what the on-chain processors use) before submitting transactions. Rounding issues should be handled by preferring to round down to avoid transaction failures.
  • Price Feed Integration: Price services should provide both scaled and non-scaled price feeds to accommodate different client needs. For market data providers, total supply and market cap calculations must account for the current multiplier to display accurate information.

Project Setup

Prefer to jump straight to the code? Check out our Examples Repo on GitHub for the complete code for this guide!

Before we start, let's recap what we are going to build. We will create a simple demonstration script that:


  1. Creates a Token-2022 token with the Scaled UI Amount extension
  2. Mints tokens to a holder
  3. Transfers tokens between accounts
  4. Updates the UI multiplier
  5. Mints and transfers additional tokens to observe the effects of the updated multiplier
  6. Logs the status at each step and prints a summary table demonstrating the relationship between multiplier, raw amount, and UI amount

Let's start by creating our project structure:

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

Initialize a new Node.js project:

npm init -y

Install the required dependencies:

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

And other development dependencies:

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

Create a tsconfig.json file:

{
"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"]
}

Update your package.json scripts:

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

Create a directory for storing keypairs:

mkdir -p keys

Create new Solana keypairs for the payer, mint authority, token holder, and mint:

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

This should create four keypair files in the keys directory.

Implementation

Let's create our token-creator.ts file and build it step by step:

Imports and Configuration

Start with the necessary imports and configuration:

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')
};

This sets up our basic configuration, including:

  • Token parameters (decimals, multiplier, name, symbol)
  • Amount to mint and transfer
  • Connection details (we will be using our solana local test validator for this demo)
  • Directory for storing keypairs

Status Logging Functions

Next, let's add a status logging system to track changes throughout our demonstration:

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
})));
}

Let's break down the key functions here:

  • getTokenMultiplier: Fetches the current multiplier for a given mint. It utilizes the unpackMint and getScaledUiAmountConfig helper functions from @solana/spl-token to get the mint's extension data (multiplier and effective timestamp).
  • getTokenBalance: Fetches the raw and UI balances for a given token account. It uses the getTokenAccountBalance method from the Solana web3.js library.
  • logStatus: Logs the status of each step, including the current multiplier, raw balance, and UI balance. It also stores this information in the demoLogs array for later display.
  • printSummaryTable: Prints a summary table of all logged steps, showing the relationship between the multiplier, raw balance, and UI balance.

Utility Functions

Now, let's add some utility functions to handle transaction confirmations and keypair management:

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;
}
}

Here we are creating two utility functions:

  • waitForTransaction - Wait for transactions to confirm (with timeout handling)
  • getOrCreateKeypair - Get or create keypairs, storing them in files for reuse

Core Functionality

Next, let's add the core functions for our demonstration. Add the setup function to your file to handle airdropping SOL to the payer account:

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.');
}
}

Next, let's create the createScaledToken function to create a new token with the Scaled UI Amount extension:

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;
}
}

This function creates and sends a transaction with three key instructions:

  1. createAccount: Creates a new account for the mint with the required space and lamports based on the extensions (in this case, just the Scaled UI Amount extension)
  2. createInitializeScaledUiAmountConfigInstruction: Initializes the Scaled UI Amount extension for the mint
  3. createInitializeMintInstruction: Initializes the mint with the specified decimals and authority

More information about these steps can be found in the Solana Program Documentation

Now, let's add a updateScaledUiAmountMultiplier function to update the UI amount multiplier:

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;
}
}

Here we are simply using the updateMultiplier function from the @solana/spl-token library to update the multiplier for the mint (note that we are passing 0 as the start timestamp for our new multiplier meaning that it will be effective immediately) and wait until the transaction has confirmed before proceeding.

Next, let's add a reusable transferTokens function to handle transferring tokens between accounts. We will use this to demonstrate transferring tokens before and after the multiplier update:

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;
}
}

This function handles:

  • Setting up a new destination wallet and token account for the transfer (this is just a new throw-away account for the demo)
  • Creating a transfer transaction with the createTransferInstruction function
  • Sending the transaction and waiting for confirmation

Main Demonstration Function

Now, let's build the main demonstrateScaledToken function step by step. First, add a placeholder function with TODOs for each step:

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);
}
}

Let's fill in each section:

Setup

Replace // TODO Add setup with:

    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);

In this step, we:

  1. Create a connection to the Solana network
  2. Load or create keypairs for all the accounts we'll need
  3. Run our setup function to airdrop funds the payer account

Step 1: Creating Token Mint

Replace // TODO Create Token Mint with UI Amount Scaled extension with:

    await createScaledToken(connection, payer, mint, mintAuthority);

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

This section:

  1. Creates a new Token-2022 token with the Scaled UI Amount extension
  2. Logs the initial status (note that there's no token account yet)

Step 2: Creating Holder's Token Account

Replace // TODO Create Holder's Token Account with:

    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"
);

Here we are creating an Associated Token Account for the holder using the getOrCreateAssociatedTokenAccount function. This will allow us to mint tokens directly to the holder's account. Note that we're using the Token-2022 program ID.

Step 3: Minting Initial Tokens

Replace // TODO Mint Initial Tokens to Holder with:

    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`
);

In this step, we:

  1. Calculate the raw amount to mint (including decimals)
  2. Mint tokens to the holder's account
  3. Wait for the transaction to confirm
  4. Log the status afterward

Step 4: Transferring Tokens

Replace // TODO Transfer Tokens to another account with:

    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`
);

This section:

  1. Transfers tokens from the holder to a new account (recall that our transfer instruction utilizes the CONFIG.TRANSFER_AMOUNT constant to determine how many tokens to transfer - we will use the same raw amount for both transfers and compare how the UI amounts vary)
  2. Logs the status after the transfer

Step 5: Updating Scale Multiplier

Replace // TODO Update Scale Multiplier with:

    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`
);

Here we:

  1. Update the UI amount multiplier using the updateScaledUiAmountMultiplier function
  2. Log the status to see how the UI amount changes

Step 6: Minting Additional Tokens

Replace // TODO Mint Additional Tokens to Holder with:

    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`
);

This section:

  1. Mints the same raw amount as before
  2. Logs the status to see how the UI amount differs with the new multiplier

Step 7: Transferring Additional Tokens

Replace // TODO Transfer Tokens to another account with:

    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();

Finally, we:

  1. Transfer tokens again using the same raw amount
  2. Log the status so we can see how the UI amount varies from the previous transfer
  3. Print a summary table of all steps

Adding the Main Function Call

At the end of your file, add:

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

To run the demonstration:


  1. Open a new terminal and start a local test validator:
solana-test-validator -r

  1. Then in your main project terminal, run the script:
npm start

Understanding the Output

Here's what you should expect to see in 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 ===

Let's take a closer look at the key points in the demonstration:


  • After Initial Mint, the UI amount = Raw amount / 10^decimals because our multiplier is 1.0
  • After Transfer #1, the raw and UI amounts decrease proportionally
  • After Multiplier Update - Raw amount unchanged, the UI amount doubled (e.g., akin to a 2:1 stock split)
  • After Second Mint - Raw amount increased as before, but the the UI amount is incremented by the amount times the new multiplier (+ 100 * 2.0)
  • After Transfer #2, the raw amount decreases by the same amount as before, but the UI amount is now decremented by the amount times the new multiplier (-10 * 2.0)

Time to Scale!

Congratulations! You've successfully implemented the Scaled UI Amount extension in a Solana Token-2022 program. You now have a working demonstration that shows how to create a token, mint and transfer tokens, and update the UI amount multiplier.

The Scaled UI Amount extension provides a powerful mechanism for token issuers to control how balances appear to users without modifying the underlying raw amounts. This opens up new possibilities for creating innovative token economics on Solana.

Key takeaways:


  • Token-2022's Scaled UI Amount extension enables advanced applications of token economics like stock splits, dividends, yields, and rebasing
  • When using multipliers, raw amounts remain unchanged while UI amounts scale with the multiplier
  • Applications should handle the conversion between raw and UI amounts appropriately
  • Historic data providers should consider indexing the multiplier changes to provide accurate historical data

Additional Resources


For questions and support, join the QuickNode Discord or follow us on Twitter.

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Share this guide