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

# Integration Guide for Wallet Applications

> Guide for Wallet Applications to add Light-token support.

export const CodeCompare = ({firstCode = "", secondCode = "", firstLabel = "Light Token", secondLabel = "SPL", language = "javascript"}) => {
  const [sliderPercent, setSliderPercent] = useState(100);
  const [isDragging, setIsDragging] = useState(false);
  const [isAnimating, setIsAnimating] = useState(false);
  const [copied, setCopied] = useState(false);
  const containerRef = useRef(null);
  const animationRef = useRef(null);
  const firstPreRef = useRef(null);
  const secondPreRef = useRef(null);
  const [containerHeight, setContainerHeight] = useState(null);
  const showingFirst = sliderPercent > 50;
  const handleCopy = async () => {
    const codeToCopy = showingFirst ? firstCode : secondCode;
    await navigator.clipboard.writeText(codeToCopy);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };
  const highlightCode = code => {
    let escaped = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    if (language === "rust") {
      const rustPattern = /(\/\/.*$)|(["'])(?:(?!\2)[^\\]|\\.)*?\2|\b(use|let|mut|pub|fn|struct|impl|enum|mod|const|static|trait|type|where|for|in|if|else|match|loop|while|return|self|Self|true|false|Some|None|Ok|Err|Result|Option|vec!)\b|::([a-zA-Z_][a-zA-Z0-9_]*)|&amp;([a-zA-Z_][a-zA-Z0-9_]*)|\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()|(\?)/gm;
      return escaped.replace(rustPattern, (match, comment, stringQuote, keyword, pathSegment, reference, func, questionMark) => {
        if (comment) return `<span style="color:#6b7280;font-style:italic">${match}</span>`;
        if (stringQuote) return `<span style="color:#059669">${match}</span>`;
        if (keyword) return `<span style="color:#db2777">${match}</span>`;
        if (pathSegment) return `::<span style="color:#0891b2">${pathSegment}</span>`;
        if (reference) return `&amp;<span style="color:#6366f1">${reference}</span>`;
        if (func) return `<span style="color:#2563eb">${match}</span>`;
        if (questionMark) return `<span style="color:#db2777">?</span>`;
        return match;
      });
    }
    const pattern = /(\/\/.*$)|(["'`])(?:(?!\2)[^\\]|\\.)*?\2|\b(const|let|var|await|async|import|from|export|return|if|else|function|class|new|throw|try|catch)\b|\.([a-zA-Z_][a-zA-Z0-9_]*)\b|\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/gm;
    return escaped.replace(pattern, (match, comment, stringQuote, keyword, property, func) => {
      if (comment) return `<span style="color:#6b7280;font-style:italic">${match}</span>`;
      if (stringQuote) return `<span style="color:#059669">${match}</span>`;
      if (keyword) return `<span style="color:#db2777">${match}</span>`;
      if (property) return `.<span style="color:#0891b2">${property}</span>`;
      if (func) return `<span style="color:#2563eb">${match}</span>`;
      return match;
    });
  };
  const animateTo = target => {
    if (animationRef.current) cancelAnimationFrame(animationRef.current);
    setIsAnimating(true);
    const start = sliderPercent;
    const startTime = performance.now();
    const duration = 400;
    const animate = currentTime => {
      const elapsed = currentTime - startTime;
      const progress = Math.min(elapsed / duration, 1);
      const eased = 1 - Math.pow(1 - progress, 3);
      const current = start + (target - start) * eased;
      setSliderPercent(current);
      if (progress < 1) {
        animationRef.current = requestAnimationFrame(animate);
      } else {
        setSliderPercent(target);
        setIsAnimating(false);
        animationRef.current = null;
      }
    };
    animationRef.current = requestAnimationFrame(animate);
  };
  const handleToggle = () => {
    animateTo(showingFirst ? 0 : 100);
  };
  const handleMouseDown = e => {
    if (isAnimating) {
      cancelAnimationFrame(animationRef.current);
      setIsAnimating(false);
    }
    e.preventDefault();
    setIsDragging(true);
  };
  const handleMouseUp = () => {
    setIsDragging(false);
  };
  const handleMouseMove = e => {
    if (!isDragging || !containerRef.current) return;
    const rect = containerRef.current.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const percent = Math.max(0, Math.min(100, x / rect.width * 100));
    setSliderPercent(percent);
  };
  const handleTouchMove = e => {
    if (!containerRef.current) return;
    if (isAnimating) {
      cancelAnimationFrame(animationRef.current);
      setIsAnimating(false);
    }
    const rect = containerRef.current.getBoundingClientRect();
    const x = e.touches[0].clientX - rect.left;
    const percent = Math.max(0, Math.min(100, x / rect.width * 100));
    setSliderPercent(percent);
  };
  const handleKeyDown = e => {
    if (e.key === "ArrowLeft") {
      setSliderPercent(p => Math.max(0, p - 5));
    } else if (e.key === "ArrowRight") {
      setSliderPercent(p => Math.min(100, p + 5));
    }
  };
  useEffect(() => {
    if (isDragging) {
      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);
      return () => {
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", handleMouseUp);
      };
    }
  }, [isDragging]);
  useEffect(() => {
    return () => {
      if (animationRef.current) cancelAnimationFrame(animationRef.current);
    };
  }, []);
  useEffect(() => {
    const activeRef = showingFirst ? firstPreRef : secondPreRef;
    if (activeRef.current) {
      setContainerHeight(activeRef.current.scrollHeight);
    }
  }, [showingFirst]);
  return <>
      <div className="rounded-3xl not-prose mt-4 backdrop-blur-xl border overflow-hidden border-zinc-300 dark:border-zinc-700" style={{
    fontFamily: "Inter, sans-serif"
  }}>
        {}
        <div className="flex items-center justify-between px-4 py-3 border-b border-zinc-200 dark:border-zinc-700 bg-gray-50 dark:bg-zinc-900">
          <span className="text-sm font-medium text-zinc-600 dark:text-zinc-300">
            {showingFirst ? firstLabel : secondLabel}
          </span>

          <div className="flex items-center gap-3">
            {}
            <button onClick={handleCopy} className="p-1.5 rounded hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors text-zinc-500 dark:text-zinc-400" title="Copy code" style={{
    background: "transparent",
    border: "none",
    cursor: "pointer"
  }}>
              {copied ? <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2">
                  <path d="M20 6L9 17l-5-5" />
                </svg> : <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
                  <path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
                  <path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
                </svg>}
            </button>

            {}
            <div onClick={handleToggle} className="bg-zinc-200 dark:bg-zinc-600" style={{
    position: "relative",
    width: "56px",
    height: "28px",
    borderRadius: "14px",
    boxShadow: "inset -2px -2px 4px rgba(255,255,255,0.3), inset 2px 2px 4px rgba(0,0,0,0.1)",
    cursor: "pointer",
    transition: "all 0.3s ease"
  }}>
              {}
              <div className="bg-white dark:bg-zinc-300" style={{
    position: "absolute",
    width: "24px",
    height: "24px",
    borderRadius: "12px",
    top: "2px",
    left: showingFirst ? "30px" : "2px",
    boxShadow: "0 2px 4px rgba(0,0,0,0.2)",
    transition: "all 0.3s ease-in-out",
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
                {}
                <div style={{
    width: "6px",
    height: "6px",
    background: showingFirst ? "#0066ff" : "#999",
    borderRadius: "50%",
    boxShadow: showingFirst ? "0 0 5px 1px rgba(0, 102, 255, 0.6)" : "0 0 4px 1px rgba(0, 0, 0, 0.1)",
    transition: "all 0.3s ease-in-out"
  }} />
              </div>
            </div>
          </div>
        </div>

        {}
        <div ref={containerRef} className="p-0" style={{
    cursor: isDragging ? "grabbing" : "default"
  }} onTouchMove={handleTouchMove} tabIndex={0} onKeyDown={handleKeyDown} role="slider" aria-valuenow={sliderPercent} aria-valuemin={0} aria-valuemax={100} aria-label="Code comparison slider">
          <div className="relative" style={{
    minHeight: "140px",
    overflow: "hidden",
    height: containerHeight ? `${containerHeight}px` : "auto",
    transition: "height 0.3s ease"
  }}>
            <div style={{
    position: "relative"
  }}>
              {}
              <pre ref={secondPreRef} className="m-0 p-4 text-zinc-700 dark:text-white/80 bg-transparent" style={{
    position: showingFirst ? "absolute" : "relative",
    top: 0,
    left: 0,
    right: 0,
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
    fontSize: "13px",
    lineHeight: "1.6",
    whiteSpace: "pre",
    zIndex: 1
  }} dangerouslySetInnerHTML={{
    __html: highlightCode(secondCode)
  }} />

              {}
              <pre ref={firstPreRef} className="m-0 p-4 text-zinc-700 dark:text-white/80 bg-white dark:bg-zinc-900" style={{
    position: showingFirst ? "relative" : "absolute",
    top: 0,
    left: 0,
    right: 0,
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
    fontSize: "13px",
    lineHeight: "1.6",
    whiteSpace: "pre",
    zIndex: 2,
    clipPath: `inset(0 ${100 - sliderPercent}% 0 0)`
  }} dangerouslySetInnerHTML={{
    __html: highlightCode(firstCode)
  }} />
            </div>

            {}
            <div className="absolute top-0 bottom-0 flex items-center justify-center pointer-events-none" style={{
    left: `${sliderPercent}%`,
    transform: "translateX(-50%)",
    zIndex: 30
  }}>
              <div className="absolute top-0 bottom-0 w-px bg-zinc-400 dark:bg-white/30" />

              <div className="absolute top-0 bottom-0" style={{
    right: "50%",
    width: "60px",
    background: "linear-gradient(to left, rgba(0, 102, 255, 0.15) 0%, transparent 100%)"
  }} />

              {}
              <div onMouseDown={handleMouseDown} className="pointer-events-auto cursor-grab flex items-center justify-center gap-px transition-transform" style={{
    width: "20px",
    height: "32px",
    borderRadius: "4px",
    background: "#f8fafc",
    border: "1px solid #d1d5db",
    boxShadow: "0 1px 2px rgba(0,0,0,0.05)",
    transform: isDragging ? "scale(1.08)" : "scale(1)"
  }}>
                <div className="flex flex-col gap-0.5">
                  {[0, 1, 2].map(i => <div key={i} style={{
    width: "3px",
    height: "3px",
    borderRadius: "50%",
    background: "#0066ff"
  }} />)}
                </div>
                <div className="flex flex-col gap-0.5">
                  {[0, 1, 2].map(i => <div key={i} style={{
    width: "3px",
    height: "3px",
    borderRadius: "50%",
    background: "#0066ff"
  }} />)}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </>;
};

1. The Light-token API matches the SPL-token API almost entirely, and extends their functionality to include the light token program in addition to the SPL-token and Token-2022 programs.
2. Your users hold and receive tokens of the same mints, just stored more efficiently.

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

### What you will implement

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

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

  <tbody>
    <tr>
      <td>**Receive**</td>
      <td>getOrCreateAssociatedTokenAccount()</td>
      <td>createLoadAtaInstructions() / loadAta()</td>
    </tr>

    <tr>
      <td>**Transfer**</td>
      <td>createTransferInstruction()</td>
      <td>createTransferInterfaceInstructions() / transferInterface()</td>
    </tr>

    <tr>
      <td>**Get Balance**</td>
      <td>getAccount()</td>
      <td>getAtaInterface()</td>
    </tr>

    <tr>
      <td>**Tx History**</td>
      <td>getSignaturesForAddress()</td>
      <td>rpc.getSignaturesForOwnerInterface()</td>
    </tr>

    <tr>
      <td>**Wrap from SPL**</td>
      <td>N/A</td>
      <td>wrap() / createWrapInstruction()</td>
    </tr>

    <tr>
      <td>**Unwrap to SPL**</td>
      <td>N/A</td>
      <td>createUnwrapInstructions() / unwrap()</td>
    </tr>
  </tbody>
</table>

<Note>
  Find full runnable code examples
  [here](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments).
</Note>

<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">
    ## Setup

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

    Snippets below assume `rpc`, `payer`, `mint`, `owner`, `recipient`, and `amount` are defined.
    See the [full examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments) for runnable setup.

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

    import {
      createLoadAtaInstructions,
      loadAta,
      createTransferInterfaceInstructions,
      transferInterface,
      createUnwrapInstructions,
      unwrap,
      getAssociatedTokenAddressInterface,
      getAtaInterface,
      wrap,
    } from "@lightprotocol/compressed-token/unified";

    const rpc = createRpc(RPC_ENDPOINT);
    ```

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

    <Info>
      **SDK 2.0 (`@lightprotocol/token-interface`)** is the latest JavaScript SDK.
      It has better API ergonomics, guarantees single-instruction loading, and is
      compatible with KIT (the new Solana SDKs).
    </Info>

    ## Receive Payments

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

    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="Instruction">
        ```typescript theme={null}
        import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
        import {
          createLoadAtaInstructions,
          getAssociatedTokenAddressInterface,
        } from "@lightprotocol/compressed-token/unified";

        const ata = getAssociatedTokenAddressInterface(mint, recipient);

        // Returns TransactionInstruction[][].
        // Each inner array is one transaction.
        // Almost always returns just one.
        const instructions = await createLoadAtaInstructions(
          rpc,
          ata,
          recipient,
          mint,
          payer.publicKey
        );

        for (const ixs of instructions) {
          const tx = new Transaction().add(...ixs);
          await sendAndConfirmTransaction(rpc, tx, [payer]);
        }
        ```
      </Tab>

      <Tab title="SDK 2.0 (token-interface)">
        ```typescript theme={null}
        import { Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";
        import { createLoadInstructions } from "@lightprotocol/token-interface";

        const rpc = createRpc();
        const payer = Keypair.fromSecretKey(/* ... */);

        const mint = /* existing mint public key */;
        const owner = payer.publicKey;

        const instructions = await createLoadInstructions({
          rpc,
          payer: payer.publicKey,
          owner,
          mint,
          authority: owner,
        });

        if (instructions.length > 0) {
          const tx = new Transaction().add(...instructions);
          const signature = await sendAndConfirmTransaction(rpc, tx, [payer]);
          console.log("Tx:", signature);
        }
        ```
      </Tab>

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

        const ata = getAssociatedTokenAddressInterface(mint, recipient);

        const sig = await loadAta(rpc, ata, recipient, mint, payer);
        if (sig) console.log("Loaded:", sig);
        ```
      </Tab>
    </Tabs>

    <Accordion title="Compare to SPL">
      ```typescript theme={null}
      import {
        createAssociatedTokenAccountInstruction,
        getAssociatedTokenAddressSync,
        getOrCreateAssociatedTokenAccount,
      } from "@solana/spl-token";

      const ata = getAssociatedTokenAddressSync(mint, recipient);

      // Instruction:
      const tx = new Transaction().add(
        createAssociatedTokenAccountInstruction(payer.publicKey, ata, recipient, mint)
      );

      // Action:
      const ata = await getOrCreateAssociatedTokenAccount(
        connection, payer, mint, recipient
      );
      ```
    </Accordion>

    ## Send Payments

    <Note>
      Find a full code example: [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/basic-send-instruction.ts) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/basic-send-action.ts).
    </Note>

    <Tabs>
      <Tab title="Instruction">
        ```typescript theme={null}
        import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
        import { createTransferInterfaceInstructions } from "@lightprotocol/compressed-token/unified";

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

        for (const ixs of instructions) {
          const tx = new Transaction().add(...ixs);
          await sendAndConfirmTransaction(rpc, tx, [payer, owner]);
        }
        ```

        <Accordion title="Compare to SPL">
          ```typescript theme={null}
          import {
            getAssociatedTokenAddressSync,
            createAssociatedTokenAccountIdempotentInstruction,
            createTransferInstruction,
          } from "@solana/spl-token";

          const sourceAta = getAssociatedTokenAddressSync(mint, owner.publicKey);
          const destinationAta = getAssociatedTokenAddressSync(mint, recipient);

          const tx = new Transaction().add(
            createAssociatedTokenAccountIdempotentInstruction(
              payer.publicKey, destinationAta, recipient, mint
            ),
            createTransferInstruction(sourceAta, destinationAta, owner.publicKey, amount)
          );
          ```
        </Accordion>
      </Tab>

      <Tab title="SDK 2.0 (token-interface)">
        ```typescript theme={null}
        import { Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
        import { createTransferInstructions } from "@lightprotocol/token-interface";
        import { rpc, payer, setup } from "../setup.js";

        (async function () {
          const { mint } = await setup();
          const recipient = Keypair.generate();

          const instructions = await createTransferInstructions({
            rpc,
            payer: payer.publicKey,
            mint,
            sourceOwner: payer.publicKey,
            authority: payer.publicKey,
            recipient: recipient.publicKey,
            amount: 100n,
          });

          const tx = new Transaction().add(...instructions);
          const sig = await sendAndConfirmTransaction(rpc, tx, [payer]);
          console.log("Tx:", sig);
        })();
        ```
      </Tab>

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

        const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);

        // Handles loading, creates recipient ATA, transfers.
        await transferInterface(
          rpc,
          payer,
          sourceAta,
          mint,
          recipient,
          owner.publicKey,
          owner,
          amount
        );
        ```

        <Accordion title="Compare to SPL">
          ```typescript theme={null}
          import {
            getAssociatedTokenAddressSync,
            getOrCreateAssociatedTokenAccount,
            transfer,
          } from "@solana/spl-token";

          await getOrCreateAssociatedTokenAccount(connection, payer, mint, recipient);

          const sourceAta = getAssociatedTokenAddressSync(mint, owner.publicKey);
          const destinationAta = getAssociatedTokenAddressSync(mint, recipient);
          await transfer(connection, payer, sourceAta, destinationAta, owner, amount);
          ```
        </Accordion>
      </Tab>
    </Tabs>

    ## Show Balance

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

    ```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="Compare to SPL">
      ```typescript theme={null}
      import { getAccount } from "@solana/spl-token";

      const account = await getAccount(connection, ata);

      console.log(account.amount);
      ```
    </Accordion>

    ## Transaction History

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

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

    Use `getSignaturesForAddressInterface(address)` if you want address-specific rather than owner-wide history.

    <Accordion title="Compare to SPL">
      ```typescript theme={null}
      const signatures = await connection.getSignaturesForAddress(ata);
      ```
    </Accordion>

    ## Wrap from SPL

    Wrap tokens from SPL/Token-2022 accounts to light-token ATA.

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

    <Tabs>
      <Tab title="Instruction">
        ```typescript theme={null}
        import { Transaction } from "@solana/web3.js";
        import { getAssociatedTokenAddressSync } from "@solana/spl-token";
        import {
          createWrapInstruction,
          getAssociatedTokenAddressInterface,
        } from "@lightprotocol/compressed-token/unified";
        import { getSplInterfaceInfos } from "@lightprotocol/compressed-token";

        const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);
        const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);

        const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint);
        const splInterfaceInfo = splInterfaceInfos.find((i) => i.isInitialized);

        const tx = new Transaction().add(
          createWrapInstruction(
            splAta,
            tokenAta,
            owner.publicKey,
            mint,
            amount,
            splInterfaceInfo,
            decimals,
            payer.publicKey
          )
        );
        ```
      </Tab>

      <Tab title="SDK 2.0 (token-interface)">
        ```typescript theme={null}
        import { ComputeBudgetProgram, Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";
        import {
          createAtaInstruction,
          createWrapInstruction,
          getAtaAddress,
          getSplInterfaces,
        } from "@lightprotocol/token-interface";

        const rpc = createRpc();
        const payer = Keypair.fromSecretKey(/* ... */);

        const mint = /* existing SPL/Token-2022 mint */;
        const splAta = /* existing SPL ATA */;
        const lightTokenAta = getAtaAddress({ mint, owner: payer.publicKey });

        const createAtaIx = createAtaInstruction({
          payer: payer.publicKey,
          owner: payer.publicKey,
          mint,
        });

        const splInterfaces = await getSplInterfaces(rpc, mint);
        const splInterface =
          splInterfaces.find((info) => info.isInitialized) ?? splInterfaces[0];
        if (!splInterface) throw new Error("No SPL interface found");

        const wrapIx = createWrapInstruction({
          source: splAta,
          destination: lightTokenAta,
          owner: payer.publicKey,
          mint,
          amount: 500n,
          splInterface,
          decimals: 9,
          payer: payer.publicKey,
        });

        const tx = new Transaction().add(
          ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
          createAtaIx,
          wrapIx
        );
        const signature = await sendAndConfirmTransaction(rpc, tx, [payer]);
        console.log("Tx:", signature);
        ```
      </Tab>

      <Tab title="Action">
        ```typescript theme={null}
        import { getAssociatedTokenAddressSync } from "@solana/spl-token";
        import {
          wrap,
          getAssociatedTokenAddressInterface,
        } from "@lightprotocol/compressed-token/unified";

        const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);
        const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);

        await wrap(rpc, payer, splAta, tokenAta, owner, mint, amount);
        ```
      </Tab>
    </Tabs>

    ## Unwrap to SPL

    Unwrap moves the token balance from a light-token account to a SPL-token account.
    Use this to compose with applications that do not yet support light-token.

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

    <Tabs>
      <Tab title="Instruction">
        ```typescript theme={null}
        import { Transaction } from "@solana/web3.js";
        import { getAssociatedTokenAddressSync } from "@solana/spl-token";
        import { createUnwrapInstructions } from "@lightprotocol/compressed-token/unified";

        const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);

        // Each inner array = one transaction. Handles loading + unwrapping together.
        const instructions = await createUnwrapInstructions(
          rpc,
          splAta,
          owner.publicKey,
          mint,
          amount,
          payer.publicKey
        );

        for (const ixs of instructions) {
          const tx = new Transaction().add(...ixs);
          await sendAndConfirmTransaction(rpc, tx, [payer, owner]);
        }
        ```
      </Tab>

      <Tab title="SDK 2.0 (token-interface)">
        ```typescript theme={null}
        import { Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";
        import { createUnwrapInstruction, getSplInterfaces } from "@lightprotocol/token-interface";

        const rpc = createRpc();
        const payer = Keypair.fromSecretKey(/* ... */);

        const mint = /* existing SPL/Token-2022 mint */;
        const lightTokenAta = /* existing Light ATA */;
        const splAta = /* destination SPL ATA */;

        const splInterfaces = await getSplInterfaces(rpc, mint);
        const splInterface =
          splInterfaces.find((info) => info.isInitialized) ?? splInterfaces[0];
        if (!splInterface) throw new Error("No SPL interface found");

        const unwrapIx = createUnwrapInstruction({
          source: lightTokenAta,
          destination: splAta,
          owner: payer.publicKey,
          mint,
          amount: 500n,
          splInterface,
          decimals: 9,
          payer: payer.publicKey,
        });

        const tx = new Transaction().add(unwrapIx);
        const signature = await sendAndConfirmTransaction(rpc, tx, [payer]);
        console.log("Tx:", signature);
        ```
      </Tab>

      <Tab title="Action">
        ```typescript theme={null}
        import { getAssociatedTokenAddressSync } from "@solana/spl-token";
        import { unwrap } from "@lightprotocol/compressed-token/unified";

        const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);

        await unwrap(rpc, payer, splAta, owner, mint, amount);
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="AI Prompt">
    <Prompt description="Add light-token support to a wallet application" actions={["copy", "cursor"]}>
      {`---
            description: Add light-token support to a wallet application
            allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
            ---

            ## Add light-token support to a wallet application

            Context:
            - Guide: https://zkcompression.com/light-token/wallets/overview
            - 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
            - Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments

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

            ### 1. Index project
            - Grep \`@solana/spl-token|@lightprotocol|createTransferInstruction|getAccount|wallet|signTransaction|signAllTransactions|Connection\` across src/
            - Glob \`**/*.ts\` and \`**/*.tsx\` for project structure
            - Identify: RPC setup, existing token display/balance logic, wallet adapter, transaction signing pattern
            - 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 Instruction and Action tabs for each operation
            - WebFetch skill.md — check for a dedicated skill and resources matching this task
            - TaskCreate one todo per phase below to track progress

            ### 3. Clarify intention
            - AskUserQuestion: what is the goal? (new wallet integration, migrate existing SPL wallet to light-token, add light-token alongside existing SPL)
            - AskUserQuestion: which operations? (receive, send, balance, history, wrap, unwrap — or all)
            - AskUserQuestion: instruction-level API (build your own transactions) or action-level API (high-level, fewer lines)?
            - 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 connection/signer setup is compatible (createRpc with ZK Compression endpoint)
            - Key pattern: import from \`@lightprotocol/compressed-token/unified\` for all interface APIs
            - APIs return TransactionInstruction[][] — handle multi-tx case with signAllTransactions
            - 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: Add light-token support to a wallet application
    allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
    ---

    ## Add light-token support to a wallet application

    Context:
    - Guide: https://zkcompression.com/light-token/wallets/overview
    - 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
    - Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments

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

    ### 1. Index project
    - Grep `@solana/spl-token|@lightprotocol|createTransferInstruction|getAccount|wallet|signTransaction|signAllTransactions|Connection` across src/
    - Glob `**/*.ts` and `**/*.tsx` for project structure
    - Identify: RPC setup, existing token display/balance logic, wallet adapter, transaction signing pattern
    - 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 Instruction and Action tabs for each operation
    - WebFetch skill.md — check for a dedicated skill and resources matching this task
    - TaskCreate one todo per phase below to track progress

    ### 3. Clarify intention
    - AskUserQuestion: what is the goal? (new wallet integration, migrate existing SPL wallet to light-token, add light-token alongside existing SPL)
    - AskUserQuestion: which operations? (receive, send, balance, history, wrap, unwrap — or all)
    - AskUserQuestion: instruction-level API (build your own transactions) or action-level API (high-level, fewer lines)?
    - 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 connection/signer setup is compatible (createRpc with ZK Compression endpoint)
    - Key pattern: import from `@lightprotocol/compressed-token/unified` for all interface APIs
    - APIs return TransactionInstruction[][] — handle multi-tx case with signAllTransactions
    - 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>

## More wallet guides

<CardGroup cols={3}>
  <Card title="Sign with Privy" icon="key" href="/light-token/wallets/privy" />

  <Card title="Sign with Wallet Adapter" icon="wallet" href="/light-token/wallets/wallet-adapter" />

  <Card title="Gasless Transactions" icon="hand-holding-dollar" href="/light-token/wallets/gasless-transactions" />
</CardGroup>

***

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