Skip to main content

Introduction

@lightprotocol/compressed-token provides a complete JavaScript API for working with compressed SPL tokens on Light Protocol. It enables you to create mints, transfer tokens, and manage compressed token accounts with the same developer experience as standard SPL tokens, but with significantly reduced costs.

Installation

npm
npm install @lightprotocol/compressed-token @lightprotocol/stateless.js @solana/web3.js @solana/spl-token
yarn
yarn add @lightprotocol/compressed-token @lightprotocol/stateless.js @solana/web3.js @solana/spl-token
pnpm
pnpm add @lightprotocol/compressed-token @lightprotocol/stateless.js @solana/web3.js @solana/spl-token

Package Information

Version

0.23.0-beta.8

License

Apache-2.0

Quick Start

Create a Mint

Create a new compressed token mint (V1 - SPL mint with compressed accounts):
import { createMint } from '@lightprotocol/compressed-token';
import { createRpc } from '@lightprotocol/stateless.js';
import { Keypair } from '@solana/web3.js';

const rpc = createRpc();
const payer = Keypair.generate();
const mintAuthority = Keypair.generate();

// Create a new mint with 9 decimals
const { mint, transactionSignature } = await createMint(
  rpc,
  payer,
  mintAuthority.publicKey,
  9  // decimals
);

console.log('Mint created:', mint.toBase58());
console.log('Transaction:', transactionSignature);

Mint Tokens

Mint compressed tokens to a recipient:
import { mintTo } from '@lightprotocol/compressed-token';
import { bn } from '@lightprotocol/stateless.js';

const recipient = Keypair.generate().publicKey;

// Mint 1000 tokens (with 9 decimals = 1000 * 10^9)
const signature = await mintTo(
  rpc,
  payer,
  mint,
  recipient,
  mintAuthority,
  bn(1000_000_000_000)
);

console.log('Minted tokens:', signature);

Transfer Tokens

Transfer compressed tokens between accounts:
import { transfer } from '@lightprotocol/compressed-token';

const owner = Keypair.generate();
const recipient = Keypair.generate().publicKey;

// Transfer 100 tokens
const signature = await transfer(
  rpc,
  payer,
  mint,
  bn(100_000_000_000),
  owner,
  recipient
);

console.log('Transfer complete:', signature);

Query Token Accounts

Fetch compressed token accounts owned by a public key:
const tokenAccounts = await rpc.getCompressedTokenAccountsByOwner(
  owner.publicKey,
  { mint }
);

console.log('Token accounts:', tokenAccounts.items.length);
tokenAccounts.items.forEach(account => {
  console.log('Balance:', account.parsed.amount.toString());
  console.log('Mint:', account.parsed.mint.toBase58());
});

V2: Compressed Mints (Beta)

V2 features are in beta and require setting LIGHT_PROTOCOL_BETA=true environment variable.
V2 introduces fully compressed mints where the mint account itself is compressed:

Create Compressed Mint

import { createMintInterface } from '@lightprotocol/compressed-token';
import { LIGHT_TOKEN_PROGRAM_ID } from '@lightprotocol/stateless.js';

// Create a fully compressed mint (V2)
const { mint, transactionSignature } = await createMintInterface(
  rpc,
  payer,
  mintAuthority,  // Must be a Signer for compressed mints
  null,           // freeze authority
  9,              // decimals
  Keypair.generate(),  // mint keypair
  undefined,      // confirm options
  LIGHT_TOKEN_PROGRAM_ID  // Use light-token program
);

console.log('Compressed mint:', mint.toBase58());

With Token Metadata

Add metadata to your compressed mint:
import type { TokenMetadataInstructionData } from '@lightprotocol/compressed-token';

const metadata: TokenMetadataInstructionData = {
  name: 'My Token',
  symbol: 'MTK',
  uri: 'https://example.com/token-metadata.json',
};

const { mint } = await createMintInterface(
  rpc,
  payer,
  mintAuthority,
  null,
  9,
  Keypair.generate(),
  undefined,
  LIGHT_TOKEN_PROGRAM_ID,
  metadata  // Token metadata
);

Core Concepts

Token Accounts

Compressed token accounts store token balances in Merkle trees:
interface ParsedTokenAccount {
  compressedAccount: CompressedAccountWithMerkleContext;
  parsed: TokenData;
}

interface TokenData {
  mint: PublicKey;
  owner: PublicKey;
  amount: BN;
  delegate: PublicKey | null;
  state: string;  // 'initialized', 'frozen'
}

Token Pool

Compressed tokens use a pool account (omnibus account) to hold SPL tokens:
import { CompressedTokenProgram } from '@lightprotocol/compressed-token';

const poolAccount = CompressedTokenProgram.deriveTokenPoolPda(mint);
console.log('Pool account:', poolAccount.toBase58());

Account Selection

When transferring tokens, the SDK automatically selects accounts:
import { selectMinCompressedTokenAccountsForTransfer } from '@lightprotocol/compressed-token';

const tokenAccounts = await rpc.getCompressedTokenAccountsByOwner(
  owner.publicKey,
  { mint }
);

const [selectedAccounts, totalAmount] = 
  selectMinCompressedTokenAccountsForTransfer(
    tokenAccounts.items,
    transferAmount
  );

if (totalAmount.lt(transferAmount)) {
  throw new Error('Insufficient balance');
}

Token Operations

Compress SPL Tokens

Convert regular SPL tokens to compressed tokens:
import { compress } from '@lightprotocol/compressed-token';
import { getOrCreateAssociatedTokenAccount } from '@solana/spl-token';

// First, ensure you have SPL tokens
const tokenAccount = await getOrCreateAssociatedTokenAccount(
  rpc,
  payer,
  mint,
  owner.publicKey
);

// Compress the tokens
const signature = await compress(
  rpc,
  payer,
  mint,
  bn(1000_000_000),  // amount to compress
  owner,
  tokenAccount.address
);

console.log('Compressed tokens:', signature);

Decompress Tokens

Convert compressed tokens back to SPL:
import { decompress } from '@lightprotocol/compressed-token';

const signature = await decompress(
  rpc,
  payer,
  mint,
  bn(1000_000_000),  // amount to decompress
  owner,
  recipient  // Destination for SPL tokens
);

console.log('Decompressed to SPL:', signature);

Delegate Tokens

Delegate spending authority:
import { approve } from '@lightprotocol/compressed-token';

const delegate = Keypair.generate().publicKey;

const signature = await approve(
  rpc,
  payer,
  mint,
  bn(500_000_000),  // delegated amount
  owner,
  delegate
);

console.log('Delegation approved:', signature);

Transfer Delegated Tokens

Transfer tokens as a delegate:
import { transferDelegated } from '@lightprotocol/compressed-token';

const delegateSigner = Keypair.generate();

const signature = await transferDelegated(
  rpc,
  payer,
  mint,
  bn(100_000_000),
  delegateSigner,  // The delegate
  owner.publicKey, // Token owner
  recipient
);

console.log('Delegated transfer:', signature);

Revoke Delegation

Revoke a delegation:
import { revoke } from '@lightprotocol/compressed-token';

const signature = await revoke(
  rpc,
  payer,
  mint,
  owner  // Owner revoking delegation
);

console.log('Delegation revoked:', signature);

Unified Interface (V2)

The unified interface provides a consistent API for SPL, Token-2022, and compressed tokens:

Create ATA Interface

Create an associated token account that works with all token types:
import { createAtaInterface } from '@lightprotocol/compressed-token';

const ata = await createAtaInterface(
  rpc,
  payer,
  mint,
  owner.publicKey
);

console.log('ATA created:', ata.toBase58());

Transfer Interface

Transfer tokens using the unified interface:
import {
  transferInterface,
  getAssociatedTokenAddressInterface,
} from '@lightprotocol/compressed-token';

const sourceAta = getAssociatedTokenAddressInterface(
  mint,
  owner.publicKey
);

const signature = await transferInterface(
  rpc,
  payer,
  sourceAta,
  mint,
  recipient,
  owner,
  bn(100_000_000)
);

console.log('Interface transfer:', signature);

Get Account Interface

Query account supporting both hot and cold balances:
import { getAccountInterface } from '@lightprotocol/compressed-token';

const accountInfo = await getAccountInterface(
  rpc,
  ata,
  owner.publicKey,
  mint
);

console.log('Total balance:', accountInfo.amount.toString());
console.log('Hot balance:', accountInfo.hot?.amount.toString());
console.log('Cold accounts:', accountInfo.cold.length);

Load Cold Balances

Load compressed balances into the hot ATA for use with standard wallets:
import { loadAta } from '@lightprotocol/compressed-token';

const signature = await loadAta(
  rpc,
  ata,
  owner,
  mint,
  payer  // Fee payer
);

if (signature) {
  console.log('Loaded cold balance:', signature);
} else {
  console.log('No cold balance to load');
}

Token Program Support

SPL Token

import { TOKEN_PROGRAM_ID } from '@solana/spl-token';

const { mint } = await createMint(
  rpc,
  payer,
  mintAuthority.publicKey,
  9,
  undefined,
  undefined,
  TOKEN_PROGRAM_ID  // SPL Token
);

Token-2022

import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';

const { mint } = await createMint(
  rpc,
  payer,
  mintAuthority.publicKey,
  9,
  undefined,
  undefined,
  TOKEN_2022_PROGRAM_ID  // Token-2022
);

Light Token (V2)

import { LIGHT_TOKEN_PROGRAM_ID } from '@lightprotocol/stateless.js';

const { mint } = await createMintInterface(
  rpc,
  payer,
  mintAuthority,  // Must be Signer
  null,
  9,
  Keypair.generate(),
  undefined,
  LIGHT_TOKEN_PROGRAM_ID  // Light Token
);

TypeScript Support

Full type definitions for all operations:
import type {
  ParsedTokenAccount,
  TokenData,
  CompressedTokenProgram,
  AccountInterface,
  MintInterface,
  InterfaceOptions,
} from '@lightprotocol/compressed-token';

const tokenAccount: ParsedTokenAccount = {
  compressedAccount: {
    hash: bn('...'),
    treeInfo: stateTree,
    leafIndex: 42,
    owner: owner.publicKey,
    lamports: bn(0),
    address: null,
    data: null,
  },
  parsed: {
    mint: mint,
    owner: owner.publicKey,
    amount: bn(1000),
    delegate: null,
    state: 'initialized',
  },
};

Browser Support

The package supports both Node.js and browser environments:
// Node.js
import { createMint } from '@lightprotocol/compressed-token';

// Browser
import { createMint } from '@lightprotocol/compressed-token/browser';

Unified Export (V2)

V2 also provides a unified export with all features:
import {
  createMintInterface,
  createAtaInterface,
  transferInterface,
  getAccountInterface,
} from '@lightprotocol/compressed-token/unified';

Best Practices

Error Handling

try {
  const signature = await transfer(
    rpc,
    payer,
    mint,
    amount,
    owner,
    recipient
  );
} catch (error) {
  if (error.message.includes('Insufficient balance')) {
    console.error('Not enough tokens');
  } else if (error.message.includes('Invalid mint')) {
    console.error('Mint does not exist');
  } else {
    throw error;
  }
}

Transaction Confirmation

import type { ConfirmOptions } from '@solana/web3.js';

const confirmOptions: ConfirmOptions = {
  commitment: 'confirmed',
  skipPreflight: false,
};

const signature = await transfer(
  rpc,
  payer,
  mint,
  amount,
  owner,
  recipient,
  confirmOptions
);

await rpc.confirmTransaction(signature, 'finalized');

Batch Operations

Mint to multiple recipients:
const recipients = [
  Keypair.generate().publicKey,
  Keypair.generate().publicKey,
  Keypair.generate().publicKey,
];

const amounts = [
  bn(100_000_000),
  bn(200_000_000),
  bn(300_000_000),
];

const signature = await mintTo(
  rpc,
  payer,
  mint,
  recipients,  // Array of recipients
  mintAuthority,
  amounts      // Array of amounts
);

Next Steps

Token Operations

Detailed guide to token operations

@lightprotocol/stateless.js

Core SDK for compressed accounts

API Reference

Full TypeScript API documentation

Examples

Code examples and tutorials

Resources

Build docs developers (and LLMs) love