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

# Payment Flows on Solana with Light Token

> Learn how the Light Token SDK reduces SPL account creation cost for payment infrastructure by 99%.

export const lightCreateAtaCpiCode = ["use light_token::instruction::CreateAssociatedAccountCpi;", "", "CreateAssociatedAccountCpi {", "    payer: payer.clone(),", "    owner: owner.clone(),", "    mint: mint.clone(),", "    ata: associated_token_account.clone(),", "    bump,", "}", ".rent_free(", "    compressible_config.clone(),", "    rent_sponsor.clone(),", "    system_program.clone(),", ")", ".invoke()?"].join("\n");

export const splCreateAtaCpiCode = ["use spl_associated_token_account::instruction::create_associated_token_account;", "", "let ix = create_associated_token_account(", "    &payer.pubkey(),", "    &owner.pubkey(),", "    &mint,", "    &spl_token::id(),", ");", "", "invoke(&ix, &[payer, owner, mint])?;"].join("\n");

export const lightCreateAtaMacroCode = ["#[light_account(", "    init,", "    associated_token::authority = ata_owner,", "    associated_token::mint = ata_mint,", "    associated_token::bump = params.ata_bump", ")]", "pub ata: UncheckedAccount<'info>,"].join("\n");

export const splCreateAtaMacroCode = ["#[account(", "    init,", "    payer = fee_payer,", "    associated_token::mint = mint,", "    associated_token::authority = owner,", ")]", "pub ata: Account<'info, TokenAccount>,"].join("\n");

export const lightCreateAtaRustCode = ["use light_token::instruction::CreateAssociatedTokenAccount;", "", "let ix = CreateAssociatedTokenAccount::new(", "    payer.pubkey(),", "    owner.pubkey(),", "    mint,", ")", ".instruction()?;"].join("\n");

export const splCreateAtaRustCode = ["use spl_associated_token_account::instruction::create_associated_token_account;", "", "let ix = create_associated_token_account(", "    &payer.pubkey(),", "    &owner.pubkey(),", "    &mint,", "    &spl_token::id(),", ");"].join("\n");

export const lightCreateAtaIxCode = ['import { createAssociatedTokenAccountInterfaceInstruction } from "@lightprotocol/compressed-token";', "", "const ix = createAssociatedTokenAccountInterfaceInstruction(", "  payer.publicKey,", "  ata,", "  owner.publicKey,", "  mint", ");"].join("\n");

export const splCreateAtaIxCode = ['import { createAssociatedTokenAccountInstruction } from "@solana/spl-token";', "", "const ix = createAssociatedTokenAccountInstruction(", "  payer.publicKey,", "  ata,", "  owner.publicKey,", "  mint", ");"].join("\n");

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

***

Payment flows on Solana include five core concepts:

1. wallets for sender and recipient
2. stablecoins as transferred asset type (USDC, USDG, ...)
3. token accounts to hold balances
4. fees for transactions and account creation
5. transactions to execute the transfer

Our token APIs optimize the account creation cost component. You typically create an account (1) when onboarding a new user, and (2) when the recipient does not hold the transferred token yet.

|                                     | Light                        | SPL / Token 2022          |
| :---------------------------------- | :--------------------------- | :------------------------ |
| **Transfer**                        | *\~\$0.001*                  | *\~\$0.001*               |
| **Create Token Account**            | **\~\$0.001** (0.000017 SOL) | **\~\$0.29** (0.0029 SOL) |
| **Transfer + Create Token Account** | **\~\$0.001** (0.000017 SOL) | **\~\$0.29** (0.0029 SOL) |

While transfer cost is identical, you can expect \~\$0.29 total when creating a new token account using SPL/Token 2022.
With Light Token, the total cost will be around \$0.001, regardless of whether a new account is created. Your users use the same stablecoins, just stored more efficiently.

<Accordion title="How the cost reduction works">
  The token standard pays rent-exemption cost for you. To prevent griefing, “rent” is paid over time to keep an account in memory. This is dealt with under the hood in a way that doesn’t disrupt the UX of what your users are used to with SPL-token.

  **Rent-exemption: paid by a rent sponsor PDA.**

  **Top-ups: paid by the fee payer.**

  The `feePayer` on the transaction bumps a small virtual rent balance (766 lamports by default)
  on each write to keep the account active (hot balance).
  Set your application as the fee payer so users never interact with SOL.

  **Hot-Cold Lifecycle of Accounts.**

  Accounts get auto-compressed (cold balance) when the virtual rent balance goes below a threshold (eg 24h without write bump).
  The cold account's state is cryptographically preserved on the Solana ledger.
  Users only interact with hot accounts and load the cold balance in-flight when using the account again.

  <div className="block dark:hidden">
    <Frame>
      <img src="https://mintcdn.com/luminouslabs-cc5545c6/c8IlA_YQ_pxDvBKq/images/account-lifecycle.png?fit=max&auto=format&n=c8IlA_YQ_pxDvBKq&q=85&s=279bbee42bce24f419ba8d8c229f3b6e" alt="Account lifecycle" width="1199" height="805" data-path="images/account-lifecycle.png" />
    </Frame>
  </div>

  <div className="hidden dark:block">
    <Frame>
      <img src="https://mintcdn.com/luminouslabs-cc5545c6/c8IlA_YQ_pxDvBKq/images/account-lifecycle-dark.png?fit=max&auto=format&n=c8IlA_YQ_pxDvBKq&q=85&s=b41a6fc1f9c7a9d7df2dbc3e0b03639c" alt="Account lifecycle" width="1182" height="788" data-path="images/account-lifecycle-dark.png" />
    </Frame>
  </div>
</Accordion>

<Info>
  You can abstract fees entirely so users never interact with SOL. See [gasless transactions](/light-token/wallets/gasless-transactions)
  to sponsor rent top-ups and transaction fees.
</Info>

## API Comparison

Transactions with Light Token APIs mirror SPL / Token 2022, so you can build transactions with a similar developer experience.
For example, here is the creation of an associated token account:

<CodeCompare firstCode={lightCreateAtaIxCode} secondCode={splCreateAtaIxCode} firstLabel="light-token" secondLabel="SPL" />

## Interoperability

The Light Token APIs cover every SPL Token operation and adds extra capabilities:

<table>
  <thead>
    <tr>
      <th style={{ width: "25%" }}>Category</th>
      <th>What it does</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>**Cross-program dispatch**</td>
      <td>`Interface` methods (e.g., `transferInterface`) auto-detect the token program and dispatch to SPL, Token 2022, or Light Token.</td>
    </tr>

    <tr>
      <td>**Unified balance**</td>
      <td>`getAtaInterface` returns a unified balance for a given mint, aggregating Light Token (hot + cold), SPL, and Token 2022 sources.</td>
    </tr>

    <tr>
      <td>**Wrap / Unwrap**</td>
      <td>For interoperability with applications that don't support Light Token yet, you can wrap / unwrap SPL or Token 2022 tokens into Light Token associated token accounts and back.</td>
    </tr>
  </tbody>
</table>

## Token Extensions

Token extensions introduce optional features you can add to a token mint or token account through
extra instructions. Light Token supports most Token 2022 extensions.

<Card title="How Extensions work with Light Token" icon="chevron-right" color="#0066ff" href="/light-token/extensions/overview" horizontal />

## Start Integrating

### Payment Skill for Agents

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

### Payment Flows

<CardGroup cols={2}>
  <Card title="Basic payment" icon="arrow-right" href="/light-token/payments/basic-payment" horizontal />

  <Card title="Batch payments" icon="layer-group" href="/light-token/payments/batch-payments" horizontal />

  <Card title="Receive payments" icon="arrow-down" href="/light-token/payments/receive-payments" horizontal />

  <Card title="Payment with memo" icon="memo" href="/light-token/payments/payment-with-memo" horizontal />

  <Card title="Get Balance and Transaction History" icon="magnifying-glass" href="/light-token/payments/verify-payments" horizontal />

  <Card title="Verify Recipients Address" icon="shield-check" href="/light-token/payments/verify-recipient-address" horizontal />

  <Card title="Configure Spend Permissions" icon="check" href="/light-token/payments/spend-permissions" horizontal />

  <Card title="Wrap and Unwrap to or from SPL / Token 2022" icon="rotate" href="/light-token/payments/wrap-unwrap" horizontal />

  <Card title="Nullifier PDA" icon="code-commit" href="/pda/compressed-pdas/nullifier-pda" horizontal />
</CardGroup>

### Fee Abstraction

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

### Signing

<Card title="When using Privy Wallets" icon="key" href="/light-token/wallets/privy" horizontal />

<Card title="When using Wallet Adapter" icon="wallet" href="/light-token/wallets/wallet-adapter" horizontal />

### Go to Production

<Card title="Checklist" icon="rocket" href="/light-token/payments/production-readiness" horizontal />

***

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