> ## Documentation Index
> Fetch the complete documentation index at: https://luminouslabs-cc5545c6-swen-add-code-runner.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Toolkit for Stablecoin Payments

> Guide to integrate light-token APIs with comparison to SPL.

export const CodeCompare = ({firstCode = "", secondCode = "", firstLabel = "Light Token", secondLabel = "SPL"}) => {
  const [sliderPercent, setSliderPercent] = useState(0);
  const [isDragging, setIsDragging] = useState(false);
  const [isAnimating, setIsAnimating] = useState(false);
  const containerRef = useRef(null);
  const animationRef = useRef(null);
  const isLightMode = sliderPercent > 50;
  const highlightCode = code => {
    let escaped = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    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(isLightMode ? 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);
    };
  }, []);
  return <>
      <div className="rounded-3xl not-prose mt-4 backdrop-blur-xl border overflow-hidden" style={{
    fontFamily: 'Inter, sans-serif',
    borderColor: '#d4d4d8'
  }}>
        {}
        <div className="flex items-center justify-between px-4 py-3 border-b" style={{
    background: 'linear-gradient(to bottom, #f8f9fa, #f1f3f4)',
    borderColor: '#e4e4e7'
  }}>
          <span className="text-sm font-medium" style={{
    color: '#52525b'
  }}>
            {isLightMode ? secondLabel : firstLabel}
          </span>

          {}
          <div onClick={handleToggle} style={{
    position: 'relative',
    width: '56px',
    height: '28px',
    background: '#e0e0e0',
    borderRadius: '14px',
    boxShadow: 'inset -2px -2px 4px #ffffff, inset 2px 2px 4px #b0b0b0',
    cursor: 'pointer',
    transition: 'background 0.3s ease, box-shadow 0.3s ease'
  }}>
            {}
            <div style={{
    position: 'absolute',
    width: '24px',
    height: '24px',
    background: 'linear-gradient(145deg, #f5f5f5, #e0e0e0)',
    borderRadius: '12px',
    top: '2px',
    left: isLightMode ? '30px' : '2px',
    boxShadow: '-2px -2px 4px #ffffff, 2px 2px 4px #b0b0b0',
    transition: 'all 0.3s ease-in-out',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center'
  }}>
              {}
              <div style={{
    width: '6px',
    height: '6px',
    background: isLightMode ? '#0066ff' : '#999',
    borderRadius: '50%',
    boxShadow: isLightMode ? '0 0 8px 2px #0066ff' : '0 0 4px 1px rgba(0, 0, 0, 0.1)',
    transition: 'all 0.3s ease-in-out'
  }} />
            </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",
    overflowX: "auto"
  }}>
            <div style={{
    display: "grid"
  }}>
              {}
              <pre className="m-0 p-4 text-zinc-700 dark:text-white/80 bg-transparent" style={{
    gridArea: "1/1",
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
    fontSize: "13px",
    lineHeight: "1.6",
    whiteSpace: "pre",
    zIndex: 1
  }} dangerouslySetInnerHTML={{
    __html: highlightCode(firstCode)
  }} />

              {}
              <pre className="m-0 p-4 text-zinc-700 dark:text-white/80 bg-white dark:bg-zinc-900" style={{
    gridArea: "1/1",
    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(secondCode)
  }} />
            </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: "80px",
    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 receive the same stablecoin, just stored more efficiently.

| Creation Cost     | SPL                  | light-token           |
| :---------------- | :------------------- | :-------------------- |
| **Token Account** | \~2,000,000 lamports | \~**11,000** lamports |

### What you will implement

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

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

  <tbody>
    <tr>
      <td>**Get/Create ATA**</td>
      <td>getOrCreateAssociatedTokenAccount()</td>
      <td>getOrCreateAtaInterface()</td>
    </tr>

    <tr>
      <td>**Derive ATA**</td>
      <td>getAssociatedTokenAddress()</td>
      <td>getAssociatedTokenAddressInterface()</td>
    </tr>

    <tr>
      <td>**Transfer**</td>
      <td>transferChecked()</td>
      <td>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>**Entry from SPL**</td>
      <td>N/A</td>
      <td>wrap()</td>
    </tr>

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

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

## Setup

<Snippet file="setup/toolkits-setup.mdx" />

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

import {
  getOrCreateAtaInterface,
  getAtaInterface,
  getAssociatedTokenAddressInterface,
  transferInterface,
  wrap,
  unwrap,
} from "@lightprotocol/compressed-token/unified";

const rpc = createRpc(RPC_ENDPOINT);
```

### Receive Payments

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

    const ata = await getOrCreateAtaInterface(rpc, payer, mint, recipient);
    // Share ata.parsed.address with sender

    console.log(ata.parsed.amount);
    ```

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

      const ata = await getOrCreateAssociatedTokenAccount(
        connection,
        payer,
        mint,
        recipient
      );
      // Share ata.address with sender

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

  <Tab title="Instruction">
    ```typescript theme={null}
    import { Transaction } from "@solana/web3.js";
    import {
      createAssociatedTokenAccountInterfaceIdempotentInstruction,
      createLoadAtaInstructions,
      getAssociatedTokenAddressInterface,
    } from "@lightprotocol/compressed-token/unified";
    import { CTOKEN_PROGRAM_ID } from "@lightprotocol/stateless.js";

    const ata = getAssociatedTokenAddressInterface(mint, recipient);

    const tx = new Transaction().add(
      createAssociatedTokenAccountInterfaceIdempotentInstruction(
        payer.publicKey,
        ata,
        recipient,
        mint,
        CTOKEN_PROGRAM_ID
      ),
      ...(await createLoadAtaInstructions(
        rpc,
        ata,
        recipient,
        mint,
        payer.publicKey
      ))
    );
    ```

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

      const ata = getAssociatedTokenAddressSync(mint, recipient);

      const tx = new Transaction().add(
        createAssociatedTokenAccountInstruction(payer.publicKey, ata, recipient, mint)
      );
      ```
    </Accordion>
  </Tab>
</Tabs>

### Send Payments

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

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

    await transferInterface(
      rpc,
      payer,
      sourceAta,
      mint,
      destinationAta,
      owner,
      amount
    );
    ```

    To ensure your recipient's ATA exists before transferring:

    ```typescript theme={null}
    import {
      getOrCreateAtaInterface,
      transferInterface,
      getAssociatedTokenAddressInterface,
    } from "@lightprotocol/compressed-token/unified";

    // Ensure recipient ATA exists (creates if needed)
    await getOrCreateAtaInterface(rpc, payer, mint, recipient);

    // Then transfer
    const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);
    const destinationAta = getAssociatedTokenAddressInterface(mint, recipient);
    await transferInterface(
      rpc,
      payer,
      sourceAta,
      mint,
      destinationAta,
      owner,
      amount
    );
    ```

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

      // Ensure recipient ATA exists
      await getOrCreateAssociatedTokenAccount(connection, payer, mint, recipient);

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

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

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

    const tx = new Transaction().add(
      ...(await createLoadAtaInstructions(
        rpc,
        sourceAta,
        owner.publicKey,
        mint,
        payer.publicKey
      )),
      createTransferInterfaceInstruction(
        sourceAta,
        destinationAta,
        owner.publicKey,
        amount
      )
    );
    ```

    To ensure your recipient's ATA exists, prepend an idempotent creation instruction:

    ```typescript theme={null}
    import { Transaction } from "@solana/web3.js";
    import {
      createAssociatedTokenAccountInterfaceIdempotentInstruction,
      createLoadAtaInstructions,
      createTransferInterfaceInstruction,
      getAssociatedTokenAddressInterface,
    } from "@lightprotocol/compressed-token/unified";
    import { CTOKEN_PROGRAM_ID } from "@lightprotocol/stateless.js";

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

    const tx = new Transaction().add(
      createAssociatedTokenAccountInterfaceIdempotentInstruction(
        payer.publicKey,
        destinationAta,
        recipient,
        mint,
        CTOKEN_PROGRAM_ID
      ),
      ...(await createLoadAtaInstructions(
        rpc,
        sourceAta,
        owner.publicKey,
        mint,
        payer.publicKey
      )),
      createTransferInterfaceInstruction(
        sourceAta,
        destinationAta,
        owner.publicKey,
        amount
      )
    );
    ```

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

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

      const tx = new Transaction().add(
        createTransferInstruction(sourceAta, destinationAta, owner.publicKey, amount)
      );
      ```

      With idempotent ATA creation:

      ```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>
</Tabs>

### Show Balance

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

```typescript theme={null}
const result = await rpc.getSignaturesForOwnerInterface(owner);

console.log(result.signatures); // All signatures
```

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

When users deposit from a CEX or legacy integration:

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

    // SPL ATA with tokens to wrap
    const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);
    // c-token ATA destination
    const ctokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);

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

  <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 ctokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);

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

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

### Unwrap to SPL

When users need SPL tokens (CEX withdrawal, legacy integration):

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

    // SPL ATA must exist
    const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);

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

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

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

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

    const tx = new Transaction().add(
      ...(await createLoadAtaInstructions(
        rpc,
        ctokenAta,
        owner.publicKey,
        mint,
        payer.publicKey
      )),
      createUnwrapInstruction(
        ctokenAta,
        splAta,
        owner.publicKey,
        mint,
        amount,
        splInterfaceInfo
      )
    );
    ```
  </Tab>
</Tabs>
