| Creation Cost | SPL | light-token |
|---|---|---|
| Token Account | ~2,000,000 lamports | ~11,000 lamports |
- Connect wallet via
useWallet() - Build unsigned transaction
- Sign transaction using
signTransaction - Send signed transaction to RPC
What you will implement
| SPL | Light | |
|---|---|---|
| Transfer | createTransferInstruction() | createTransferInterfaceInstructions() |
| Receive | getOrCreateAssociatedTokenAccount() | createLoadAtaInstructions() |
| Wrap from SPL | N/A | createWrapInstruction() |
| Unwrap to SPL | N/A | createUnwrapInstructions() |
| Get Balance | getAccount() | getAtaInterface() |
| Tx History | getSignaturesForAddress() | getSignaturesForOwnerInterface() |
Agent skill
Agent skill
Use the payments-and-wallets agent skill to add light-token payment support to your project:For orchestration, install the general skill:
- Claude Code
- Cursor
- Any Agent
Add the marketplace and install:
Report incorrect code
Copy
Ask AI
/plugin marketplace add Lightprotocol/skills
/plugin install solana-rent-free-dev
- Open Settings (Cmd+Shift+J / Ctrl+Shift+J)
- Navigate to Rules & Commands → Project Rules → Add Rule → Remote Rule (GitHub)
- Enter:
https://github.com/Lightprotocol/skills.git
Report incorrect code
Copy
Ask AI
npx skills add Lightprotocol/skills
Report incorrect code
Copy
Ask AI
npx skills add https://zkcompression.com
- Guide
- AI Prompt
Prerequisites
Report incorrect code
Copy
Ask AI
npm install @lightprotocol/compressed-token@beta \
@lightprotocol/stateless.js@beta
Report incorrect code
Copy
Ask AI
npm install @solana/wallet-adapter-base \
@solana/wallet-adapter-react \
@solana/wallet-adapter-react-ui \
@solana/wallet-adapter-wallets
Report incorrect code
Copy
Ask AI
import { createRpc } from "@lightprotocol/stateless.js";
import {
createTransferInterfaceInstructions,
createLoadAtaInstructions,
createWrapInstruction,
createUnwrapInstructions,
getAssociatedTokenAddressInterface,
getAtaInterface,
} from "@lightprotocol/compressed-token/unified";
const rpc = createRpc(RPC_ENDPOINT);
Sign with Wallet Adapter
Find the complete example on GitHub: React.
signTransaction from useWallet() to sign light-token transactions:Report incorrect code
Copy
Ask AI
import { useWallet } from "@solana/wallet-adapter-react";
const { publicKey, signTransaction } = useWallet();
- Transfer
- Receive
- Wrap (SPL → Light)
- Unwrap (Light → SPL)
Transfer light-tokens between wallets. Auto-loads cold (compressed) light-token, SPL or
Token-2022 balance before sending.Your app logic may require you to create a single sign request for your user. Here’s how to do this:
While almost always you will have only one transfer transaction, you can
optimize sending in the rare cases where you have multiple transactions.
parallelize the loads, confirm them, and then send the transfer instruction
after.
About loading: Light Token accounts reduce account rent ~200x by auto-compressing inactive accounts.
Before any action, the SDK detects cold balances and adds instructions to load them.
This almost always fits in a single atomic transaction with your regular transfer.
APIs return
TransactionInstruction[][] so the same loop handles the rare multi-transaction case
automatically.Report incorrect code
Copy
Ask AI
import { useState } from 'react';
import { PublicKey, type TransactionInstruction } from '@solana/web3.js';
import {
createTransferInterfaceInstructions,
} from '@lightprotocol/compressed-token/unified';
import { createRpc } from '@lightprotocol/stateless.js';
import { signAndSendBatches, type SignTransactionFn } from './signAndSendBatches';
// For native SOL, the user must wrap SOL → WSOL first (via useWrap or
// the payments toolkit). createTransferInterfaceInstructions works with
// WSOL like any other SPL token once it exists in a token account.
export interface TransferParams {
ownerPublicKey: string;
mint: string;
toAddress: string;
amount: number;
decimals?: number;
}
export interface TransferArgs {
params: TransferParams;
signTransaction: SignTransactionFn;
}
export function useTransfer() {
const [isLoading, setIsLoading] = useState(false);
const transfer = async (args: TransferArgs): Promise<string> => {
setIsLoading(true);
try {
const { params, signTransaction } = args;
const { ownerPublicKey, mint, toAddress, amount, decimals = 9 } = params;
const rpc = import.meta.env.VITE_LOCALNET === 'true'
? createRpc()
: createRpc(import.meta.env.VITE_HELIUS_RPC_URL);
const owner = new PublicKey(ownerPublicKey);
const mintPubkey = new PublicKey(mint);
const recipient = new PublicKey(toAddress);
const tokenAmount = Math.round(amount * Math.pow(10, decimals));
const instructions: TransactionInstruction[][] =
await createTransferInterfaceInstructions(
rpc, owner, mintPubkey, tokenAmount, owner, recipient,
);
const signature = await signAndSendBatches(instructions, {
rpc,
feePayer: owner,
signTransaction,
});
if (!signature) {
throw new Error('Transfer returned no instructions');
}
return signature;
} finally {
setIsLoading(false);
}
};
return { transfer, isLoading };
}
Sign all transactions together
Sign all transactions together
Report incorrect code
Copy
Ask AI
const transactions = instructions.map((ixs) => new Transaction().add(...ixs));
// One approval for all
const signed = await wallet.signAllTransactions(transactions);
for (const tx of signed) {
// send...
await sendAndConfirmTransaction(rpc, tx);
}
Optimize sending (parallel conditional loads, then transfer)
Optimize sending (parallel conditional loads, then transfer)
Report incorrect code
Copy
Ask AI
import {
createTransferInterfaceInstructions,
sliceLast,
} from "@lightprotocol/compressed-token/unified";
const instructions = await createTransferInterfaceInstructions(
rpc, owner, mint, amount, owner, recipient
);
const { rest: loadInstructions, last: transferInstructions } = sliceLast(instructions);
// empty = nothing to load, will no-op.
await Promise.all(
loadInstructions.map(async (ixs) => {
const tx = new Transaction().add(...ixs);
const { blockhash } = await rpc.getLatestBlockhash();
tx.recentBlockhash = blockhash;
tx.feePayer = owner;
const signed = await signTransaction(tx);
const sig = await rpc.sendRawTransaction(signed.serialize(), {
skipPreflight: false, preflightCommitment: 'confirmed',
});
await rpc.confirmTransaction(sig, 'confirmed');
})
);
// Then send the transfer
const transferTx = new Transaction().add(...transferInstructions);
const { blockhash } = await rpc.getLatestBlockhash();
transferTx.recentBlockhash = blockhash;
transferTx.feePayer = owner;
const signedTransfer = await signTransaction(transferTx);
await rpc.sendRawTransaction(signedTransfer.serialize(), {
skipPreflight: false, preflightCommitment: 'confirmed',
});
signAndSendBatches helper
signAndSendBatches helper
All hooks in this toolkit use a shared
signAndSendBatches helper that handles blockhash, signing, sending, and confirming for each instruction batch:Report incorrect code
Copy
Ask AI
import { Transaction, TransactionInstruction, PublicKey } from '@solana/web3.js';
export type SignTransactionFn = (transaction: Transaction) => Promise<Transaction>;
interface SignAndSendOptions {
rpc: any;
feePayer: PublicKey;
signTransaction: SignTransactionFn;
}
export async function signAndSendBatches(
instructionBatches: TransactionInstruction[][],
options: SignAndSendOptions,
): Promise<string | null> {
const { rpc, feePayer, signTransaction } = options;
const signatures: string[] = [];
for (const ixs of instructionBatches) {
const tx = new Transaction().add(...ixs);
const { blockhash } = await rpc.getLatestBlockhash();
tx.recentBlockhash = blockhash;
tx.feePayer = feePayer;
const signedTx = await signTransaction(tx);
const sig = await rpc.sendRawTransaction(signedTx.serialize(), {
skipPreflight: false,
preflightCommitment: 'confirmed',
});
await rpc.confirmTransaction(sig, 'confirmed');
signatures.push(sig);
}
return signatures.length > 0 ? signatures[signatures.length - 1] : null;
}
Load creates the associated token account (ATA) if needed and loads any compressed state into it.
Share the ATA address with the sender.
Report incorrect code
Copy
Ask AI
import { useState } from 'react';
import { PublicKey } from '@solana/web3.js';
import {
createLoadAtaInstructions,
getAssociatedTokenAddressInterface,
} from '@lightprotocol/compressed-token/unified';
import { createRpc } from '@lightprotocol/stateless.js';
import { signAndSendBatches, type SignTransactionFn } from './signAndSendBatches';
export interface ReceiveParams {
ownerPublicKey: string;
mint: string;
}
export interface ReceiveArgs {
params: ReceiveParams;
signTransaction: SignTransactionFn;
}
export function useReceive() {
const [isLoading, setIsLoading] = useState(false);
const receive = async (args: ReceiveArgs): Promise<string | null> => {
setIsLoading(true);
try {
const { params, signTransaction } = args;
const rpc = import.meta.env.VITE_LOCALNET === 'true'
? createRpc()
: createRpc(import.meta.env.VITE_HELIUS_RPC_URL);
const owner = new PublicKey(params.ownerPublicKey);
const mintPubkey = new PublicKey(params.mint);
const ata = getAssociatedTokenAddressInterface(mintPubkey, owner);
// Returns TransactionInstruction[][].
// Each inner array is one transaction.
// Almost always returns just one.
const instructions = await createLoadAtaInstructions(
rpc, ata, owner, mintPubkey, owner,
);
return await signAndSendBatches(instructions, {
rpc,
feePayer: owner,
signTransaction,
});
} finally {
setIsLoading(false);
}
};
return { receive, isLoading };
}
Wrap SPL or Token-2022 tokens into a light-token associated token account.
Report incorrect code
Copy
Ask AI
import { useState } from 'react';
import { PublicKey, Transaction, ComputeBudgetProgram } from '@solana/web3.js';
import { getAssociatedTokenAddressSync, getAccount } from '@solana/spl-token';
import { getSplInterfaceInfos } from '@lightprotocol/compressed-token';
import {
createWrapInstruction,
getAssociatedTokenAddressInterface,
createAssociatedTokenAccountInterfaceIdempotentInstruction,
} from '@lightprotocol/compressed-token/unified';
import { createRpc, CTOKEN_PROGRAM_ID } from '@lightprotocol/stateless.js';
import type { SignTransactionFn } from './signAndSendBatches';
export interface WrapParams {
ownerPublicKey: string;
mint: string;
amount: number;
decimals?: number;
}
export interface WrapArgs {
params: WrapParams;
signTransaction: SignTransactionFn;
}
export function useWrap() {
const [isLoading, setIsLoading] = useState(false);
const wrap = async (args: WrapArgs): Promise<string> => {
setIsLoading(true);
try {
const { params, signTransaction } = args;
const { ownerPublicKey, mint, amount, decimals = 9 } = params;
const rpc = import.meta.env.VITE_LOCALNET === 'true'
? createRpc()
: createRpc(import.meta.env.VITE_HELIUS_RPC_URL);
const owner = new PublicKey(ownerPublicKey);
const mintPubkey = new PublicKey(mint);
const tokenAmount = BigInt(Math.round(amount * Math.pow(10, decimals)));
// Get SPL interface info — determines whether mint uses SPL or T22
const splInterfaceInfos = await getSplInterfaceInfos(rpc, mintPubkey);
const splInterfaceInfo = splInterfaceInfos.find(
(info) => info.isInitialized,
);
if (!splInterfaceInfo) throw new Error('No SPL interface found for this mint');
const { tokenProgram } = splInterfaceInfo;
// Derive source associated token account using the mint's token program (SPL or T22)
const splAta = getAssociatedTokenAddressSync(mintPubkey, owner, false, tokenProgram);
const ataAccount = await getAccount(rpc, splAta, undefined, tokenProgram);
if (ataAccount.amount < BigInt(tokenAmount)) {
throw new Error('Insufficient SPL balance');
}
// Derive light-token associated token account
const lightTokenAta = getAssociatedTokenAddressInterface(mintPubkey, owner);
// Build transaction
const tx = new Transaction().add(
ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
createAssociatedTokenAccountInterfaceIdempotentInstruction(
owner, lightTokenAta, owner, mintPubkey, CTOKEN_PROGRAM_ID,
),
createWrapInstruction(
splAta, lightTokenAta, owner, mintPubkey,
tokenAmount, splInterfaceInfo, decimals, owner,
),
);
const { blockhash } = await rpc.getLatestBlockhash();
tx.recentBlockhash = blockhash;
tx.feePayer = owner;
const signedTx = await signTransaction(tx);
const sig = await rpc.sendRawTransaction(signedTx.serialize(), {
skipPreflight: false,
preflightCommitment: 'confirmed',
});
await rpc.confirmTransaction(sig, 'confirmed');
return sig;
} finally {
setIsLoading(false);
}
};
return { wrap, isLoading };
}
Unwrap light-token balance to SPL or Token-2022. Use unwrap to interact with applications that only support SPL/Token-2022.
Report incorrect code
Copy
Ask AI
import { useState } from 'react';
import { PublicKey } from '@solana/web3.js';
import { getAssociatedTokenAddressSync } from '@solana/spl-token';
import {
createUnwrapInstructions,
} from '@lightprotocol/compressed-token/unified';
import { createRpc } from '@lightprotocol/stateless.js';
import { signAndSendBatches, type SignTransactionFn } from './signAndSendBatches';
export interface UnwrapParams {
ownerPublicKey: string;
mint: string;
amount: number;
decimals?: number;
}
export interface UnwrapArgs {
params: UnwrapParams;
signTransaction: SignTransactionFn;
}
export function useUnwrap() {
const [isLoading, setIsLoading] = useState(false);
const unwrap = async (args: UnwrapArgs): Promise<string> => {
setIsLoading(true);
try {
const { params, signTransaction } = args;
const { ownerPublicKey, mint, amount, decimals = 9 } = params;
const rpc = import.meta.env.VITE_LOCALNET === 'true'
? createRpc()
: createRpc(import.meta.env.VITE_HELIUS_RPC_URL);
const owner = new PublicKey(ownerPublicKey);
const mintPubkey = new PublicKey(mint);
const tokenAmount = BigInt(Math.round(amount * Math.pow(10, decimals)));
// Auto-detect token program (SPL vs T22) from mint account owner
const mintAccountInfo = await rpc.getAccountInfo(mintPubkey);
if (!mintAccountInfo) throw new Error(`Mint account ${mint} not found`);
const tokenProgramId = mintAccountInfo.owner;
// Destination: SPL/T22 associated token account
const splAta = getAssociatedTokenAddressSync(mintPubkey, owner, false, tokenProgramId);
// Returns TransactionInstruction[][].
// Each inner array is one transaction.
// Handles loading + unwrapping together.
const instructions = await createUnwrapInstructions(
rpc, splAta, owner, mintPubkey, tokenAmount, owner,
);
const signature = await signAndSendBatches(instructions, {
rpc,
feePayer: owner,
signTransaction,
});
if (!signature) {
throw new Error('Unwrap returned no instructions');
}
return signature;
} finally {
setIsLoading(false);
}
};
return { unwrap, isLoading };
}
Show Balance
Query token balances to show a unified balance of SOL, Light, SPL, and Token-2022.Report incorrect code
Copy
Ask AI
import {
getAssociatedTokenAddressInterface,
getAtaInterface,
} from "@lightprotocol/compressed-token/unified";
const ata = getAssociatedTokenAddressInterface(mint, owner);
const account = await getAtaInterface(rpc, ata, owner, mint);
console.log(account.parsed.amount);
Unified balance example
Unified balance example
Report incorrect code
Copy
Ask AI
import { useState, useCallback } from 'react';
import { PublicKey } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, getMint } from '@solana/spl-token';
import { createRpc } from '@lightprotocol/stateless.js';
import {
getAssociatedTokenAddressInterface,
getAtaInterface,
} from '@lightprotocol/compressed-token/unified';
export interface TokenBalance {
mint: string;
decimals: number;
isNative: boolean;
hot: bigint;
cold: bigint;
spl: bigint;
t22: bigint;
unified: bigint;
}
export function useUnifiedBalance() {
const [balances, setBalances] = useState<TokenBalance[]>([]);
const [isLoading, setIsLoading] = useState(false);
const fetchBalances = useCallback(async (ownerAddress: string) => {
if (!ownerAddress) return;
setIsLoading(true);
try {
const rpc = import.meta.env.VITE_LOCALNET === 'true'
? createRpc()
: createRpc(import.meta.env.VITE_HELIUS_RPC_URL);
const owner = new PublicKey(ownerAddress);
// Per-mint accumulator
const mintMap = new Map<string, { spl: bigint; t22: bigint; hot: bigint; cold: bigint; decimals: number; tokenProgram: PublicKey }>();
const getOrCreate = (mintStr: string) => {
let entry = mintMap.get(mintStr);
if (!entry) {
entry = { spl: 0n, t22: 0n, hot: 0n, cold: 0n, decimals: 9, tokenProgram: TOKEN_PROGRAM_ID };
mintMap.set(mintStr, entry);
}
return entry;
};
// 1. SOL balance
let solLamports = 0;
try {
solLamports = await rpc.getBalance(owner);
} catch {
// Failed to fetch SOL balance
}
// 2. SPL accounts
try {
const splAccounts = await rpc.getTokenAccountsByOwner(owner, {
programId: TOKEN_PROGRAM_ID,
});
for (const { account } of splAccounts.value) {
const buf = toBuffer(account.data);
if (!buf || buf.length < 72) continue;
const mint = new PublicKey(buf.subarray(0, 32));
const amount = buf.readBigUInt64LE(64);
const mintStr = mint.toBase58();
getOrCreate(mintStr).spl += amount;
}
} catch {
// No SPL accounts
}
// 3. Token 2022 accounts
try {
const t22Accounts = await rpc.getTokenAccountsByOwner(owner, {
programId: TOKEN_2022_PROGRAM_ID,
});
for (const { account } of t22Accounts.value) {
const buf = toBuffer(account.data);
if (!buf || buf.length < 72) continue;
const mint = new PublicKey(buf.subarray(0, 32));
const amount = buf.readBigUInt64LE(64);
const mintStr = mint.toBase58();
const entry = getOrCreate(mintStr);
entry.t22 += amount;
entry.tokenProgram = TOKEN_2022_PROGRAM_ID;
}
} catch {
// No Token 2022 accounts
}
// 4. Cold balance from compressed token accounts
try {
const compressed = await rpc.getCompressedTokenBalancesByOwnerV2(owner);
for (const item of compressed.value.items) {
const mintStr = item.mint.toBase58();
getOrCreate(mintStr).cold += BigInt(item.balance.toString());
}
} catch {
// No compressed accounts
}
// 5. Fetch actual decimals for each mint
const mintKeys = [...mintMap.keys()];
await Promise.allSettled(
mintKeys.map(async (mintStr) => {
try {
const mint = new PublicKey(mintStr);
const entry = getOrCreate(mintStr);
const mintInfo = await getMint(rpc, mint, undefined, entry.tokenProgram);
entry.decimals = mintInfo.decimals;
} catch {
// Keep default decimals if mint fetch fails
}
}),
);
// 6. Hot balance from Light Token associated token account
await Promise.allSettled(
mintKeys.map(async (mintStr) => {
try {
const mint = new PublicKey(mintStr);
const ata = getAssociatedTokenAddressInterface(mint, owner);
const { parsed } = await getAtaInterface(rpc, ata, owner, mint);
const entry = getOrCreate(mintStr);
entry.hot = BigInt(parsed.amount.toString());
} catch {
// Associated token account does not exist for this mint — hot stays 0n
}
}),
);
// 7. Assemble TokenBalance[]
const result: TokenBalance[] = [];
// Pull WSOL data from mintMap (if user has WSOL in token accounts)
const WSOL_MINT = 'So11111111111111111111111111111111111111112';
const wsolEntry = mintMap.get(WSOL_MINT);
mintMap.delete(WSOL_MINT); // prevent duplicate row
// SOL entry: native SOL + WSOL Light Token balances
result.push({
mint: WSOL_MINT,
decimals: 9,
isNative: true,
hot: wsolEntry?.hot ?? 0n,
cold: wsolEntry?.cold ?? 0n,
spl: BigInt(solLamports),
t22: wsolEntry?.t22 ?? 0n,
unified: (wsolEntry?.hot ?? 0n) + (wsolEntry?.cold ?? 0n),
});
// Token entries
for (const [mintStr, entry] of mintMap) {
result.push({
mint: mintStr,
decimals: entry.decimals,
isNative: false,
hot: entry.hot,
cold: entry.cold,
spl: entry.spl,
t22: entry.t22,
unified: entry.hot + entry.cold,
});
}
setBalances(result);
} catch (error) {
console.error('Failed to fetch balances:', error);
setBalances([]);
} finally {
setIsLoading(false);
}
}, []);
return { balances, isLoading, fetchBalances };
}
function toBuffer(data: Buffer | Uint8Array | string | unknown): Buffer | null {
if (data instanceof Buffer) return data;
if (data instanceof Uint8Array) return Buffer.from(data);
return null;
}
Get transaction history
Fetch light-token transaction history for an owner.Report incorrect code
Copy
Ask AI
import { createRpc } from "@lightprotocol/stateless.js";
const result = await rpc.getSignaturesForOwnerInterface(owner);
console.log(result.signatures); // Merged + deduplicated
console.log(result.solana); // On-chain txs only
console.log(result.compressed); // Compressed txs only
Transaction history example
Transaction history example
Report incorrect code
Copy
Ask AI
import { useState, useCallback } from 'react';
import { PublicKey } from '@solana/web3.js';
import { createRpc } from '@lightprotocol/stateless.js';
export interface Transaction {
signature: string;
slot: number;
blockTime: number;
timestamp: string;
}
export function useTransactionHistory() {
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchTransactionHistory = useCallback(
async (
ownerAddress: string,
limit: number = 10,
) => {
if (!ownerAddress) {
setTransactions([]);
return;
}
setIsLoading(true);
setError(null);
try {
const rpc = import.meta.env.VITE_LOCALNET === 'true'
? createRpc()
: createRpc(import.meta.env.VITE_HELIUS_RPC_URL);
const owner = new PublicKey(ownerAddress);
const result = await rpc.getSignaturesForOwnerInterface(owner);
if (!result.signatures || result.signatures.length === 0) {
setTransactions([]);
return;
}
const limitedSignatures = result.signatures.slice(0, limit);
const basicTransactions = limitedSignatures.map((sig) => ({
signature: sig.signature,
slot: sig.slot,
blockTime: sig.blockTime ?? 0,
timestamp: sig.blockTime ? new Date(sig.blockTime * 1000).toISOString() : '',
}));
setTransactions(basicTransactions);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
setError(message);
setTransactions([]);
} finally {
setIsLoading(false);
}
},
[]
);
return { transactions, isLoading, error, fetchTransactionHistory };
}
One-time: Create interface PDA to existing SPL Mint
For existing SPL mints (e.g. USDC), register the SPL interface once. This creates the omnibus PDA that holds SPL tokens when wrapped to light-token.Find a full code example here.
Report incorrect code
Copy
Ask AI
import { getSplInterfaceInfos } from "@lightprotocol/compressed-token";
try {
const infos = await getSplInterfaceInfos(rpc, mint);
const exists = infos.some((i) => i.isInitialized);
console.log("Interface exists:", exists);
} catch {
console.log("No interface registered for this mint.");
}
- Instruction
- Action
Report incorrect code
Copy
Ask AI
import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
import { LightTokenProgram } from "@lightprotocol/compressed-token";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
const ix = await LightTokenProgram.createSplInterface({
feePayer: payer.publicKey,
mint,
tokenProgramId: TOKEN_PROGRAM_ID,
});
const tx = new Transaction().add(ix);
await sendAndConfirmTransaction(rpc, tx, [payer]);
Report incorrect code
Copy
Ask AI
import { createSplInterface } from "@lightprotocol/compressed-token";
await createSplInterface(rpc, payer, mint);
Or: Create a new SPL mint with interface
Or: Create a new SPL mint with interface
Use
createMintInterface with TOKEN_PROGRAM_ID to create a new SPL mint and register the interface in one transaction:Report incorrect code
Copy
Ask AI
import { createMintInterface } from "@lightprotocol/compressed-token/unified";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
const { mint } = await createMintInterface(
rpc, payer, payer, null, 9, undefined, undefined, TOKEN_PROGRAM_ID
);
Integrate light-token with Solana Wallet Adapter
Report incorrect code
Copy
Ask AI
---
description: Integrate light-token with Solana Wallet Adapter
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
---
## Integrate light-token with Solana Wallet Adapter
Context:
- Guide: https://zkcompression.com/light-token/toolkits/for-wallet-adapter
- Skills and resources index: https://zkcompression.com/skill.md
- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets
- Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js
- React example: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-wallet-adapter/react
SPL → Light Token API mapping:
| Operation | SPL | Light Token |
| Transfer | createTransferInstruction() | createTransferInterfaceInstructions() |
| Wrap SPL→Light | N/A | createWrapInstruction() |
| Unwrap Light→SPL | N/A | createUnwrapInstructions() |
| Get balance | getAccount() | getAtaInterface() |
| Tx history | getSignaturesForAddress() | getSignaturesForOwnerInterface() |
### 1. Index project
- Grep `wallet-adapter|useWallet|WalletProvider|useConnection|createTransferInstruction|@solana/spl-token|Connection` across src/
- Glob `**/*.ts` and `**/*.tsx` for project structure
- Identify: wallet adapter packages, existing wallet setup, RPC config, token operations
- Check package.json for existing @lightprotocol/* or @solana/spl-token dependencies
- Task subagent (Grep/Read/WebFetch) if project has multiple packages to scan in parallel
### 2. Read references
- WebFetch the guide above — review the React code examples
- WebFetch skill.md — check for a dedicated skill and resources matching this task
- TaskCreate one todo per phase below to track progress
### 3. Clarify intention
- AskUserQuestion: what is the goal? (new wallet adapter integration, migrate existing wallet-adapter+SPL code, add light-token alongside existing SPL)
- AskUserQuestion: which operations? (transfer, wrap, unwrap, balances, tx history — or all)
- Summarize findings and wait for user confirmation before implementing
### 4. Create plan
- Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
- Verify existing wallet adapter setup is compatible (signTransaction pattern from useWallet)
- Key integration pattern: build unsigned tx with light-token SDK → sign with `signTransaction` from `useWallet()` → send to RPC
- If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
- Present the plan to the user for approval before proceeding
### 5. Implement
- Add deps if missing: Bash `npm install @lightprotocol/compressed-token @lightprotocol/stateless.js`
- Set up RPC: `createRpc(RPC_ENDPOINT)` with a ZK Compression endpoint (Helius, Triton)
- Import from `@lightprotocol/compressed-token/unified` for the interface APIs
- Use `useWallet()` to get `publicKey` and `signTransaction`
- Follow the guide and the approved plan
- Write/Edit to create or modify files
- TaskUpdate to mark each step done
### 6. Verify
- Bash `tsc --noEmit`
- Bash run existing test suite if present
- TaskUpdate to mark complete
### Tools
- mcp__zkcompression__SearchLightProtocol("<query>") for API details
- mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
- Task subagent with Grep/Read/WebFetch for parallel lookups
- TaskList to check remaining work