> ## 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.

# Sign with Privy

> Integrate light-token with Privy embedded wallets for rent-free token accounts and transfers.

| Creation Cost     |      Light Token |  SPL-Token |
| :---------------- | ---------------: | ---------: |
| **Token Account** | **0.000017 SOL** | 0.0029 SOL |

Privy handles user authentication and wallet management. You build transactions with light-token and Privy signs them:

1. Authenticate with Privy
2. Build unsigned transaction
3. Sign transaction using Privy's wallet provider
4. Send signed transaction to RPC

## What you will implement

<table>
  <thead>
    <tr>
      <th style={{ width: "20%" }} />

      <th>SPL</th>
      <th>Light</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>[**Transfer**](#full-code-examples)</td>
      <td>createTransferInstruction()</td>
      <td>createTransferInterfaceInstructions()</td>
    </tr>

    <tr>
      <td>[**Receive**](#full-code-examples)</td>
      <td>getOrCreateAssociatedTokenAccount()</td>
      <td>createLoadAtaInstructions()</td>
    </tr>

    <tr>
      <td>[**Wrap from SPL**](#full-code-examples)</td>
      <td>N/A</td>
      <td>createWrapInstruction()</td>
    </tr>

    <tr>
      <td>[**Unwrap to SPL**](#full-code-examples)</td>
      <td>N/A</td>
      <td>createUnwrapInstructions()</td>
    </tr>

    <tr>
      <td>[**Get Balance**](#get-balances)</td>
      <td>getAccount()</td>
      <td>getAtaInterface()</td>
    </tr>

    <tr>
      <td>[**Tx History**](#get-transaction-history)</td>
      <td>getSignaturesForAddress()</td>
      <td>getSignaturesForOwnerInterface()</td>
    </tr>
  </tbody>
</table>

<Accordion title="Agent skill">
  Use the [payments](https://github.com/Lightprotocol/skills/tree/main/skills/payments) agent skill to add light-token payment support to your project:

  ```
  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="Guide">
    <Steps>
      <Step>
        ### Prerequisites

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

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

        Connect to an RPC endpoint that supports ZK Compression (Helius, Triton):

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

        import {
          createTransferInterfaceInstructions,
          createLoadAtaInstructions,
          createWrapInstruction,
          createUnwrapInstructions,
          getAssociatedTokenAddressInterface,
          getAtaInterface,
        } from "@lightprotocol/compressed-token/unified";

        const rpc = createRpc(RPC_ENDPOINT);
        ```
      </Step>

      <Step>
        ## Sign with Privy

        <Note>
          Find complete examples on GitHub: [Node.js](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-privy/nodejs) and [React](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-privy/react).
        </Note>

        <Tabs>
          <Tab title="Transfer">
            Transfer light-tokens between wallets. Auto-loads cold (compressed) light-token, SPL or
            Token-2022 balance before sending.

            <Info>
              **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.
            </Info>

            <Tabs>
              <Tab title="Node.js">
                ```typescript theme={null}
                import 'dotenv/config';
                import {PrivyClient} from '@privy-io/node';
                import {createRpc} from '@lightprotocol/stateless.js';
                import {PublicKey, Transaction} from '@solana/web3.js';
                import {
                  createTransferInterfaceInstructions,
                } from '@lightprotocol/compressed-token/unified';

                const transferLightTokens = async (
                  fromAddress: string,
                  toAddress: string,
                  tokenMintAddress: string,
                  amount: number,
                  decimals: number = 9,
                ) => {
                  const connection = createRpc(process.env.HELIUS_RPC_URL!);

                  const privy = new PrivyClient({
                    appId: process.env.PRIVY_APP_ID!,
                    appSecret: process.env.PRIVY_APP_SECRET!,
                  });

                  const fromPubkey = new PublicKey(fromAddress);
                  const toPubkey = new PublicKey(toAddress);
                  const mintPubkey = new PublicKey(tokenMintAddress);
                  const tokenAmount = Math.floor(amount * Math.pow(10, decimals));

                  // Loads cold (compressed), SPL, and Token 2022 balances into the Light Token associated token account before transfer.
                  // Returns TransactionInstruction[][] — send [0..n-2] in parallel, then [n-1] last.
                  const instructions = await createTransferInterfaceInstructions(
                    connection, fromPubkey, mintPubkey, tokenAmount, fromPubkey, toPubkey,
                  );

                  // Sign and send each batch via Privy
                  const walletId = process.env.TREASURY_WALLET_ID!;
                  const authorizationKey = process.env.TREASURY_AUTHORIZATION_KEY!;
                  const signatures: string[] = [];

                  for (const ixs of instructions) {
                    const tx = new Transaction().add(...ixs);
                    const {blockhash} = await connection.getLatestBlockhash();
                    tx.recentBlockhash = blockhash;
                    tx.feePayer = fromPubkey;

                    const {signed_transaction} = await privy.wallets().solana().signTransaction(
                      walletId, {
                        transaction: tx.serialize({requireAllSignatures: false}),
                        authorization_context: {authorization_private_keys: [authorizationKey]},
                      },
                    ) as any;

                    const sig = await connection.sendRawTransaction(
                      Buffer.from(signed_transaction, 'base64'),
                      {skipPreflight: false, preflightCommitment: 'confirmed'},
                    );
                    await connection.confirmTransaction(sig, 'confirmed');
                    signatures.push(sig);
                  }

                  return signatures[signatures.length - 1];
                };

                export default transferLightTokens;

                ```

                Your app logic may require you to sign all transactions in sequence. Here's how:

                <Accordion title="Sign all transactions together">
                  ```typescript theme={null}
                  for (const ixs of instructions) {
                    const tx = new Transaction().add(...ixs);
                    const { blockhash } = await connection.getLatestBlockhash();
                    tx.recentBlockhash = blockhash;
                    tx.feePayer = fromPubkey;

                    const { signed_transaction } = await privy.wallets().solana().signTransaction(
                      walletId, {
                        transaction: tx.serialize({ requireAllSignatures: false }),
                        authorization_context: { authorization_private_keys: [authorizationKey] },
                      },
                    ) as any;

                    const sig = await connection.sendRawTransaction(
                      Buffer.from(signed_transaction, 'base64'),
                      { skipPreflight: false, preflightCommitment: 'confirmed' },
                    );
                    await connection.confirmTransaction(sig, 'confirmed');
                  }
                  ```
                </Accordion>

                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.

                <Accordion title="Optimize sending (parallel conditional loads, then transfer)">
                  ```typescript theme={null}
                  import {
                    createTransferInterfaceInstructions,
                    sliceLast,
                  } from "@lightprotocol/compressed-token/unified";

                  const instructions = await createTransferInterfaceInstructions(
                    connection, fromPubkey, mint, amount, fromPubkey, 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 connection.getLatestBlockhash();
                      tx.recentBlockhash = blockhash;
                      tx.feePayer = fromPubkey;

                      const { signed_transaction } = await privy.wallets().solana().signTransaction(
                        walletId, {
                          transaction: tx.serialize({ requireAllSignatures: false }),
                          authorization_context: { authorization_private_keys: [authorizationKey] },
                        },
                      ) as any;

                      const sig = await connection.sendRawTransaction(
                        Buffer.from(signed_transaction, 'base64'),
                        { skipPreflight: false, preflightCommitment: 'confirmed' },
                      );
                      await connection.confirmTransaction(sig, 'confirmed');
                    })
                  );

                  const transferTx = new Transaction().add(...transferInstructions);
                  const { blockhash } = await connection.getLatestBlockhash();
                  transferTx.recentBlockhash = blockhash;
                  transferTx.feePayer = fromPubkey;

                  const { signed_transaction } = await privy.wallets().solana().signTransaction(
                    walletId, {
                      transaction: transferTx.serialize({ requireAllSignatures: false }),
                      authorization_context: { authorization_private_keys: [authorizationKey] },
                    },
                  ) as any;

                  await connection.sendRawTransaction(
                    Buffer.from(signed_transaction, 'base64'),
                    { skipPreflight: false, preflightCommitment: 'confirmed' },
                  );
                  ```
                </Accordion>
              </Tab>

              <Tab title="React">
                ```typescript theme={null}
                import { useState } from 'react';
                import { PublicKey } from '@solana/web3.js';
                import {
                  createTransferInterfaceInstructions,
                } from '@lightprotocol/compressed-token/unified';
                import { createRpc } from '@lightprotocol/stateless.js';
                import type { ConnectedStandardSolanaWallet } from '@privy-io/js-sdk-core';
                import { useSignTransaction } from '@privy-io/react-auth/solana';
                import { signAndSendBatches } from './signAndSendBatches';

                type SignTransactionFn = ReturnType<typeof useSignTransaction>['signTransaction'];

                export interface TransferParams {
                  ownerPublicKey: string;
                  mint: string;
                  toAddress: string;
                  amount: number;
                  decimals?: number;
                }

                export interface TransferArgs {
                  params: TransferParams;
                  wallet: ConnectedStandardSolanaWallet;
                  signTransaction: SignTransactionFn;
                }

                export function useTransfer() {
                  const [isLoading, setIsLoading] = useState(false);

                  const transfer = async (args: TransferArgs): Promise<string> => {
                    setIsLoading(true);

                    try {
                      const { params, wallet, signTransaction } = args;
                      const { ownerPublicKey, mint, toAddress, amount, decimals = 9 } = params;

                      const rpc = 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.floor(amount * Math.pow(10, decimals));

                      // Returns TransactionInstruction[][].
                      // Each inner array is one transaction.
                      // Almost always returns just one.
                      const instructions = await createTransferInterfaceInstructions(
                        rpc, owner, mintPubkey, tokenAmount, owner, recipient,
                      );

                      const signature = await signAndSendBatches(instructions, {
                        rpc,
                        feePayer: owner,
                        wallet,
                        signTransaction,
                      });

                      if (!signature) {
                        throw new Error('Transfer returned no instructions');
                      }

                      return signature;
                    } finally {
                      setIsLoading(false);
                    }
                  };

                  return { transfer, isLoading };
                }
                ```

                All React hooks use a shared `signAndSendBatches` helper that handles serialization, signing, sending, and confirming for each instruction batch:

                <Accordion title="signAndSendBatches helper">
                  ```typescript theme={null}
                  import { Transaction, TransactionInstruction, PublicKey } from '@solana/web3.js';
                  import type { ConnectedStandardSolanaWallet } from '@privy-io/js-sdk-core';
                  import { useSignTransaction } from '@privy-io/react-auth/solana';

                  type SignTransactionFn = ReturnType<typeof useSignTransaction>['signTransaction'];

                  interface SignAndSendOptions {
                    rpc: any;
                    feePayer: PublicKey;
                    wallet: ConnectedStandardSolanaWallet;
                    signTransaction: SignTransactionFn;
                  }

                  export async function signAndSendBatches(
                    instructionBatches: TransactionInstruction[][],
                    options: SignAndSendOptions,
                  ): Promise<string | null> {
                    const { rpc, feePayer, wallet, 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 unsignedTxBuffer = tx.serialize({ requireAllSignatures: false });
                      const signedTx = await signTransaction({
                        transaction: unsignedTxBuffer,
                        wallet,
                        chain: 'solana:devnet',
                      });

                      const signedTxBuffer = Buffer.from(signedTx.signedTransaction);
                      const sig = await rpc.sendRawTransaction(signedTxBuffer, {
                        skipPreflight: false,
                        preflightCommitment: 'confirmed',
                      });
                      await rpc.confirmTransaction(sig, 'confirmed');
                      signatures.push(sig);
                    }

                    return signatures.length > 0 ? signatures[signatures.length - 1] : null;
                  }
                  ```
                </Accordion>

                Your app logic may require you to sign all transactions in sequence. Here's how:

                <Accordion title="Sign all transactions together">
                  ```typescript theme={null}
                  for (const ixs of instructions) {
                    const tx = new Transaction().add(...ixs);
                    const { blockhash } = await rpc.getLatestBlockhash();
                    tx.recentBlockhash = blockhash;
                    tx.feePayer = owner;

                    const unsigned = tx.serialize({ requireAllSignatures: false });
                    const signed = await signTransaction({ transaction: unsigned, wallet, chain: 'solana:devnet' });

                    const sig = await rpc.sendRawTransaction(Buffer.from(signed.signedTransaction), {
                      skipPreflight: false, preflightCommitment: 'confirmed',
                    });
                    await rpc.confirmTransaction(sig, 'confirmed');
                  }
                  ```
                </Accordion>

                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.

                <Accordion title="Optimize sending (parallel conditional loads, then transfer)">
                  ```typescript theme={null}
                  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 unsigned = tx.serialize({ requireAllSignatures: false });
                      const signed = await signTransaction({ transaction: unsigned, wallet, chain: 'solana:devnet' });
                      const sig = await rpc.sendRawTransaction(Buffer.from(signed.signedTransaction), {
                        skipPreflight: false, preflightCommitment: 'confirmed',
                      });
                      await rpc.confirmTransaction(sig, 'confirmed');
                    })
                  );

                  const transferTx = new Transaction().add(...transferInstructions);
                  const { blockhash } = await rpc.getLatestBlockhash();
                  transferTx.recentBlockhash = blockhash;
                  transferTx.feePayer = owner;

                  const unsigned = transferTx.serialize({ requireAllSignatures: false });
                  const signed = await signTransaction({ transaction: unsigned, wallet, chain: 'solana:devnet' });
                  await rpc.sendRawTransaction(Buffer.from(signed.signedTransaction), {
                    skipPreflight: false, preflightCommitment: 'confirmed',
                  });
                  ```
                </Accordion>
              </Tab>
            </Tabs>
          </Tab>

          <Tab title="Receive">
            Load creates the associated token account (ATA) if needed and loads any compressed state into it.
            Share the ATA address with the sender.

            <Tabs>
              <Tab title="Node.js">
                ```typescript theme={null}
                import 'dotenv/config';
                import {PrivyClient} from '@privy-io/node';
                import {createRpc} from '@lightprotocol/stateless.js';
                import {PublicKey, Transaction} from '@solana/web3.js';
                import {
                  createLoadAtaInstructions,
                  getAssociatedTokenAddressInterface,
                } from '@lightprotocol/compressed-token/unified';

                const receiveLightTokens = async (
                  recipientAddress: string,
                  tokenMintAddress: string,
                ) => {
                  const connection = createRpc(process.env.HELIUS_RPC_URL!);

                  const privy = new PrivyClient({
                    appId: process.env.PRIVY_APP_ID!,
                    appSecret: process.env.PRIVY_APP_SECRET!,
                  });

                  const recipient = new PublicKey(recipientAddress);
                  const mintPubkey = new PublicKey(tokenMintAddress);
                  const ata = getAssociatedTokenAddressInterface(mintPubkey, recipient);

                  // Creates the ATA if needed and loads any compressed state into it.
                  // Returns TransactionInstruction[][] — almost always just one.
                  const instructions = await createLoadAtaInstructions(
                    connection, ata, recipient, mintPubkey, recipient,
                  );

                  // Sign and send each batch via Privy
                  const walletId = process.env.TREASURY_WALLET_ID!;
                  const authorizationKey = process.env.TREASURY_AUTHORIZATION_KEY!;
                  const signatures: string[] = [];

                  for (const ixs of instructions) {
                    const tx = new Transaction().add(...ixs);
                    const {blockhash} = await connection.getLatestBlockhash();
                    tx.recentBlockhash = blockhash;
                    tx.feePayer = recipient;

                    const {signed_transaction} = await privy.wallets().solana().signTransaction(
                      walletId, {
                        transaction: tx.serialize({requireAllSignatures: false}),
                        authorization_context: {authorization_private_keys: [authorizationKey]},
                      },
                    ) as any;

                    const sig = await connection.sendRawTransaction(
                      Buffer.from(signed_transaction, 'base64'),
                      {skipPreflight: false, preflightCommitment: 'confirmed'},
                    );
                    await connection.confirmTransaction(sig, 'confirmed');
                    signatures.push(sig);
                  }

                  return signatures.length > 0 ? signatures[signatures.length - 1] : null;
                };

                export default receiveLightTokens;

                ```
              </Tab>

              <Tab title="React">
                ```typescript theme={null}
                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 type { ConnectedStandardSolanaWallet } from '@privy-io/js-sdk-core';
                import { useSignTransaction } from '@privy-io/react-auth/solana';
                import { signAndSendBatches } from './signAndSendBatches';

                type SignTransactionFn = ReturnType<typeof useSignTransaction>['signTransaction'];

                export interface ReceiveParams {
                  ownerPublicKey: string;
                  mint: string;
                }

                export interface ReceiveArgs {
                  params: ReceiveParams;
                  wallet: ConnectedStandardSolanaWallet;
                  signTransaction: SignTransactionFn;
                }

                export function useReceive() {
                  const [isLoading, setIsLoading] = useState(false);

                  const receive = async (args: ReceiveArgs): Promise<string | null> => {
                    setIsLoading(true);

                    try {
                      const { params, wallet, signTransaction } = args;

                      const rpc = 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,
                        wallet,
                        signTransaction,
                      });
                    } finally {
                      setIsLoading(false);
                    }
                  };

                  return { receive, isLoading };
                }
                ```
              </Tab>
            </Tabs>
          </Tab>

          <Tab title="Wrap (SPL → Light)">
            Wrap SPL or Token-2022 tokens into a light-token associated token account.

            <Tabs>
              <Tab title="Node.js">
                ```typescript theme={null}
                import 'dotenv/config';
                import {PrivyClient} from '@privy-io/node';
                import {createRpc, LIGHT_TOKEN_PROGRAM_ID} from '@lightprotocol/stateless.js';
                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';

                const wrapTokens = async (
                  fromAddress: string,
                  tokenMintAddress: string,
                  amount: number,
                  decimals: number = 9,
                ) => {
                  const connection = createRpc(process.env.HELIUS_RPC_URL!);

                  const privy = new PrivyClient({
                    appId: process.env.PRIVY_APP_ID!,
                    appSecret: process.env.PRIVY_APP_SECRET!,
                  });

                  const fromPubkey = new PublicKey(fromAddress);
                  const mintPubkey = new PublicKey(tokenMintAddress);
                  const tokenAmount = BigInt(Math.floor(amount * Math.pow(10, decimals)));

                  // Get SPL interface info — determines whether mint uses SPL or Token 2022
                  const splInterfaceInfos = await getSplInterfaceInfos(connection, mintPubkey);
                  const splInterfaceInfo = splInterfaceInfos.find(
                    (info) => info.isInitialized,
                  );
                  if (!splInterfaceInfo) throw new Error('No SPL interface found for this mint');

                  // Derive source associated token account using the mint's token program (SPL or Token 2022)
                  const {tokenProgram} = splInterfaceInfo;
                  const splAta = getAssociatedTokenAddressSync(mintPubkey, fromPubkey, false, tokenProgram);
                  const ataAccount = await getAccount(connection, splAta, undefined, tokenProgram);
                  if (ataAccount.amount < BigInt(tokenAmount)) {
                    throw new Error('Insufficient SPL balance');
                  }

                  // Derive Light Token associated token account
                  const lightTokenAta = getAssociatedTokenAddressInterface(mintPubkey, fromPubkey);

                  // Build instructions
                  const tx = new Transaction().add(
                    ComputeBudgetProgram.setComputeUnitLimit({units: 200_000}),
                    createAssociatedTokenAccountInterfaceIdempotentInstruction(
                      fromPubkey, lightTokenAta, fromPubkey, mintPubkey, LIGHT_TOKEN_PROGRAM_ID,
                    ),
                    createWrapInstruction(
                      splAta, lightTokenAta, fromPubkey, mintPubkey,
                      tokenAmount, splInterfaceInfo, decimals, fromPubkey,
                    ),
                  );

                  // Sign and send via Privy
                  const {blockhash} = await connection.getLatestBlockhash();
                  tx.recentBlockhash = blockhash;
                  tx.feePayer = fromPubkey;

                  const walletId = process.env.TREASURY_WALLET_ID!;
                  const authorizationKey = process.env.TREASURY_AUTHORIZATION_KEY!;

                  const {signed_transaction} = await privy.wallets().solana().signTransaction(
                    walletId, {
                      transaction: tx.serialize({requireAllSignatures: false}),
                      authorization_context: {authorization_private_keys: [authorizationKey]},
                    },
                  ) as any;

                  const signature = await connection.sendRawTransaction(
                    Buffer.from(signed_transaction, 'base64'),
                    {skipPreflight: false, preflightCommitment: 'confirmed'},
                  );
                  await connection.confirmTransaction(signature, 'confirmed');

                  return signature;
                };

                export default wrapTokens;

                ```
              </Tab>

              <Tab title="React">
                ```typescript theme={null}
                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, LIGHT_TOKEN_PROGRAM_ID } from '@lightprotocol/stateless.js';
                import type { ConnectedStandardSolanaWallet } from '@privy-io/js-sdk-core';
                import { useSignTransaction } from '@privy-io/react-auth/solana';

                type SignTransactionFn = ReturnType<typeof useSignTransaction>['signTransaction'];

                export interface WrapParams {
                  ownerPublicKey: string;
                  mint: string;
                  amount: number;
                  decimals?: number;
                }

                export interface WrapArgs {
                  params: WrapParams;
                  wallet: ConnectedStandardSolanaWallet;
                  signTransaction: SignTransactionFn;
                }

                export function useWrap() {
                  const [isLoading, setIsLoading] = useState(false);

                  const wrap = async (args: WrapArgs): Promise<string> => {
                    setIsLoading(true);

                    try {
                      const { params, wallet, signTransaction } = args;
                      const { ownerPublicKey, mint, amount, decimals = 9 } = params;

                      const rpc = createRpc(import.meta.env.VITE_HELIUS_RPC_URL);

                      const owner = new PublicKey(ownerPublicKey);
                      const mintPubkey = new PublicKey(mint);
                      const tokenAmount = BigInt(Math.floor(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, LIGHT_TOKEN_PROGRAM_ID,
                        ),
                        createWrapInstruction(
                          splAta, lightTokenAta, owner, mintPubkey,
                          tokenAmount, splInterfaceInfo, decimals, owner,
                        ),
                      );

                      const { blockhash } = await rpc.getLatestBlockhash();
                      tx.recentBlockhash = blockhash;
                      tx.feePayer = owner;

                      const unsignedTxBuffer = tx.serialize({ requireAllSignatures: false });
                      const signedTx = await signTransaction({
                        transaction: unsignedTxBuffer,
                        wallet,
                        chain: 'solana:devnet',
                      });

                      const signedTxBuffer = Buffer.from(signedTx.signedTransaction);
                      return rpc.sendRawTransaction(signedTxBuffer, {
                        skipPreflight: false,
                        preflightCommitment: 'confirmed',
                      });
                    } finally {
                      setIsLoading(false);
                    }
                  };

                  return { wrap, isLoading };
                }
                ```
              </Tab>
            </Tabs>
          </Tab>

          <Tab title="Unwrap (Light → SPL)">
            Unwrap light-token balance to SPL or Token-2022. Use unwrap to interact with applications that only support SPL/Token-2022.

            <Tabs>
              <Tab title="Node.js">
                ```typescript theme={null}
                import 'dotenv/config';
                import {PrivyClient} from '@privy-io/node';
                import {createRpc} from '@lightprotocol/stateless.js';
                import {PublicKey, Transaction} from '@solana/web3.js';
                import {getAssociatedTokenAddressSync} from '@solana/spl-token';
                import {
                  createUnwrapInstructions,
                } from '@lightprotocol/compressed-token/unified';

                const unwrapTokens = async (
                  fromAddress: string,
                  tokenMintAddress: string,
                  amount: number,
                  decimals: number = 9,
                ) => {
                  const connection = createRpc(process.env.HELIUS_RPC_URL!);

                  const privy = new PrivyClient({
                    appId: process.env.PRIVY_APP_ID!,
                    appSecret: process.env.PRIVY_APP_SECRET!,
                  });

                  const fromPubkey = new PublicKey(fromAddress);
                  const mintPubkey = new PublicKey(tokenMintAddress);
                  const tokenAmount = BigInt(Math.floor(amount * Math.pow(10, decimals)));

                  // Auto-detect token program (SPL vs Token 2022) from mint account owner
                  const mintAccountInfo = await connection.getAccountInfo(mintPubkey);
                  if (!mintAccountInfo) throw new Error(`Mint account ${tokenMintAddress} not found`);
                  const tokenProgramId = mintAccountInfo.owner;

                  // Destination: SPL/T22 associated token account
                  const splAta = getAssociatedTokenAddressSync(mintPubkey, fromPubkey, false, tokenProgramId);

                  // Returns TransactionInstruction[][].
                  // Each inner array is one transaction.
                  // Handles loading + unwrapping together.
                  const instructions = await createUnwrapInstructions(
                    connection, splAta, fromPubkey, mintPubkey, tokenAmount, fromPubkey,
                  );

                  // Sign and send each batch via Privy
                  const walletId = process.env.TREASURY_WALLET_ID!;
                  const authorizationKey = process.env.TREASURY_AUTHORIZATION_KEY!;
                  const signatures: string[] = [];

                  for (const ixs of instructions) {
                    const tx = new Transaction().add(...ixs);
                    const {blockhash} = await connection.getLatestBlockhash();
                    tx.recentBlockhash = blockhash;
                    tx.feePayer = fromPubkey;

                    const {signed_transaction} = await privy.wallets().solana().signTransaction(
                      walletId, {
                        transaction: tx.serialize({requireAllSignatures: false}),
                        authorization_context: {authorization_private_keys: [authorizationKey]},
                      },
                    ) as any;

                    const sig = await connection.sendRawTransaction(
                      Buffer.from(signed_transaction, 'base64'),
                      {skipPreflight: false, preflightCommitment: 'confirmed'},
                    );
                    await connection.confirmTransaction(sig, 'confirmed');
                    signatures.push(sig);
                  }

                  return signatures[signatures.length - 1];
                };

                export default unwrapTokens;

                ```
              </Tab>

              <Tab title="React">
                ```typescript theme={null}
                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 type { ConnectedStandardSolanaWallet } from '@privy-io/js-sdk-core';
                import { useSignTransaction } from '@privy-io/react-auth/solana';
                import { signAndSendBatches } from './signAndSendBatches';

                type SignTransactionFn = ReturnType<typeof useSignTransaction>['signTransaction'];

                export interface UnwrapParams {
                  ownerPublicKey: string;
                  mint: string;
                  amount: number;
                  decimals?: number;
                }

                export interface UnwrapArgs {
                  params: UnwrapParams;
                  wallet: ConnectedStandardSolanaWallet;
                  signTransaction: SignTransactionFn;
                }

                export function useUnwrap() {
                  const [isLoading, setIsLoading] = useState(false);

                  const unwrap = async (args: UnwrapArgs): Promise<string> => {
                    setIsLoading(true);

                    try {
                      const { params, wallet, signTransaction } = args;
                      const { ownerPublicKey, mint, amount, decimals = 9 } = params;

                      const rpc = createRpc(import.meta.env.VITE_HELIUS_RPC_URL);

                      const owner = new PublicKey(ownerPublicKey);
                      const mintPubkey = new PublicKey(mint);
                      const tokenAmount = BigInt(Math.floor(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,
                        wallet,
                        signTransaction,
                      });

                      if (!signature) {
                        throw new Error('Unwrap returned no instructions');
                      }

                      return signature;
                    } finally {
                      setIsLoading(false);
                    }
                  };

                  return { unwrap, isLoading };
                }
                ```
              </Tab>
            </Tabs>
          </Tab>
        </Tabs>
      </Step>
    </Steps>

    ## Show Balance

    Query token balances to show a unified balance of SOL,
    Light, SPL, and Token-2022.

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

    <Accordion title="Unified Balance Example (Node.js and React)">
      <Tabs>
        <Tab title="Node.js">
          ```typescript theme={null}
          import 'dotenv/config';
          import {PublicKey, LAMPORTS_PER_SOL} from '@solana/web3.js';
          import {TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, getMint} from '@solana/spl-token';
          import {createRpc} from '@lightprotocol/stateless.js';
          import {
            getAtaInterface,
            getAssociatedTokenAddressInterface,
          } from '@lightprotocol/compressed-token/unified';

          interface TokenBalance {
            mint: string;
            decimals: number;
            hot: number;
            cold: number;
            spl: number;
            t22: number;
            unified: number;
          }

          interface BalanceBreakdown {
            sol: number;
            tokens: TokenBalance[];
          }

          export async function getBalances(
            ownerAddress: string,
          ): Promise<BalanceBreakdown> {
            const rpc = createRpc(process.env.HELIUS_RPC_URL!);
            const owner = new PublicKey(ownerAddress);

            // SOL balance
            let solLamports = 0;
            try {
              solLamports = await rpc.getBalance(owner);
            } catch (e) {
              console.error('Failed to fetch SOL balance:', e);
            }

            // Per-mint accumulator (raw values, converted at assembly)
            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. 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
            }

            // 2. 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
            }

            // 3. 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
            }

            // 4. 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
                }
              }),
            );

            // 5. 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);
                  getOrCreate(mintStr).hot = BigInt(parsed.amount.toString());
                } catch {
                  // Associated token account does not exist for this mint
                }
              }),
            );

            // 6. Assemble result (convert raw → UI amounts here)
            const tokens: TokenBalance[] = [];
            for (const [mintStr, entry] of mintMap) {
              const d = entry.decimals;
              tokens.push({
                mint: mintStr,
                decimals: d,
                hot: toUiAmount(entry.hot, d),
                cold: toUiAmount(entry.cold, d),
                spl: toUiAmount(entry.spl, d),
                t22: toUiAmount(entry.t22, d),
                unified: toUiAmount(entry.hot + entry.cold, d),
              });
            }

            return {sol: solLamports / LAMPORTS_PER_SOL, tokens};
          }

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

          function toUiAmount(raw: bigint, decimals: number): number {
            return Number(raw) / 10 ** decimals;
          }

          export default getBalances;

          ```
        </Tab>

        <Tab title="React">
          ```typescript theme={null}
          import { useState, useCallback } from 'react';
          import { PublicKey, LAMPORTS_PER_SOL } 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 = 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[] = [];

                // SOL entry
                result.push({
                  mint: 'So11111111111111111111111111111111111111112',
                  decimals: 9,
                  isNative: true,
                  hot: 0n,
                  cold: 0n,
                  spl: BigInt(solLamports),
                  t22: 0n,
                  unified: 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;
          }
          ```
        </Tab>
      </Tabs>
    </Accordion>

    ## Get Transaction History

    Fetch light-token transaction history for an owner.

    ```typescript theme={null}
    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
    ```

    <Accordion title="Example (Node.js and React)">
      <Tabs>
        <Tab title="Node.js">
          ```typescript theme={null}
          import 'dotenv/config';
          import {createRpc} from '@lightprotocol/stateless.js';
          import {PublicKey} from '@solana/web3.js';

          const getTransactionHistory = async (
            ownerAddress: string,
            limit: number = 10,
          ) => {
            const connection = createRpc(process.env.HELIUS_RPC_URL!);

            const owner = new PublicKey(ownerAddress);

            // Get Light Token interface signatures
            const result = await connection.getSignaturesForOwnerInterface(owner);

            if (!result.signatures || result.signatures.length === 0) {
              return {
                count: 0,
                transactions: [],
              };
            }

            const limitedSignatures = result.signatures.slice(0, limit);

            const transactions = limitedSignatures.map((sig) => ({
              signature: sig.signature,
              slot: sig.slot,
              blockTime: sig.blockTime ?? 0,
              timestamp: sig.blockTime ? new Date(sig.blockTime * 1000).toISOString() : '',
            }));

            return {
              count: result.signatures.length,
              transactions,
            };
          };

          export default getTransactionHistory;

          ```
        </Tab>

        <Tab title="React">
          ```typescript theme={null}
          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 = 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 };
          }
          ```
        </Tab>
      </Tabs>
    </Accordion>

    ## 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.

    <Note>
      Find a full code example [here](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/register-spl-mint.ts).
    </Note>

    Check if the interface already exists:

    ```typescript theme={null}
    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.");
    }
    ```

    Register:

    <Tabs>
      <Tab title="Instruction">
        ```typescript theme={null}
        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]);
        ```
      </Tab>

      <Tab title="Action">
        ```typescript theme={null}
        import { createSplInterface } from "@lightprotocol/compressed-token";

        await createSplInterface(rpc, payer, mint);
        ```
      </Tab>
    </Tabs>

    <Accordion title="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:

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

  <Tab title="AI Prompt">
    <Prompt description="Integrate light-token with Privy embedded wallets" actions={["copy", "cursor"]}>
      {`---
            description: Integrate light-token with Privy embedded wallets
            allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
            ---

            ## Integrate light-token with Privy embedded wallets

            Context:
            - Guide: https://zkcompression.com/light-token/wallets/privy
            - Skills and resources index: https://zkcompression.com/skill.md
            - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
            - Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments
            - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js
            - Node.js example: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-privy/nodejs
            - React example: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-privy/react

            SPL → Light Token API mapping:
            | Operation        | SPL                          | Light Token                              |
            | Transfer         | createTransferInstruction()  | createTransferInterfaceInstructions()    |
            | Receive          | getOrCreateAssociatedTokenAccount() | createLoadAtaInstructions()       |
            | Wrap SPL→Light   | N/A                          | createWrapInstruction()                  |
            | Unwrap Light→SPL | N/A                          | createUnwrapInstructions()               |
            | Get balance      | getAccount()                 | getAtaInterface()                        |
            | Tx history       | getSignaturesForAddress()    | getSignaturesForOwnerInterface()         |

            ### 1. Index project
            - Grep \`privy|@privy-io|usePrivy|PrivyProvider|createTransferInstruction|@solana/spl-token|Connection\` across src/
            - Glob \`**/*.ts\` and \`**/*.tsx\` for project structure
            - Identify: Privy SDK version, 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 both Node.js and 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: Node.js or React?
            - AskUserQuestion: what is the goal? (new Privy integration, migrate existing Privy+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 Privy wallet setup is compatible (signTransaction or sendTransaction pattern)
            - Key integration pattern: build unsigned tx with light-token SDK → sign with Privy → 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
            - 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`}
    </Prompt>

    ```text theme={null}
    ---
    description: Integrate light-token with Privy embedded wallets
    allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
    ---

    ## Integrate light-token with Privy embedded wallets

    Context:
    - Guide: https://zkcompression.com/light-token/wallets/privy
    - Skills and resources index: https://zkcompression.com/skill.md
    - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
    - Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments
    - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js
    - Node.js example: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-privy/nodejs
    - React example: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-privy/react

    SPL → Light Token API mapping:
    | Operation        | SPL                          | Light Token                              |
    | Transfer         | createTransferInstruction()  | createTransferInterfaceInstructions()    |
    | Receive          | getOrCreateAssociatedTokenAccount() | createLoadAtaInstructions()       |
    | Wrap SPL→Light   | N/A                          | createWrapInstruction()                  |
    | Unwrap Light→SPL | N/A                          | createUnwrapInstructions()               |
    | Get balance      | getAccount()                 | getAtaInterface()                        |
    | Tx history       | getSignaturesForAddress()    | getSignaturesForOwnerInterface()         |

    ### 1. Index project
    - Grep `privy|@privy-io|usePrivy|PrivyProvider|createTransferInstruction|@solana/spl-token|Connection` across src/
    - Glob `**/*.ts` and `**/*.tsx` for project structure
    - Identify: Privy SDK version, 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 both Node.js and 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: Node.js or React?
    - AskUserQuestion: what is the goal? (new Privy integration, migrate existing Privy+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 Privy wallet setup is compatible (signTransaction or sendTransaction pattern)
    - Key integration pattern: build unsigned tx with light-token SDK → sign with Privy → 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
    - 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
    ```
  </Tab>
</Tabs>

***

## Didn't find what you were looking for?

<Callout type="info">
  Reach out! [Telegram](https://t.me/swen_light) | [email](mailto:support@lightprotocol.com) | [Discord](https://discord.com/invite/7cJ8BhAXhu)
</Callout>
