Add Wallet Support for Compressed Tokens
Guide to add Compressed Token Support to Your Wallet Application
Best Practices
Clear UI Indicators — Provide clear visual distinctions between compressed and uncompressed SPL tokens
Transaction History — Provide detailed transaction histories for compressed tokens
Decompression and Compression — Provide a clear path for users to convert between compressed and uncompressed tokens when needed
Leading Solana Wallets like Phantom and Backpack already support compressed tokens.
Integration Steps
Display Compressed Token Balances
import { Rpc, createRpc } from '@lightprotocol/stateless.js';
import { PublicKey } from '@solana/web3.js';
const connection: Rpc = createRpc();
const publicKey = new PublicKey('FWwR2s4TwpWN3nkCzVfhuPrpePG8kNzBXAxEbNsaDFNu');
(async () => {
const balances = await connection.getCompressedTokenBalancesByOwnerV2(publicKey);
if (balances.value.items.length === 0) {
console.log("No compressed token balances found");
return;
}
for (const item of balances.value.items) {
const balanceValue = typeof item.balance === 'string'
? parseInt(item.balance, 16)
: item.balance;
const mintInfo = await connection.getAccountInfo(new PublicKey(item.mint));
const decimals = mintInfo.data[44];
const formattedBalance = balanceValue / Math.pow(10, decimals);
console.log(`Mint: ${item.mint}`);
console.log(`Balance: ${formattedBalance} tokens\n`);
}
})();Get Transaction History
import { Rpc, createRpc } from '@lightprotocol/stateless.js';
import { PublicKey } from '@solana/web3.js';
const connection: Rpc = createRpc();
const publicKey = new PublicKey('FWwR2s4TwpWN3nkCzVfhuPrpePG8kNzBXAxEbNsaDFNu');
(async () => {
const signatures = await connection.getCompressionSignaturesForTokenOwner(publicKey);
if (signatures.items.length > 0) {
console.log(`Signatures:`);
signatures.items.forEach((sig, index) => {
console.log(`${index + 1}. ${sig.signature}`);
console.log(` Slot: ${sig.slot}`);
console.log(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`);
});
} else {
console.log("No transactions found");
}
})();Send Compressed Tokens
Display Compressed Token Balances
This example fetches and displays all compressed token balances for a wallet address.
import { Rpc, createRpc } from '@lightprotocol/stateless.js';
import { Keypair, PublicKey } from '@solana/web3.js';
import * as fs from 'fs';
import * as os from 'os';
const connection: Rpc = createRpc();
const walletPath = `${os.homedir()}/.config/solana/id.json`;
const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
const payer = Keypair.fromSecretKey(Buffer.from(secretKey));
(async () => {
const balances = await connection.getCompressedTokenBalancesByOwnerV2(payer.publicKey);
if (balances.value.items.length === 0) {
console.log("No compressed token balances found");
return;
}
for (const item of balances.value.items) {
const balanceValue = typeof item.balance === 'string'
? parseInt(item.balance, 16)
: item.balance;
const mintInfo = await connection.getAccountInfo(new PublicKey(item.mint));
const decimals = mintInfo.data[44];
const formattedBalance = balanceValue / Math.pow(10, decimals);
console.log(`Mint: ${item.mint}`);
console.log(`Balance: ${formattedBalance} tokens\n`);
}
})();Get Transaction History
This example retrieves compression transaction signatures to display transaction history of the wallet.
import { Rpc, createRpc } from '@lightprotocol/stateless.js';
import { Keypair } from '@solana/web3.js';
import * as fs from 'fs';
import * as os from 'os';
const connection: Rpc = createRpc();
const walletPath = `${os.homedir()}/.config/solana/id.json`;
const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
const payer = Keypair.fromSecretKey(Buffer.from(secretKey));
(async () => {
const signatures = await connection.getCompressionSignaturesForTokenOwner(payer.publicKey);
if (signatures.items.length > 0) {
console.log(`Signatures:`);
signatures.items.forEach((sig, index) => {
console.log(`${index + 1}. ${sig.signature}`);
console.log(` Slot: ${sig.slot}`);
console.log(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`);
});
} else {
console.log("No transactions found");
}
})();Send Compressed Tokens
First, set up a test mint to and mint 10 compressed tokens to your filesystem wallet.
Make sure you add your Mint address to send-tokens.ts.
// Compressed Token Transfer - Local
// 1. Load wallet and fetch compressed token accounts with getCompressedTokenAccountsByOwner()
// 2. Select accounts for transfer using selectMinCompressedTokenAccountsForTransfer()
// and get validity proof with getValidityProof()
// 3. Create transfer instruction with CompressedTokenProgram.transfer()
// and submit transaction with sendAndConfirmTx()
// 4. Verify balances via getCompressedTokenAccountsByOwner()
import {
Rpc,
createRpc,
bn,
dedupeSigner,
sendAndConfirmTx,
buildAndSignTx,
} from "@lightprotocol/stateless.js";
import {
CompressedTokenProgram,
selectMinCompressedTokenAccountsForTransfer,
} from "@lightprotocol/compressed-token";
import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js";
import * as fs from 'fs';
import * as os from 'os';
// Step 1: Setup RPC connection and define transfer parameters
const connection: Rpc = createRpc(); // defaults to localhost:8899
const mint = new PublicKey("MINT ADDRESS"); // Replace with mint address
// Load wallet from filesystem
const walletPath = `${os.homedir()}/.config/solana/id.json`;
const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
const payer = Keypair.fromSecretKey(Buffer.from(secretKey));
const owner = payer;
const recipient = Keypair.generate();
const amount = bn(1e8);
(async () => {
// Step 2: Fetch compressed account hashes from state trees
const compressedTokenAccounts =
await connection.getCompressedTokenAccountsByOwner(owner.publicKey, {
mint, // SPL mint with token pool for compression
});
if (compressedTokenAccounts.items.length === 0) {
console.log("No compressed token accounts found for this mint");
return;
}
// Show initial sender balance
const initialBalance = compressedTokenAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0);
console.log(`Sender balance: ${initialBalance / 1e8} compressed tokens`);
// Step 3: Select minimum compressed accounts for transfer amount
const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
compressedTokenAccounts.items,
amount
);
// Get validity proof for Merkle tree verification
const proof = await connection.getValidityProof(
inputAccounts.map((account) => account.compressedAccount.hash)
);
// Step 4: Create transfer instruction that consumes input accounts and creates new output accounts
const ix = await CompressedTokenProgram.transfer({
payer: payer.publicKey,
inputCompressedTokenAccounts: inputAccounts, // accounts to consume
toAddress: recipient.publicKey,
amount,
recentInputStateRootIndices: proof.rootIndices,
recentValidityProof: proof.compressedProof,
});
// Step 5: Build, sign, and submit transaction
const { blockhash } = await connection.getLatestBlockhash();
const additionalSigners = dedupeSigner(payer, [owner]);
const signedTx = buildAndSignTx(
[ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix],
payer,
blockhash,
additionalSigners
);
const transferTxId = await sendAndConfirmTx(connection, signedTx);
console.log(`\nTransferred ${amount.toNumber() / 1e8} compressed tokens`);
console.log(`Transaction: ${transferTxId}`);
console.log(`Recipient: ${recipient.publicKey.toString()}`);
// Step 6: Verify via getCompressedTokenAccountsByOwner
const senderCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(payer.publicKey, { mint });
const senderBalance = senderCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0);
const recipientCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(recipient.publicKey, { mint });
const recipientBalance = recipientCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0);
console.log(`\nSummary compressed token balances:`);
console.log(`Sender balance: ${senderBalance / 1e8} compressed tokens`);
console.log(`Recipient balance: ${recipientBalance / 1e8} compressed token`);
return transferTxId;
})();Advanced Integrations
Use these integrations to let users convert between regular and compressed format as needed.
Common Errors
Next Steps
Take a look at other compressed token guides.
GuidesLast updated
Was this helpful?