Skip to main content
The 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 via createTokenPool().
// 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!
Dependencies
npm install --save-dev typescript tsx @types/node && \
npm install --save \
    @lightprotocol/stateless.js \
    @lightprotocol/compressed-token \
    @solana/web3.js \
    @solana/spl-token
Alternatives:
yarn add --dev typescript tsx @types/node && \
yarn add \
    @lightprotocol/stateless.js \
    @lightprotocol/compressed-token \
    @solana/web3.js \
    @solana/spl-token
pnpm add --save-dev typescript tsx @types/node && \
pnpm add \
    @lightprotocol/stateless.js \
    @lightprotocol/compressed-token \
    @solana/web3.js \
    @solana/spl-token
Developer EnvironmentBy default, this guide uses Localnet.
# Install the development CLI
npm install @lightprotocol/zk-compression-cli
# Start a local test validator
light test-validator

## ensure you have the Solana CLI accessible in your system PATH
// 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();
Alternative: Using DevnetFollow these steps to create an RPC Connection. Replace <your_api_key> with your API key before running.
Get your API Key here, if you don’t have one yet.
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
// 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);

Troubleshooting

Attempting to revoke non-delegated accounts.
/// 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

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

Next Steps

That’s it! Explore more guides in our cookbook section, or check out the advanced guides.