> ## Documentation Index
> Fetch the complete documentation index at: https://www.zkcompression.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Token Distribution

> Complete client and program guides to create an airdrop – with or without code. ZK compression is the most efficient way to distribute SPL tokens.

export const TokenAccountCompressedVsSpl = () => {
  const [numAccounts, setNumAccounts] = useState(100000);
  const [showCustomAccounts, setShowCustomAccounts] = useState(false);
  const ACCOUNT_STORAGE_OVERHEAD = 128;
  const LAMPORTS_PER_BYTE = 6960;
  const DATA_LEN = 165;
  const COMPRESSED_COST_PER_ACCOUNT = 10300;
  const LAMPORTS_PER_SOL = 1_000_000_000;
  const ACCOUNTS_MAX = 1000000;
  const solanaCost = numAccounts * (ACCOUNT_STORAGE_OVERHEAD + DATA_LEN) * LAMPORTS_PER_BYTE;
  const compressedCost = numAccounts * COMPRESSED_COST_PER_ACCOUNT;
  const savings = solanaCost - compressedCost;
  const savingsPercent = (savings / solanaCost * 100).toFixed(1);
  const handleAccountsChange = value => {
    const num = Math.max(1, Math.min(1000000, Number.parseInt(value) || 1));
    setNumAccounts(num);
  };
  const formatSOL = lamports => {
    const sol = lamports / LAMPORTS_PER_SOL;
    if (sol >= 1000) {
      return sol.toLocaleString(undefined, {
        maximumFractionDigits: 2
      });
    } else if (sol >= 1) {
      return sol.toFixed(4);
    }
    return sol.toFixed(6);
  };
  const accountsPresets = [10000, 100000, 500000];
  const SliderMarkers = ({max, step}) => {
    const marks = [];
    for (let i = step; i < max; i += step) {
      const percent = i / max * 100;
      marks.push(<div key={i} className="absolute top-1/2 -translate-y-1/2 w-px h-2 bg-zinc-300 dark:bg-white/30" style={{
        left: `${percent}%`
      }} />);
    }
    return <>{marks}</>;
  };
  return <div className="p-5 rounded-3xl not-prose mt-4 dark:bg-white/5 backdrop-blur-xl border border-black/[0.04] dark:border-white/10 shadow-lg" style={{
    fontFamily: "Inter, sans-serif"
  }}>
      <div className="space-y-5">
        {}
        <div className="space-y-2 px-3">
          <div className="flex justify-between items-center">
            <span className="text-sm text-zinc-700 dark:text-white/80">
              Number of Token Accounts
            </span>
            <div className="flex items-center gap-1.5">
              {accountsPresets.map(a => <button key={a} onClick={() => {
    setNumAccounts(a);
    setShowCustomAccounts(false);
  }} className={`px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all ${numAccounts === a && !showCustomAccounts ? "bg-blue-500/20 border-blue-500/50 text-blue-600 dark:text-blue-400" : "bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]"}`}>
                  {a.toLocaleString()}
                </button>)}
              {showCustomAccounts ? <input type="number" min="1" max="1000000" value={numAccounts} onChange={e => handleAccountsChange(e.target.value)} className="w-24 px-2 py-1 text-right text-xs font-mono font-medium bg-blue-500/10 dark:bg-blue-500/20 border border-blue-500/50 rounded-lg backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50" autoFocus /> : <button onClick={() => setShowCustomAccounts(true)} className="px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]">
                  Custom
                </button>}
            </div>
          </div>
          <div className="flex items-center">
            <span className="w-1/3 text-xs text-zinc-500 dark:text-white/50 whitespace-nowrap">
              {numAccounts.toLocaleString()} accounts
            </span>
            <div className="w-2/3 relative">
              <SliderMarkers max={ACCOUNTS_MAX} step={200000} />
              <input type="range" min="5000" max={ACCOUNTS_MAX} step="5000" value={Math.min(numAccounts, ACCOUNTS_MAX)} onChange={e => {
    setNumAccounts(Number.parseInt(e.target.value));
    setShowCustomAccounts(false);
  }} className="relative w-full h-1.5 bg-black/[0.03] dark:bg-white/20 rounded-full appearance-none cursor-pointer backdrop-blur-sm z-10" />
            </div>
          </div>
        </div>

        {}
        <div className="grid grid-cols-3 gap-3">
          <div className="p-4 bg-black/[0.015] dark:bg-white/5 backdrop-blur-md rounded-2xl text-center border border-black/[0.04] dark:border-white/10 shadow-sm">
            <div className="text-xs text-zinc-500 dark:text-white/50 mb-1 uppercase tracking-wide">
              SPL Token
            </div>
            <div className="text-xl font-mono font-semibold text-zinc-900 dark:text-white">
              {formatSOL(solanaCost)}
            </div>
            <div className="text-xs text-zinc-400 dark:text-white/40">SOL</div>
          </div>

          <div className="p-4 bg-black/[0.015] dark:bg-white/5 backdrop-blur-md rounded-2xl text-center border border-black/[0.04] dark:border-white/10 shadow-sm">
            <div className="text-xs text-zinc-500 dark:text-white/50 mb-1 uppercase tracking-wide">
              Compressed
            </div>
            <div className="text-xl font-mono font-semibold text-zinc-900 dark:text-white">
              {formatSOL(compressedCost)}
            </div>
            <div className="text-xs text-zinc-400 dark:text-white/40">SOL</div>
          </div>

          <div className="p-4 bg-black/[0.015] dark:bg-white/5 backdrop-blur-md rounded-2xl text-center border border-black/[0.04] dark:border-white/10 shadow-sm">
            <div className="text-xs text-zinc-500 dark:text-white/50 mb-1 uppercase tracking-wide">
              Savings
            </div>
            <div className="text-xl font-mono font-semibold text-zinc-900 dark:text-white">
              {savingsPercent}%
            </div>
            <div className="text-xs text-zinc-400 dark:text-white/40">{formatSOL(savings)} SOL</div>
          </div>
        </div>
      </div>
    </div>;
};

<TokenAccountCompressedVsSpl />

***

<Accordion title="Agent skill">
  Use the [airdrop](https://github.com/Lightprotocol/skills/tree/main/skills/airdrop) agent skill for token distribution and airdrops:

  ```
  npx skills add Lightprotocol/skills
  ```

  For orchestration, install the [general skill](https://zkcompression.com/skill.md):

  ```bash theme={null}
  npx skills add https://zkcompression.com
  ```
</Accordion>

<Tabs>
  <Tab title="Client Side Distribution">
    Choose your implementation based on your needs:

    | Tab                                   | Best For                   | What You'll Get                 | Time   |
    | :------------------------------------ | :------------------------- | :------------------------------ | :----- |
    | **Localnet Guide**                    | First-time users, learning | Step-by-step Localnet tutorial  | 20 min |
    | **Simple Airdrop**                    | \<10,000 recipients        | Production-ready single script  | 10 min |
    | **Airdrop with Batched Instructions** | 10,000+ recipients         | Batched system with retry logic | 15 min |

    <Info>
      Via Webapp you can use [Airship by Helius Labs](https://airship.helius.dev/) to airdrop to up to 200,000 recipients.
    </Info>

    <Tabs>
      <Tab title="Localnet Guide">
        <Check>
          **What you'll build:** A test airdrop sending compressed tokens to 3 recipients on your local validator.
        </Check>

        <Steps>
          <Step>
            ### Prerequisites

            Make sure you have dependencies and developer environment set up!

            <Info>
              System Requirements

              * **Node.js >= 20.18.0** (required by latest Solana packages)
              * npm or yarn package manager
            </Info>

            <Accordion title="Prerequisites & Setup">
              **Dependencies**

              <Tabs>
                <Tab title="npm">
                  ```bash theme={null}
                  npm install @lightprotocol/stateless.js@^0.23.0 \
                              @lightprotocol/compressed-token@^0.23.0
                  ```
                </Tab>

                <Tab title="yarn">
                  ```bash theme={null}
                  yarn add @lightprotocol/stateless.js@^0.23.0 \
                           @lightprotocol/compressed-token@^0.23.0
                  ```
                </Tab>

                <Tab title="pnpm">
                  ```bash theme={null}
                  pnpm add @lightprotocol/stateless.js@^0.23.0 \
                           @lightprotocol/compressed-token@^0.23.0
                  ```
                </Tab>

                <Tab title="SDK 2.0 (token-interface)">
                  ```bash theme={null}
                  # npm
                  npm install @lightprotocol/stateless.js@^0.23.0 \
                              @lightprotocol/token-interface@^0.1.2

                  # yarn
                  yarn add @lightprotocol/stateless.js@^0.23.0 \
                           @lightprotocol/token-interface@^0.1.2

                  # pnpm
                  pnpm add @lightprotocol/stateless.js@^0.23.0 \
                           @lightprotocol/token-interface@^0.1.2
                  ```
                </Tab>
              </Tabs>

              **Developer Environment**

              <Tabs>
                <Tab title="Localnet">
                  By default, all guides use Localnet.

                  <Tabs>
                    <Tab title="npm">
                      ```bash theme={null}
                      npm install -g @lightprotocol/zk-compression-cli
                      ```
                    </Tab>

                    <Tab title="yarn">
                      ```bash theme={null}
                      yarn global add @lightprotocol/zk-compression-cli
                      ```
                    </Tab>

                    <Tab title="pnpm">
                      ```bash theme={null}
                      pnpm add -g @lightprotocol/zk-compression-cli
                      ```
                    </Tab>
                  </Tabs>

                  ```bash theme={null}
                  # Start a local test validator
                  light test-validator

                  ## ensure you have the Solana CLI accessible in your system PATH
                  ```

                  ```typescript theme={null}
                  // 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();
                  ```
                </Tab>

                <Tab title="Devnet">
                  Replace `<your-api-key>` with your actual API key. [Get your API key here](https://www.helius.dev/zk-compression), if you don't have one yet.

                  ```typescript theme={null}
                  import { createRpc } from "@lightprotocol/stateless.js";

                  const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=<your_api_key>";
                  const connection = createRpc(RPC_ENDPOINT);

                  async function main() {
                    let slot = await connection.getSlot();
                    console.log(slot);

                    let health = await connection.getIndexerHealth(slot);
                    console.log(health);
                    // "Ok"
                  }

                  main();
                  ```
                </Tab>
              </Tabs>
            </Accordion>
          </Step>

          <Step>
            ### Mint SPL tokens to your wallet

            Run this `mint-spl-tokens.ts` to mint SPL tokens to your wallet.

            ```typescript mint-spl-tokens.ts expandable theme={null}
            // Mint SPL Tokens for Airdrop - LocalNet
            // 1. Load wallet and connect to local validator
            // 2. Create SPL mint with SPL interface via createMint()
            // 3. Create ATA and mint SPL tokens to sender for airdrop preparation
            // 4. Output mint address for use in simple-airdrop.ts

            import { Keypair } from "@solana/web3.js";
            import { createRpc } from "@lightprotocol/stateless.js";
            import {
              createMint,
              getOrCreateAssociatedTokenAccount,
              mintTo,
            } from "@solana/spl-token";
            import { createSplInterface } from "@lightprotocol/compressed-token";
            import * as fs from 'fs';
            import * as os from 'os';

            // Step 1: Setup local connection and load wallet
            const connection = createRpc(); // defaults to localhost:8899

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

            (async () => {
              // Step 2: Create SPL mint with SPL interface
              const mint = await createMint(connection, payer, payer.publicKey, null, 9);
              const poolTxId = await createSplInterface(connection, payer, mint);
              console.log(`Mint address: ${mint.toBase58()}`);
              console.log(`SPL interface created: ${poolTxId}`);

              // Step 3: Create associated token account for sender
              // The sender will send tokens from this account to the recipients as compressed tokens.
              const ata = await getOrCreateAssociatedTokenAccount(
                connection,
                payer,
                mint, // SPL mint with SPL interface for compression
                payer.publicKey
              );
              console.log(`ATA address: ${ata.address.toBase58()}`);

              // Step 4: Mint SPL tokens to ATA.
              // The sender will send tokens from this account to the recipients as compressed tokens.
              const mintToTxId = await mintTo(
                connection,
                payer,
                mint, // SPL mint with SPL interface for compression
                ata.address, // distributor ATA
                payer.publicKey,
                100_000_000_000 // amount: 100 tokens with 9 decimals
              );
              console.log(`\nSPL tokens minted and ready for distribution!`);
              console.log(`Transaction: ${mintToTxId}`);

              console.log(`\nCopy mint address to your airdrop script: ${mint.toBase58()}`);
            })();
            ```
          </Step>

          <Step>
            ### Execute the Airdrop

            Next, distribute the SPL tokens to all recipients.

            <Warning>
              Ensure you have the latest `@lightprotocol/stateless.js` and `@lightprotocol/compressed-token` versions `≥ 0.21.0`!
            </Warning>

            ```typescript simple-airdrop.ts expandable highlight={29-30,45,74,100} theme={null}
            // Simple Airdrop - LocalNet
            // 1. Load wallet and select compression infrastructure with getStateTreeInfos() and getTokenPoolInfos()
            // 2. Build LightTokenProgram.compress() instruction for multiple recipients in one transaction
            // 3. Execute transaction with compute budget and confirm compression operation with sendAndConfirmTx()
            // 4. Verify distribution via getCompressedTokenAccountsByOwner

            import { Keypair, PublicKey, ComputeBudgetProgram } from "@solana/web3.js";
            import {
              LightTokenProgram,
              getTokenPoolInfos,
              selectTokenPoolInfo,
            } from "@lightprotocol/compressed-token";
            import {
              bn,
              buildAndSignTx,
              calculateComputeUnitPrice,
              createRpc,
              dedupeSigner,
              Rpc,
              selectStateTreeInfo,
              sendAndConfirmTx,
            } from "@lightprotocol/stateless.js";
            import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
            import * as fs from 'fs';
            import * as os from 'os';

            // Step 1: Setup local connection and load wallet
            const connection: Rpc = createRpc(); // defaults to localhost:8899
            const mint = new PublicKey("MINTADDRESS"); // Replace with mint address from mint-spl-tokens.ts
            // Local uses file wallet. Use constants from .env file in production
            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;

            (async () => {
              // Step 2: Select state tree and SPL interface
              const activeStateTrees = await connection.getStateTreeInfos();
              const treeInfo = selectStateTreeInfo(activeStateTrees);

              const infos = await getTokenPoolInfos(connection, mint);
              const info = selectTokenPoolInfo(infos);

              // Step 3: Get or create source token account for distribution
              // The sender will send tokens from this account to the recipients as compressed tokens.
              const sourceTokenAccount = await getOrCreateAssociatedTokenAccount(
                connection,
                payer,
                mint, // SPL mint with SPL interface for compression
                payer.publicKey
              );

              // Step 4: Define airdrop recipients and amounts
              const airDropAddresses = [
                Keypair.generate().publicKey,
                Keypair.generate().publicKey,
                Keypair.generate().publicKey,
              ];

              const amounts = [
                bn(20_000_000_000), // 20 tokens
                bn(30_000_000_000), // 30 tokens
                bn(40_000_000_000), // 40 tokens
              ];

              const totalAmount = amounts.reduce((sum, amt) => sum + amt.toNumber(), 0);
              console.log(`Distributing ${totalAmount / 1e9} compressed tokens to ${airDropAddresses.length} recipients`);

              const initialSplBalance = await connection.getTokenAccountBalance(sourceTokenAccount.address);
              console.log(`Sender initial balance: ${initialSplBalance.value.uiAmount} tokens`);

              // Step 5: Build transaction with compute budget and compression instruction
              const instructions = [];
              // Set compute unit limits based on recipient count (estimated 120k CU per recipient)
              instructions.push(
                ComputeBudgetProgram.setComputeUnitLimit({ units: 120_000 * airDropAddresses.length }),
                ComputeBudgetProgram.setComputeUnitPrice({
                  microLamports: calculateComputeUnitPrice(20_000, 120_000 * airDropAddresses.length), // dynamic priority fee
                })
              );

              // Create compression instruction for multiple recipients in one transaction
              const compressInstruction = await LightTokenProgram.compress({
                payer: payer.publicKey,
                owner: owner.publicKey,
                source: sourceTokenAccount.address, // source ATA holding SPL tokens
                toAddress: airDropAddresses, // recipient addresses for compressed tokens
                amount: amounts, // different amounts for each recipient
                mint, // SPL mint with SPL interface for compression
                tokenPoolInfo: info,
                outputStateTreeInfo: treeInfo, // destination state tree
              });
              instructions.push(compressInstruction);

              // Step 6: Sign and send transaction
              const additionalSigners = dedupeSigner(payer, [owner]);
              const { blockhash } = await connection.getLatestBlockhash();
              const tx = buildAndSignTx(instructions, payer, blockhash, additionalSigners);

              // For production: Add address lookup table to reduce transaction size and fees
              // const lookupTableAddress = new PublicKey("9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ"); // mainnet // or "qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V" for devnet
              // const lookupTableAccount = (await connection.getAddressLookupTable(lookupTableAddress)).value!;
              // const tx = buildAndSignTx(instructions, payer, blockhash, additionalSigners, [lookupTableAccount]);
              const txId = await sendAndConfirmTx(connection, tx);

              console.log(`\nAirdrop completed!`);
              console.log(`Transaction: ${txId}`);

              // Step 7: Verify distribution via getCompressedTokenAccountsByOwner
              for (let i = 0; i &#x3C; airDropAddresses.length; i++) {
                const recipientAccounts = await connection.getCompressedTokenAccountsByOwner(airDropAddresses[i], { mint });
                const balance = recipientAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0);
                console.log(`Recipient ${i + 1} (${airDropAddresses[i].toString()}): ${balance / 1e9} compressed tokens`);
              }

              const finalSplBalance = await connection.getTokenAccountBalance(sourceTokenAccount.address);
              console.log(`\nSender balance after airdrop: ${finalSplBalance.value.uiAmount} SPL tokens`);

              return txId;
            })();
            ```
          </Step>
        </Steps>
      </Tab>

      <Tab title="Simple Airdrop">
        <Check>
          For small airdrops (\<10,000 recipients). [View the source code here](https://github.com/Lightprotocol/example-token-distribution/blob/main/src/simple-airdrop/simple-airdrop.ts).
        </Check>

        <Steps>
          <Step>
            ### Environment Setup

            Configure your environment variables:

            ```bash .env theme={null}
            PAYER_KEYPAIR=YOUR_BASE58_ENCODED_PRIVATE_KEY
            MINT_ADDRESS=YOUR_MINT_ADDRESS
            ```
          </Step>

          <Step>
            ### Mint SPL Tokens

            Use your existing mint or mint SPL tokens to your wallet for distribution.

            ```typescript expandable theme={null}
            import { Rpc, createRpc } from '@lightprotocol/stateless.js';
            import { createMint } from '@lightprotocol/compressed-token';
            import {
                getOrCreateAssociatedTokenAccount,
                mintTo as mintToSpl,
            } from '@solana/spl-token';
            import { PAYER_KEYPAIR, RPC_ENDPOINT } from '../constants';

            const payer = PAYER_KEYPAIR;
            const connection: Rpc = createRpc(RPC_ENDPOINT);
            const decimals = 9;
            const mintAmount = 100;

            (async () => {
                // airdrop lamports to pay tx fees
                // await confirmTx(
                //   connection,
                //   await connection.requestAirdrop(payer.publicKey, 1e7)
                // );

                const { mint, transactionSignature } = await createMint(
                    connection,
                    payer,
                    payer.publicKey,
                    decimals,
                );
                console.log(
                    `create-mint success! txId: ${transactionSignature}, mint: ${mint.toBase58()}`,
                );

                const ata = await getOrCreateAssociatedTokenAccount(
                    connection,
                    payer,
                    mint,
                    payer.publicKey,
                );
                console.log(`ata: ${ata.address}`);

                const mintTxId = await mintToSpl(
                    connection,
                    payer,
                    mint,
                    ata.address,
                    payer.publicKey,
                    mintAmount,
                );
                console.log(`mint-spl success! txId: ${mintTxId}`);
            })();
            ```
          </Step>

          <Step>
            ### Execute Airdrop

            Run the airdrop script with your configured environment:

            ```typescript expandable theme={null}
            // 1. Load environment and select compression infrastructure with getStateTreeInfos() and getTokenPoolInfos()
            // 2. Build LightTokenProgram.compress() instruction for multiple recipients in one transaction
            // 3. Execute transaction with compute budget, address lookup table, and confirm with sendAndConfirmTx()
            // 4. Verify distribution via getCompressedTokenAccountsByOwner

            import {
                PublicKey,
                TransactionInstruction,
                ComputeBudgetProgram,
            } from '@solana/web3.js';
            import {
                LightTokenProgram,
                getTokenPoolInfos,
                selectTokenPoolInfo,
            } from '@lightprotocol/compressed-token';
            import {
                bn,
                buildAndSignTx,
                calculateComputeUnitPrice,
                createRpc,
                dedupeSigner,
                Rpc,
                selectStateTreeInfo,
                sendAndConfirmTx,
            } from '@lightprotocol/stateless.js';
            import { getOrCreateAssociatedTokenAccount } from '@solana/spl-token';
            import { MINT_ADDRESS, PAYER_KEYPAIR, RPC_ENDPOINT } from '../constants';

            (async () => {
                const connection: Rpc = createRpc(RPC_ENDPOINT);
                const mintAddress = MINT_ADDRESS;
                const payer = PAYER_KEYPAIR;
                const owner = payer;
                const recipients = [
                    PublicKey.default,
                    // ...
                ];

                // 1. Select a state tree
                const treeInfos = await connection.getStateTreeInfos(); // Fixed: removed deprecated getCachedActiveStateTreeInfos
                const treeInfo = selectStateTreeInfo(treeInfos);

                // 2. Select SPL interface
                const tokenPoolInfos = await getTokenPoolInfos(connection, mintAddress);
                const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos);

                // Create an SPL token account for the sender.
                // The sender will send tokens from this account to the recipients as compressed tokens.
                const sourceTokenAccount = await getOrCreateAssociatedTokenAccount(
                    connection,
                    payer,
                    mintAddress,
                    payer.publicKey,
                );

                // 1 recipient = 120_000 CU
                // 5 recipients = 170_000 CU
                const units = 120_000;
                const amount = bn(333);
                // To land faster, replace this with a dynamic fee based on network
                // conditions.
                const microLamports = calculateComputeUnitPrice(20_000, units);

                const instructions: TransactionInstruction[] = [
                    ComputeBudgetProgram.setComputeUnitLimit({ units }),
                    ComputeBudgetProgram.setComputeUnitPrice({
                        microLamports,
                    }),
                ];

                const compressInstruction = await LightTokenProgram.compress({
                    payer: payer.publicKey,
                    owner: owner.publicKey,
                    source: sourceTokenAccount.address,
                    toAddress: recipients,
                    amount: recipients.map(() => amount),
                    mint: mintAddress,
                    outputStateTreeInfo: treeInfo,
                    tokenPoolInfo,
                });
                instructions.push(compressInstruction);

                // https://www.zkcompression.com/developers/protocol-addresses-and-urls#lookup-tables
                const lookupTableAddress = new PublicKey(
                    '9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ', // mainnet
                    // "qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V" // devnet
                );

                // Get the lookup table account state
                const lookupTableAccount = (
                    await connection.getAddressLookupTable(lookupTableAddress)
                ).value!;

                const additionalSigners = dedupeSigner(payer, [owner]);

                const { blockhash } = await connection.getLatestBlockhash();

                const tx = buildAndSignTx(
                    instructions,
                    payer,
                    blockhash,
                    additionalSigners,
                    [lookupTableAccount],
                );

                const txId = await sendAndConfirmTx(connection, tx);
                console.log(`txId: ${txId}`);
            })();
            ```
          </Step>
        </Steps>
      </Tab>

      <Tab title="Airdrop with Batched Instructions">
        <Check>
          For large-scale airdrops (10,000+ recipients) we recommend batched instructions.\
          [View the source code here.](https://github.com/Lightprotocol/example-token-distribution/tree/main/src/optimized-airdrop)
        </Check>

        <Info>
          This implementation creates the mint and executes the airdrop in a single operation. You only need to configure your RPC endpoint and payer keypair.
        </Info>

        <Steps>
          <Step>
            ### Create Instructions

            Process recipients in chunks and create batched instructions with optimized compute limits.

            ```typescript create-instructions.ts expandable theme={null}

            // 1. Process recipients in chunks with selectStateTreeInfo() and selectTokenPoolInfo() per batch
            // 2. Create LightTokenProgram.compress() instructions with ComputeBudgetProgram limits for multiple recipients
            // 3. Return batched instructions for optimized large-scale airdrop execution

            import {
              LightTokenProgram,
              TokenPoolInfo,
            } from "@lightprotocol/compressed-token";
            import {
              bn,
              selectStateTreeInfo,
              StateTreeInfo,
            } from "@lightprotocol/stateless.js";
            import {
              ComputeBudgetProgram,
              TransactionInstruction,
              PublicKey,
            } from "@solana/web3.js";

            interface CreateAirdropInstructionsParams {
              amount: number | bigint;
              recipients: PublicKey[];
              payer: PublicKey;
              sourceTokenAccount: PublicKey;
              mint: PublicKey;
              stateTreeInfos: StateTreeInfo[];
              tokenPoolInfos: TokenPoolInfo[];
              maxRecipientsPerInstruction?: number;
              maxInstructionsPerTransaction?: number;
              computeUnitLimit?: number;
              computeUnitPrice?: number | undefined;
            }

            export type InstructionBatch = TransactionInstruction[];

            export async function createAirdropInstructions({
              amount,
              recipients,
              payer,
              sourceTokenAccount,
              mint,
              stateTreeInfos,
              tokenPoolInfos,
              maxRecipientsPerInstruction = 5,
              maxInstructionsPerTransaction = 3,
              computeUnitLimit = 500_000,
              computeUnitPrice = undefined,
            }: CreateAirdropInstructionsParams): Promise<InstructionBatch[]> {
              const instructionBatches: InstructionBatch[] = [];
              const amountBn = bn(amount.toString());

              // Process recipients in chunks
              for (
                let i = 0;
                i < recipients.length;
                i += maxRecipientsPerInstruction * maxInstructionsPerTransaction
              ) {
                const instructions: TransactionInstruction[] = [];

                instructions.push(
                  ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit })
                );
                if (computeUnitPrice) {
                  instructions.push(
                    ComputeBudgetProgram.setComputeUnitPrice({
                      microLamports: computeUnitPrice,
                    })
                  );
                }

                const treeInfo = selectStateTreeInfo(stateTreeInfos);
                const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos);

                for (let j = 0; j < maxInstructionsPerTransaction; j++) {
                  const startIdx = i + j * maxRecipientsPerInstruction;
                  const recipientBatch = recipients.slice(
                    startIdx,
                    startIdx + maxRecipientsPerInstruction
                  );

                  if (recipientBatch.length === 0) break;

                  const compressIx = await LightTokenProgram.compress({
                    payer,
                    owner: payer,
                    source: sourceTokenAccount,
                    toAddress: recipientBatch,
                    amount: recipientBatch.map(() => amountBn),
                    mint,
                    tokenPoolInfo,
                    outputStateTreeInfo: treeInfo,
                  });

                  instructions.push(compressIx);
                }

                if (
                  (computeUnitPrice && instructions.length > 2) ||
                  (!computeUnitPrice && instructions.length > 1)
                ) {
                  instructionBatches.push(instructions);
                }
              }

              return instructionBatches;
            }
            ```
          </Step>

          <Step>
            ### Update Blockhash

            Maintain fresh blockhashes with background refresh loop using `getLatestBlockhash()` every 30 seconds.

            ```typescript update-blockhash.ts expandable theme={null}
            import { Rpc } from "@lightprotocol/stateless.js";

            // 1. Fetch initial blockhash with getLatestBlockhash() and store in exported variable
            // 2. Set up background refresh loop with setTimeout() to update blockhash every 30 seconds
            // 3. Provide AbortSignal support to stop background updates when airdrop completes

            export let currentBlockhash: string;

            export async function updateBlockhash(
              connection: Rpc,
              signal: AbortSignal
            ): Promise<void> {
              try {
                const { blockhash } = await connection.getLatestBlockhash();
                currentBlockhash = blockhash;
                console.log(`Initial blockhash: ${currentBlockhash}`);
              } catch (error) {
                console.error("Failed to fetch initial blockhash:", error);
                return;
              }

              // Update blockhash in the background
              (function updateInBackground() {
                if (signal.aborted) return;
                const timeoutId = setTimeout(async () => {
                  if (signal.aborted) return;
                  try {
                    const { blockhash } = await connection.getLatestBlockhash();
                    currentBlockhash = blockhash;
                    console.log(`Updated blockhash: ${currentBlockhash}`);
                  } catch (error) {
                    console.error("Failed to update blockhash:", error);
                  }
                  updateInBackground();
                }, 30_000);

                signal.addEventListener("abort", () => clearTimeout(timeoutId));
              })();
            }
            ```
          </Step>

          <Step>
            ### Sign and Send

            Execute batched transactions with `VersionedTransaction`, retry logic, and `sendAndConfirmTx()` confirmation.

            ```typescript sign-and-send.ts expandable theme={null}

            // 1. Initialize blockhash updates with updateBlockhash() and get address lookup table with getAddressLookupTable()
            // 2. Process instruction batches with VersionedTransaction and retry logic for failed transactions
            // 3. Yield batch results with sendAndConfirmTx() confirmation and comprehensive error handling

            import { Rpc, sendAndConfirmTx } from "@lightprotocol/stateless.js";
            import {
              Keypair,
              PublicKey,
              TransactionMessage,
              VersionedTransaction,
            } from "@solana/web3.js";
            import { InstructionBatch } from "./create-instructions";
            import { currentBlockhash, updateBlockhash } from "./update-blockhash";
            import bs58 from "bs58";

            export enum BatchResultType {
              Success = "success",
              Error = "error",
            }

            export type BatchResult =
              | { type: BatchResultType.Success; index: number; signature: string }
              | { type: BatchResultType.Error; index: number; error: string };

            export async function* signAndSendAirdropBatches(
              batches: InstructionBatch[],
              payer: Keypair,
              connection: Rpc,
              maxRetries = 3
            ): AsyncGenerator<BatchResult> {
              const abortController = new AbortController();
              const { signal } = abortController;

              await updateBlockhash(connection, signal);

              const statusMap = new Array(batches.length).fill(0); // Initialize all as pending (0)

              // Use zk-compression look up table for your network
              // https://www.zkcompression.com/developers/protocol-addresses-and-urls#lookup-tables
              const lookupTableAddress = new PublicKey(
                "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ"
              );

              // Get the lookup table account
              const lookupTableAccount = (
                await connection.getAddressLookupTable(lookupTableAddress)
              ).value!;

              while (statusMap.includes(0)) {
                // Continue until all are confirmed or errored
                const pendingBatches = statusMap.filter((status) => status === 0).length;
                console.log(`Sending ${pendingBatches} transactions`);

                const sends = statusMap.map(async (status, index) => {
                  if (status !== 0) return; // Skip non-pending batches

                  let retries = 0;
                  while (retries < maxRetries && statusMap[index] === 0) {
                    if (!currentBlockhash) {
                      console.warn("Waiting for blockhash to be set...");
                      await new Promise((resolve) => setTimeout(resolve, 1000));
                      continue;
                    }

                    try {
                      const tx = new VersionedTransaction(
                        new TransactionMessage({
                          payerKey: payer.publicKey,
                          recentBlockhash: currentBlockhash,
                          instructions: batches[index],
                        }).compileToV0Message([lookupTableAccount])
                      );
                      tx.sign([payer]);

                      const sig = bs58.encode(tx.signatures[0]);
                      console.log(`Batch ${index} signature: ${sig}`);

                      const confirmedSig = await sendAndConfirmTx(connection, tx, {
                        skipPreflight: true,
                        commitment: "confirmed",
                      });

                      if (confirmedSig) {
                        statusMap[index] = 1; // Mark as confirmed
                        return {
                          type: BatchResultType.Success,
                          index,
                          signature: confirmedSig,
                        };
                      }
                    } catch (e) {
                      retries++;
                      console.warn(`Retrying batch ${index}, attempt ${retries + 1}`);
                      if (retries >= maxRetries) {
                        statusMap[index] = `err: ${(e as Error).message}`; // Mark as error
                        return {
                          type: BatchResultType.Error,
                          index,
                          error: (e as Error).message,
                        };
                      }
                    }
                  }
                });

                const results = await Promise.all(sends);
                for (const result of results) {
                  if (result) yield result as BatchResult;
                }
              }

              // Stop the blockhash update loop
              abortController.abort();
            }
            ```
          </Step>

          <Step>
            ### Main Airdrop File

            Put it all together in the main airdrop file.

            ```typescript airdrop.ts expandable theme={null}
            // 1. Create compressed mint with createMint(), mint supply with mintTo(), get infrastructure with getStateTreeInfos() and getTokenPoolInfos() (SPL interface infos)
            // 2. Generate batched compression instructions with createAirdropInstructions() - create LightTokenProgram.compress() calls
            // 3. Execute batched airdrop with signAndSendAirdropBatches() - sign transactions and confirm with sendAndConfirmTx() for large-scale distribution

            import { Keypair, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
            import {
              calculateComputeUnitPrice,
              createRpc,
              Rpc,
            } from "@lightprotocol/stateless.js";
            import { createMint, getTokenPoolInfos } from "@lightprotocol/compressed-token";
            import { getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token";
            import { createAirdropInstructions } from "./create-instructions";
            import { BatchResultType, signAndSendAirdropBatches } from "./sign-and-send";
            import dotenv from "dotenv";
            import bs58 from "bs58";
            dotenv.config();

            // Step 1: Setup environment and RPC connection
            const RPC_ENDPOINT = `https://mainnet.helius-rpc.com?api-key=${process.env.HELIUS_API_KEY}`;
            const connection: Rpc = createRpc(RPC_ENDPOINT);
            const PAYER = Keypair.fromSecretKey(bs58.decode(process.env.PAYER_KEYPAIR!));

            // Step 2: Define airdrop recipient list (20 example addresses)
            const recipients = [
              "GMPWaPPrCeZPse5kwSR3WUrqYAPrVZBSVwymqh7auNW7",
              "GySGrTgPtPfMtYoYTmwUdUDFwVJbFMfip7QZdhgXp8dy",
              "Bk1r2vcgX2uTzwV3AUyfRbSfGKktoQrQufBSrHzere74",
              "8BvkadZ6ycFNmQF7S1MHRvEVNb1wvDBFdjkAUnxjK9Ug",
              "EmxcvFKXsWLzUho8AhV9LCKeKRFHg5gAs4sKNJwhe5PF",
              "6mqdHkSpcvNexmECjp5XLt9V9KnSQre9TvbMLGr6sEPM",
              "3k4MViTWXBjFvoUZiJcNGPvzrqnTa41gcrbWCMMnV6ys",
              "2k6BfYRUZQHquPtpkyJpUx3DzM7W3K6H95igtJk8ztpd",
              "89jPyNNLCcqWn1RZThSS4jSqU5VCJkR5mAaSaVzuuqH4",
              "3MzSRLf9jSt6d1MFFMMtPfUcDY6XziRxTB8C5mfvgxXG",
              "9A1H6f3N8mpAPSdfqvYRD4cM1NwDZoMe3yF5DwibL2R2",
              "PtUAhLvUsVcoesDacw198SsnMoFNVskR5pT3QvsBSQw",
              "6C6W6WpgFK8TzTTMNCPMz2t9RaMs4XnkfB6jotrWWzYJ",
              "8sLy9Jy8WSh6boq9xgDeBaTznn1wb1uFpyXphG3oNjL5",
              "GTsQu2XCgkUczigdBFTWKrdDgNKLs885jKguyhkqdPgV",
              "85UK4bjC71Jwpyn8mPSaW3oYyEAiHPbESByq9s5wLcke",
              "9aEJT4CYHEUWwwSQwueZc9EUjhWSLD6AAbpVmmKDeP7H",
              "CY8QjRio1zd9bYWMKiVRrDbwVenf3JzsGf5km5zLgY9n",
              "CeHbdxgYifYhpB6sXGonKzmaejqEfq2ym5utTmB6XMVv",
              "4z1qss12DjUzGUkK1fFesqrUwrEVJJvzPMNkwqYnbAR5",
            ].map((address) => new PublicKey(address));

            (async () => {
              // Step 3: Create compressed mint and register for compression
              // 3a: Call createMint() to initialize mint with compression pool
              const { mint, transactionSignature } = await createMint(
                connection,
                PAYER, // fee payer
                PAYER.publicKey, // mint authority
                9 // decimals
              );
              console.log(
                `create-mint success! txId: ${transactionSignature}, mint: ${mint.toBase58()}`
              );

              // Step 4: Create associated token account for distributor
              // 4a: Ensure PAYER has ATA for holding tokens before compression
              const ata = await getOrCreateAssociatedTokenAccount(
                connection,
                PAYER, // fee payer
                mint, // token mint
                PAYER.publicKey // token owner
              );
              console.log(`ATA: ${ata.address.toBase58()}`);

              // Step 5: Mint initial token supply to distributor
              // 5a: Create 10 billion tokens in the ATA for airdrop distribution
              const mintToTxId = await mintTo(
                connection,
                PAYER, // fee payer and mint authority
                mint, // token mint
                ata.address, // destination ATA
                PAYER.publicKey, // mint authority
                10e9 * LAMPORTS_PER_SOL // amount: 10 billion tokens with decimals
              );
              console.log(`mint-to success! txId: ${mintToTxId}`);

              // Step 6: Get compression infrastructure for batch operations
              // 6a: Fetch available state trees for compressed account storage
              const stateTreeInfos = await connection.getStateTreeInfos();

              // 6b: Get SPL interface infos for compression
              const tokenPoolInfos = await getTokenPoolInfos(connection, mint);

              // Step 7: Create instruction batches for large-scale airdrop
              // 7a: Generate batched compression instructions with compute optimization
              const instructionBatches = await createAirdropInstructions({
                amount: 1e6, // 1 million tokens per recipient
                recipients, // array of recipient addresses
                payer: PAYER.publicKey, // transaction fee payer
                sourceTokenAccount: ata.address, // source ATA holding SPL tokens
                mint, // token mint
                stateTreeInfos, // state trees for compressed accounts
                tokenPoolInfos,
                computeUnitPrice: calculateComputeUnitPrice(10_000, 500_000), // dynamic priority fee
              });

              // Step 8: Execute batched airdrop with error handling
              // 8a: Process instruction batches with retry logic and confirmation
              for await (const result of signAndSendAirdropBatches(
                instructionBatches,
                PAYER,
                connection
              )) {
                if (result.type === BatchResultType.Success) {
                  console.log(`Batch ${result.index} confirmed: ${result.signature}`);
                } else if (result.type === BatchResultType.Error) {
                  console.log(`Batch ${result.index} failed: ${result.error}`);
                  // Use result.index to access the specific batch in instructionBatches
                  const failedBatch = instructionBatches[result.index];
                  console.log(`Failed batch instructions:`, failedBatch);
                  // Additional logic to handle failed instructions
                }
              }

              console.log("Airdrop process complete.");
            })();
            ```
          </Step>
        </Steps>
      </Tab>

      <Tab title="AI Prompt">
        <Prompt description="Distribute compressed tokens via airdrop" actions={["copy", "cursor"]}>
          {`---
                    description: Distribute compressed tokens via airdrop
                    allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
                    ---

                    ## Distribute compressed tokens via airdrop

                    Context:
                    - Guide: https://zkcompression.com/token-distribution
                    - Skills and resources index: https://zkcompression.com/skill.md
                    - Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/airdrop
                    - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js, @solana/spl-token
                    - Example repo: https://github.com/Lightprotocol/example-token-distribution
                    - Webapp alternative: https://airship.helius.dev/ (Airship by Helius Labs, up to 200k recipients)

                    Key APIs: LightTokenProgram.compress(), getTokenPoolInfos(), selectTokenPoolInfo(), getStateTreeInfos(), selectStateTreeInfo(), buildAndSignTx(), sendAndConfirmTx()

                    ### 1. Index project
                    - Grep \`LightTokenProgram|compress|getTokenPoolInfos|selectTokenPoolInfo|getStateTreeInfos|@lightprotocol|airdrop|distribution\` across src/
                    - Glob \`**/*.ts\` for project structure
                    - Identify: existing airdrop/distribution logic, token minting setup, recipient list format
                    - 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 all three tabs (Localnet Guide, Simple Airdrop, Batched)
                    - 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 scale? (<10k recipients = simple airdrop, 10k+ = batched)
                    - AskUserQuestion: localnet testing first, or production deploy?
                    - AskUserQuestion: do you have an existing SPL mint, or need to create one?
                    - AskUserQuestion: do you need decompression/claim functionality, or just direct distribution?
                    - Summarize findings and wait for user confirmation before implementing

                    ### 4. Create plan
                    - Based on steps 1–3, draft an implementation plan
                    - For simple airdrop: create mint → mint SPL tokens → LightTokenProgram.compress() with recipients array
                    - For batched: create instruction batches → manage blockhash refresh → sign and send with retry logic
                    - Address lookup table needed for production (mainnet: 9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ)
                    - 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 @solana/spl-token\`
                    - Set up RPC: \`createRpc(RPC_ENDPOINT)\` with a ZK Compression endpoint (Helius, Triton)
                    - 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 or execute localnet test airdrop
                    - 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`}
        </Prompt>

        ```text theme={null}
        ---
        description: Distribute compressed tokens via airdrop
        allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
        ---

        ## Distribute compressed tokens via airdrop

        Context:
        - Guide: https://zkcompression.com/token-distribution
        - Skills and resources index: https://zkcompression.com/skill.md
        - Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/airdrop
        - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js, @solana/spl-token
        - Example repo: https://github.com/Lightprotocol/example-token-distribution
        - Webapp alternative: https://airship.helius.dev/ (Airship by Helius Labs, up to 200k recipients)

        Key APIs: LightTokenProgram.compress(), getTokenPoolInfos(), selectTokenPoolInfo(), getStateTreeInfos(), selectStateTreeInfo(), buildAndSignTx(), sendAndConfirmTx()

        ### 1. Index project
        - Grep `LightTokenProgram|compress|getTokenPoolInfos|selectTokenPoolInfo|getStateTreeInfos|@lightprotocol|airdrop|distribution` across src/
        - Glob `**/*.ts` for project structure
        - Identify: existing airdrop/distribution logic, token minting setup, recipient list format
        - 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 all three tabs (Localnet Guide, Simple Airdrop, Batched)
        - 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 scale? (<10k recipients = simple airdrop, 10k+ = batched)
        - AskUserQuestion: localnet testing first, or production deploy?
        - AskUserQuestion: do you have an existing SPL mint, or need to create one?
        - AskUserQuestion: do you need decompression/claim functionality, or just direct distribution?
        - Summarize findings and wait for user confirmation before implementing

        ### 4. Create plan
        - Based on steps 1–3, draft an implementation plan
        - For simple airdrop: create mint → mint SPL tokens → LightTokenProgram.compress() with recipients array
        - For batched: create instruction batches → manage blockhash refresh → sign and send with retry logic
        - Address lookup table needed for production (mainnet: 9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ)
        - 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 @solana/spl-token`
        - Set up RPC: `createRpc(RPC_ENDPOINT)` with a ZK Compression endpoint (Helius, Triton)
        - 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 or execute localnet test airdrop
        - 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
        ```
      </Tab>
    </Tabs>

    # Advanced Features

    <Info>
      Solana Wallets like Phantom and Backpack already support compressed tokens.
      Still, you can let users decompress to SPL via your Frontend to customize claims.
    </Info>

    Add decompression of SPL Tokens with this script.

    ```typescript expandable theme={null}
    import {
      bn,
      buildAndSignTx,
      sendAndConfirmTx,
      dedupeSigner,
      Rpc,
      createRpc,
    } from "@lightprotocol/stateless.js";
    import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js";
    import {
      LightTokenProgram,
      getTokenPoolInfos,
      selectMinCompressedTokenAccountsForTransfer,
      selectTokenPoolInfosForDecompression,
    } from "@lightprotocol/compressed-token";
    import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
    import bs58 from "bs58";
    import dotenv from "dotenv";
    dotenv.config();

    // Set these values in your .env file
    const RPC_ENDPOINT = process.env.RPC_ENDPOINT;
    const mint = new PublicKey(process.env.MINT_ADDRESS!);
    const payer = Keypair.fromSecretKey(bs58.decode(process.env.PAYER_KEYPAIR!));

    const owner = payer;
    const amount = 1e5;
    const connection: Rpc = createRpc(RPC_ENDPOINT);

    (async () => {
      // 1. Create an associated token account for the user if it doesn't exist
      const ata = await getOrCreateAssociatedTokenAccount(
        connection,
        payer,
        mint,
        payer.publicKey
      );

      // 2. Fetch compressed token accounts
      const compressedTokenAccounts =
        await connection.getCompressedTokenAccountsByOwner(owner.publicKey, {
          mint,
        });

      // 3. Select
      const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
        compressedTokenAccounts.items,
        bn(amount)
      );

      // 4. Fetch validity proof
      const proof = await connection.getValidityProof(
        inputAccounts.map((account) => account.compressedAccount.hash)
      );

      // 5. Fetch SPL interface infos
      const tokenPoolInfos = await getTokenPoolInfos(connection, mint);

      // 6. Select
      const selectedTokenPoolInfos = selectTokenPoolInfosForDecompression(
        tokenPoolInfos,
        amount
      );

      // 7. Build instruction
      const ix = await LightTokenProgram.decompress({
        payer: payer.publicKey,
        inputCompressedTokenAccounts: inputAccounts,
        toAddress: ata.address,
        amount,
        tokenPoolInfos: selectedTokenPoolInfos,
        recentInputStateRootIndices: proof.rootIndices,
        recentValidityProof: proof.compressedProof,
      });

      // 8. Sign, send, and confirm
      const { blockhash } = await connection.getLatestBlockhash();
      const additionalSigners = dedupeSigner(payer, [owner]);
      const signedTx = buildAndSignTx(
        [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix],
        payer,
        blockhash,
        additionalSigners
      );
      return await sendAndConfirmTx(connection, signedTx);
    })();

    ```

    <Tip>
      Set priority fees dynamically for decompression. Learn more [here](https://docs.helius.dev/guides/sending-transactions-on-solana#summary).
    </Tip>

    ### Native Swap via Jup-API

    If you have a custom FE, you can let users swap compressed tokens using the Jup-API. A reference implementation is available [here](https://github.com/Lightprotocol/example-jupiter-swap-node).

    ## Next Steps

    <Card title="Create an airdrop with claim functionality." icon="chevron-right" color="#0066ff" href="#claim-reference-implementations" horizontal />
  </Tab>

  <Tab title="Claim Reference Implementations">
    Customize token distribution and let users claim.

    |                | distributor    | simple-claim    |
    | -------------- | -------------- | --------------- |
    | Vesting        | Linear Vesting | Cliff at Slot X |
    | Partial claims | Yes            | No              |
    | Clawback       | Yes            | No              |
    | Frontend       | REST API + CLI | None            |

    <Info>
      The programs are reference implementations and should be audited before production use.
    </Info>

    <CardGroup>
      <Card title="Merkle Distributor" icon="parachute-box" color="#0066ff" href="https://github.com/Lightprotocol/distributor" horizontal>
        Merkle distributor based on jito airdrop and optimized with rent-free PDAs.
      </Card>

      <Card title="Simple Airdrop with Claim" icon="hand-holding" color="#0066ff" href="https://github.com/Lightprotocol/program-examples/tree/main/airdrop-implementations/simple-claim" horizontal>
        Simple SPL token airdrop with claim.
      </Card>
    </CardGroup>
  </Tab>
</Tabs>
