approve() and revoke() functions grant and remove delegate spending authority for compressed tokens. Only the token owner can perform these operations.
Before we approve or revoke delegates, we need:
- compressed token accounts to delegate or revoke delegation from, and
- an SPL mint with a token pool for compression. This token pool can be created for new SPL mints via
createMint()or added to existing SPL mints viacreateTokenPool().
Copy
Ask AI
// Approve delegate for spending up to the specified amount
const approveSignature = await approve(
rpc,
payer,
mint, // SPL mint with token pool for compression
amount,
owner,
delegate.publicKey, // delegate account
);
Full Code Example
Prerequisites
Make sure you have dependencies and developer environment set up!Prerequisites & Setup
Prerequisites & Setup
DependenciesAlternatives:Developer EnvironmentBy default, this guide uses Localnet.Alternative: Using DevnetFollow these steps to create an RPC Connection. Replace
Copy
Ask AI
npm install --save-dev typescript tsx @types/node && \
npm install --save \
@lightprotocol/stateless.js \
@lightprotocol/compressed-token \
@solana/web3.js \
@solana/spl-token
Copy
Ask AI
yarn add --dev typescript tsx @types/node && \
yarn add \
@lightprotocol/stateless.js \
@lightprotocol/compressed-token \
@solana/web3.js \
@solana/spl-token
Copy
Ask AI
pnpm add --save-dev typescript tsx @types/node && \
pnpm add \
@lightprotocol/stateless.js \
@lightprotocol/compressed-token \
@solana/web3.js \
@solana/spl-token
Copy
Ask AI
# Install the development CLI
npm install @lightprotocol/zk-compression-cli
Copy
Ask AI
# Start a local test validator
light test-validator
## ensure you have the Solana CLI accessible in your system PATH
Copy
Ask AI
// createRpc() defaults to local test validator endpoints
import {
Rpc,
createRpc,
} from "@lightprotocol/stateless.js";
const connection: Rpc = createRpc();
async function main() {
let slot = await connection.getSlot();
console.log(slot);
let health = await connection.getIndexerHealth(slot);
console.log(health);
// "Ok"
}
main();
<your_api_key> with your API key before running.Get your API Key here, if you don’t have one yet.
Copy
Ask AI
import { createRpc } from "@lightprotocol/stateless.js";
// Helius exposes Solana and Photon RPC endpoints through a single URL
const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=<your_api_key>";
const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT);
console.log("Connection created!");
console.log("RPC Endpoint:", RPC_ENDPOINT);
Approve / Revoke Delegates
- Approve Delegate
- Approve and Revoke
Approve delegate authority for compressed tokens. The delegate can spend up to the approved amount.
approve-delegates.ts
Copy
Ask AI
// 1. Setup funded payer and connect to local validator
// 2. Create mint and token pool with initial tokens
// 3. Call approve() with mint, amount, owner, delegate
// 4. Verify delegation via getCompressedTokenAccountsByDelegate
import { Keypair } from '@solana/web3.js';
import { createRpc } from '@lightprotocol/stateless.js';
import {
createMint,
mintTo,
approve
} from '@lightprotocol/compressed-token';
import BN from 'bn.js';
async function approveDelegates() {
// Step 1: Setup funded payer and connect to local validator
const rpc = createRpc(); // defaults to localhost:8899
const payer = Keypair.generate();
const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
await rpc.confirmTransaction(airdropSignature);
// Step 2: Create SPL mint with token pool and mint initial tokens
const { mint } = await createMint(
rpc,
payer,
payer.publicKey, // mint authority
9 // decimals
);
console.log("SPL mint with token pool created:", mint.toBase58());
const tokenOwner = Keypair.generate();
const initialAmount = 1_000_000_000; // 1 token with 9 decimals
await mintTo(
rpc,
payer,
mint, // SPL mint with token pool for compression
tokenOwner.publicKey, // recipient
payer, // mint authority
initialAmount
);
console.log("Initial tokens minted:", initialAmount / 1_000_000_000, "tokens");
console.log("Token owner:", tokenOwner.publicKey.toBase58());
// Generate delegate address and define amount to approve for delegation
const delegate = Keypair.generate();
const delegateAmount = 500_000_000; // 0.5 tokens
// Step 3: Call approve() with mint, amount, owner, delegate
const approveTx = await approve(
rpc,
payer,
mint, // SPL mint with token pool for compression
delegateAmount,
tokenOwner, // owner keypair
delegate.publicKey // delegate address
);
console.log("Delegate approved");
console.log("Delegate:", delegate.publicKey.toBase58());
console.log("Approved amount:", delegateAmount / 1_000_000_000, "tokens");
console.log("Transaction:", approveTx);
// Step 4: Verify delegation via getCompressedTokenAccountsByDelegate
const delegateAccounts = await rpc.getCompressedTokenAccountsByDelegate(
delegate.publicKey,
{ mint }
);
// Check delegated balance
if (delegateAccounts.items.length > 0) {
const delegatedBalance = delegateAccounts.items.reduce(
(sum, account) => sum.add(account.parsed.amount),
new BN(0)
);
console.log("Verified delegation:", delegatedBalance.toNumber() / 1_000_000_000, "tokens");
}
return {
mint,
tokenOwner,
delegate: delegate.publicKey,
approveTransaction: approveTx,
delegatedAmount: delegateAmount
};
}
approveDelegates().catch(console.error);
Approve delegation, then revoke it in a single script.
approve-and-revoke-delegates.ts
Copy
Ask AI
// Complete workflow: approve and revoke delegation
// 1. Setup and create mint with initial tokens
// 2. Approve delegation
// 3. Verify delegation exists
// 4. Revoke delegation
// 5. Verify delegation removed
import { Keypair } from '@solana/web3.js';
import { createRpc } from '@lightprotocol/stateless.js';
import {
createMint,
mintTo,
approve,
revoke
} from '@lightprotocol/compressed-token';
import BN from 'bn.js';
async function approveAndRevokeDelegates() {
// Step 1: Setup funded payer and connect to local validator
const rpc = createRpc(); // defaults to localhost:8899
const payer = Keypair.generate();
const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
await rpc.confirmTransaction(airdropSignature);
// Create SPL mint with token pool and mint initial tokens
const { mint } = await createMint(
rpc,
payer,
payer.publicKey, // mint authority
9 // decimals
);
console.log("SPL mint with token pool created:", mint.toBase58());
const tokenOwner = Keypair.generate();
const initialAmount = 1_000_000_000; // 1 token with 9 decimals
await mintTo(
rpc,
payer,
mint, // SPL mint with token pool for compression
tokenOwner.publicKey, // recipient
payer, // mint authority
initialAmount
);
console.log("Initial tokens minted:", initialAmount / 1_000_000_000, "tokens");
console.log("Token owner:", tokenOwner.publicKey.toBase58());
// Generate delegate and define delegation amount
const delegate = Keypair.generate();
const delegateAmount = 500_000_000; // 0.5 tokens
// Step 2: Approve delegation
console.log("\n--- Approving Delegation ---");
const approveTx = await approve(
rpc,
payer,
mint,
delegateAmount,
tokenOwner,
delegate.publicKey
);
console.log("Delegate approved");
console.log("Delegate:", delegate.publicKey.toBase58());
console.log("Approved amount:", delegateAmount / 1_000_000_000, "tokens");
console.log("Transaction:", approveTx);
// Step 3: Verify delegation exists
const delegateAccountsBefore = await rpc.getCompressedTokenAccountsByDelegate(
delegate.publicKey,
{ mint }
);
const delegatedBalance = delegateAccountsBefore.items.reduce(
(sum, account) => sum.add(account.parsed.amount),
new BN(0)
);
console.log("Verified delegation:", delegatedBalance.toNumber() / 1_000_000_000, "tokens");
// Step 4: Revoke delegation
console.log("\n--- Revoking Delegation ---");
const revokeTx = await revoke(
rpc,
payer,
delegateAccountsBefore.items, // delegated accounts to revoke
tokenOwner,
);
console.log("Delegate revoked!");
console.log("Transaction:", revokeTx);
// Step 5: Verify delegation removed
const delegateAccountsAfter = await rpc.getCompressedTokenAccountsByDelegate(
delegate.publicKey,
{ mint }
);
const ownerAccounts = await rpc.getCompressedTokenAccountsByOwner(
tokenOwner.publicKey,
{ mint }
);
const ownerBalance = ownerAccounts.items.reduce(
(sum, account) => sum.add(account.parsed.amount),
new BN(0)
);
console.log("\n--- Final State ---");
console.log("Delegated accounts remaining:", delegateAccountsAfter.items.length);
console.log("Owner balance after revocation:", ownerBalance.toNumber() / 1_000_000_000, "tokens");
return {
approveTransaction: approveTx,
revokeTransaction: revokeTx,
finalOwnerBalance: ownerBalance
};
}
approveAndRevokeDelegates().catch(console.error);
Troubleshooting
Account is not delegated
Account is not delegated
Attempting to revoke non-delegated accounts.
Copy
Ask AI
/// Verify accounts are delegated before revocation.
const delegateAccounts = await rpc.getCompressedTokenAccountsByDelegate(
delegate.publicKey,
{ mint }
);
if (delegateAccounts.items.length === 0) {
console.log("No delegated accounts to revoke");
return;
}
Advanced Configuration
Approve Multiple Delegates
Approve Multiple Delegates
Copy
Ask AI
const delegates = [
Keypair.generate().publicKey,
Keypair.generate().publicKey,
];
const amounts = [
200_000_000, // 0.2 tokens to first delegate
300_000_000, // 0.3 tokens to second delegate
];
// Approve each delegate
for (let i = 0; i < delegates.length; i++) {
const approveTx = await approve(
rpc,
payer,
mint,
amounts[i],
tokenOwner,
delegates[i],
);
console.log(`Delegate ${i + 1} approved:`, approveTx);
}
Revoke Multiple Delegates
Revoke Multiple Delegates
Copy
Ask AI
const delegates = [
new PublicKey("DELEGATE_1_ADDRESS"),
new PublicKey("DELEGATE_2_ADDRESS"),
];
// Revoke each delegate
for (const delegate of delegates) {
// Get delegated accounts for this delegate
const delegateAccounts = await rpc.getCompressedTokenAccountsByDelegate(
delegate,
{ mint }
);
if (delegateAccounts.items.length > 0) {
const revokeTx = await revoke(
rpc,
payer,
delegateAccounts.items,
tokenOwner,
);
console.log(`Delegate ${delegate.toBase58()} revoked:`, revokeTx);
}
}