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

# FAQ

> Frequently asked questions about light token, PDA accounts, and use cases.

export const lightCreateAtaCode = ['import { createAtaInterface } from "@lightprotocol/compressed-token";', "", "const ata = await createAtaInterface(", "  rpc,", "  payer,", "  mint,", "  owner", ");"].join("\n");

export const splCreateAtaCode = ['import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";', "", "const ata = await getOrCreateAssociatedTokenAccount(", "  connection,", "  payer,", "  mint,", "  owner", ");"].join("\n");

export const RentLifecycleVisualizer = () => {
  const [, setTime] = useState(0);
  const [lamports, setLamports] = useState(0);
  const [isRunning, setIsRunning] = useState(false);
  const [phase, setPhase] = useState("uninitialized");
  const [hasUserClicked, setHasUserClicked] = useState(false);
  const [showControls, setShowControls] = useState(false);
  const containerRef = useRef(null);
  const [isHighlighted, setIsHighlighted] = useState(false);
  const [activeArrows, setActiveArrows] = useState([]);
  const [activeLines, setActiveLines] = useState([]);
  const [isButtonPressed, setIsButtonPressed] = useState(false);
  const [flyingArrows, setFlyingArrows] = useState([]);
  const [floatingAmounts, setFloatingAmounts] = useState([]);
  const [resetCount, setResetCount] = useState(0);
  const [timelineStarted, setTimelineStarted] = useState(false);
  const LAMPORTS_PER_TICK = 60;
  const INITIAL_RENT = 6400;
  const TOPUP_LAMPORTS = 800;
  const TOPUP_THRESHOLD = 800;
  const COLD_THRESHOLD = 400;
  const GREY = {
    r: 161,
    g: 161,
    b: 170
  };
  const RED = {
    r: 227,
    g: 89,
    b: 48
  };
  const BLUE = {
    r: 120,
    g: 140,
    b: 180
  };
  const txLines = [{
    id: 0,
    x1: 5,
    y1: 20,
    x2: 50,
    y2: 50
  }, {
    id: 1,
    x1: 95,
    y1: 15,
    x2: 50,
    y2: 50
  }, {
    id: 2,
    x1: 0,
    y1: 50,
    x2: 50,
    y2: 50
  }, {
    id: 3,
    x1: 100,
    y1: 55,
    x2: 50,
    y2: 50
  }, {
    id: 4,
    x1: 10,
    y1: 85,
    x2: 50,
    y2: 50
  }, {
    id: 5,
    x1: 90,
    y1: 90,
    x2: 50,
    y2: 50
  }, {
    id: 6,
    x1: 50,
    y1: 0,
    x2: 50,
    y2: 50
  }];
  const interpolateColor = (c1, c2, t) => {
    const clamp = v => Math.max(0, Math.min(255, Math.round(v)));
    return {
      r: clamp(c1.r + (c2.r - c1.r) * t),
      g: clamp(c1.g + (c2.g - c1.g) * t),
      b: clamp(c1.b + (c2.b - c1.b) * t)
    };
  };
  const colorToRgba = (c, alpha = 1) => `rgba(${c.r}, ${c.g}, ${c.b}, ${alpha})`;
  const formatLamports = l => {
    if (l <= 0) return "0";
    const rounded = Math.round(l / 500) * 500;
    return `~${rounded.toLocaleString()}`;
  };
  const arrowIdRef = useRef(0);
  const flyingArrowIdRef = useRef(0);
  const floatingAmountIdRef = useRef(0);
  const triggerFlyingArrow = (amount, lineIndex) => {
    const id = flyingArrowIdRef.current++;
    setFlyingArrows(prev => [...prev, id]);
    setTimeout(() => {
      setFlyingArrows(prev => prev.filter(a => a !== id));
    }, 600);
    const amountId = floatingAmountIdRef.current++;
    const line = txLines[lineIndex] || txLines[0];
    setFloatingAmounts(prev => [...prev, {
      id: amountId,
      amount,
      x: line.x1,
      y: line.y1
    }]);
    setTimeout(() => {
      setFloatingAmounts(prev => prev.filter(a => a.id !== amountId));
    }, 800);
  };
  const triggerHighlight = () => {
    setIsHighlighted(true);
    setTimeout(() => setIsHighlighted(false), 500);
    const arrowId = arrowIdRef.current++;
    setActiveArrows(prev => [...prev, arrowId]);
    setTimeout(() => {
      setActiveArrows(prev => prev.filter(id => id !== arrowId));
    }, 500);
  };
  const triggerTransaction = lineIndex => {
    setActiveLines(prev => [...prev, {
      id: lineIndex,
      startTime: Date.now()
    }]);
    setTimeout(() => {
      setActiveLines(prev => prev.filter(l => l.id !== lineIndex));
    }, 500);
  };
  const txLineIndexRef = useRef(0);
  const lastLineIndexRef = useRef(0);
  const getNextLineIndex = () => {
    const index = txLineIndexRef.current;
    txLineIndexRef.current = (txLineIndexRef.current + 1) % txLines.length;
    lastLineIndexRef.current = index;
    return index;
  };
  const getAccountColor = () => {
    if (phase === "uninitialized") return GREY;
    if (phase === "cold") return BLUE;
    if (lamports > TOPUP_THRESHOLD) {
      return RED;
    } else if (lamports > COLD_THRESHOLD) {
      const t = 1 - (lamports - COLD_THRESHOLD) / (TOPUP_THRESHOLD - COLD_THRESHOLD);
      return interpolateColor(RED, BLUE, t);
    } else {
      return BLUE;
    }
  };
  const handleTopup = () => {
    triggerTransaction(getNextLineIndex());
    setIsButtonPressed(true);
    setTimeout(() => setIsButtonPressed(false), 200);
    if (phase === "uninitialized" || phase === "cold" || lamports === 0) {
      setLamports(INITIAL_RENT);
      setPhase("hot");
      triggerHighlight();
      triggerFlyingArrow(INITIAL_RENT, lastLineIndexRef.current);
      return;
    }
    if (lamports < TOPUP_THRESHOLD) {
      setLamports(l => l + TOPUP_LAMPORTS);
      triggerHighlight();
      triggerFlyingArrow(TOPUP_LAMPORTS, lastLineIndexRef.current);
    }
  };
  const txTimesRef = useRef([1.3, 2, 2.7, 3.3, 4, 4.7, 5.3, 6, 6.7, 7.3, 8, 8.7, 9.3, 10.1, 11.5, 12.8, 16, 16.7, 17.3, 18, 18.7, 19.3, 20, 20.7, 21.3, 22, 22.7, 23.3, 24, 24.7, 25.5, 26.8]);
  const handleReset = () => {
    setTime(0);
    setLamports(0);
    setPhase("uninitialized");
    setIsRunning(true);
    setTimelineStarted(false);
    setActiveLines([]);
    setActiveArrows([]);
    setFlyingArrows([]);
    setResetCount(c => c + 1);
    txLineIndexRef.current = 0;
    arrowIdRef.current = 0;
    flyingArrowIdRef.current = 0;
  };
  const handleDiamondClick = () => {
    if (!hasUserClicked) {
      setHasUserClicked(true);
      setIsRunning(true);
      setShowControls(true);
    }
  };
  useEffect(() => {
    if (!isRunning) return;
    const interval = setInterval(() => {
      setTime(t => {
        const newTime = t + 0.1;
        if (t < 1.0 && newTime >= 1.0) {
          setLamports(INITIAL_RENT);
          setPhase("hot");
          setTimelineStarted(true);
          triggerHighlight();
          triggerTransaction(getNextLineIndex());
        }
        txTimesRef.current.forEach(txTime => {
          if (newTime >= txTime && t < txTime) {
            triggerTransaction(getNextLineIndex());
            if (phase === "cold") {
              setLamports(INITIAL_RENT);
              setPhase("hot");
              triggerHighlight();
              triggerFlyingArrow(INITIAL_RENT, lastLineIndexRef.current);
            } else if (phase === "hot") {
              setLamports(currentLamports => {
                if (currentLamports > 0 && currentLamports < TOPUP_THRESHOLD) {
                  triggerHighlight();
                  triggerFlyingArrow(TOPUP_LAMPORTS, lastLineIndexRef.current);
                  return currentLamports + TOPUP_LAMPORTS;
                }
                return currentLamports;
              });
            }
          }
        });
        if ((phase === "hot" || phase === "cold") && newTime > 0.1) {
          setLamports(l => {
            const tickAmount = LAMPORTS_PER_TICK;
            const newLamports = Math.max(0, l - tickAmount);
            if (newLamports < COLD_THRESHOLD) {
              setPhase("cold");
            }
            return newLamports;
          });
        }
        if (newTime >= 29) {
          setPhase("uninitialized");
          setLamports(0);
          setHasUserClicked(false);
          setIsRunning(false);
          setShowControls(false);
          setTimelineStarted(false);
          txLineIndexRef.current = 0;
          return 0;
        }
        return newTime;
      });
    }, 100);
    return () => clearInterval(interval);
  }, [isRunning, phase]);
  const accountColor = getAccountColor();
  const generateDiamondDots = () => {
    const dots = [];
    const size = 5;
    const centerSize = 4;
    for (let row = -size; row <= size; row++) {
      const width = size - Math.abs(row);
      for (let col = -width; col <= width; col++) {
        const distFromCenter = Math.max(Math.abs(row), Math.abs(col));
        const fadeProgress = distFromCenter / size;
        const dotSize = Math.max(0.3, centerSize * Math.pow(1 - fadeProgress, 1.5));
        const opacity = Math.pow(1 - fadeProgress, 2.5);
        dots.push({
          x: 50 + col * 6,
          y: 50 + row * 6,
          size: dotSize,
          opacity
        });
      }
    }
    return dots;
  };
  const diamondDots = generateDiamondDots();
  const isLineActive = lineId => activeLines.some(l => l.id === lineId);
  return <div ref={containerRef} className="relative p-6 my-4 overflow-hidden" style={{
    fontFamily: "'Inter', 'IBM Plex Mono'"
  }}>
      {}
      <style>{`
        @keyframes scrollTimeline {
          from { transform: translateX(15rem); }
          to { transform: translateX(-95rem); }
        }
        .timeline-scroll {
          animation: scrollTimeline 29s linear infinite;
          animation-play-state: paused;
          animation-fill-mode: backwards;
        }
        .timeline-scroll-running {
          animation-play-state: running;
        }
        @keyframes bobbleMove {
          0% { offset-distance: 0%; opacity: 0; }
          10% { opacity: 1; }
          90% { opacity: 1; }
          100% { offset-distance: 100%; opacity: 0; }
        }
        .tx-bobble {
          animation: bobbleMove 0.5s ease-out forwards;
        }
        .btn-interactive {
          position: relative;
          overflow: hidden;
          background: rgba(120, 140, 180, 0.06);
          border: 1px solid rgba(120, 140, 180, 0.2);
          border-bottom-color: rgba(0, 0, 0, 0.08);
          box-shadow: 0 1px 2px rgba(0,0,0,0.05);
          color: rgb(0, 0, 0);
        }
        .dark .btn-interactive {
          background: rgba(120, 140, 180, 0.1);
          border: 1px solid rgba(120, 140, 180, 0.25);
          border-bottom-color: rgba(0, 0, 0, 0.2);
          box-shadow: 0 1px 2px rgba(0,0,0,0.1);
          color: rgb(255, 255, 255);
        }
        .btn-interactive:hover {
          background: rgba(120, 140, 180, 0.1);
          box-shadow: 0 2px 4px rgba(0,0,0,0.08);
        }
        .dark .btn-interactive:hover {
          background: rgba(120, 140, 180, 0.15);
          box-shadow: 0 2px 4px rgba(0,0,0,0.15);
        }
        .btn-interactive:active {
          background: rgba(120, 140, 180, 0.12);
          box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
          transform: translateY(0.5px);
        }
        .dark .btn-interactive:active {
          background: rgba(120, 140, 180, 0.18);
          box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);
        }
        @keyframes arrowUp {
          0% { opacity: 0; transform: translateY(calc(-50% + 4px)); }
          20% { opacity: 1; }
          80% { opacity: 1; }
          100% { opacity: 0; transform: translateY(calc(-50% - 8px)); }
        }
        .arrow-up {
          animation: arrowUp 0.5s ease-out forwards;
        }
        @keyframes arrowFlyUp {
          0% { opacity: 1; transform: translateY(calc(-50% + 24px)); }
          80% { opacity: 1; }
          100% { opacity: 0; transform: translateY(calc(-50% - 8px)); }
        }
        .arrow-fly-up {
          animation: arrowFlyUp 0.4s ease-out forwards;
        }
        @keyframes amountFlyUp {
          0% { opacity: 1; transform: translateY(0); }
          70% { opacity: 1; }
          100% { opacity: 0; transform: translateY(-20px); }
        }
        .amount-fly-up {
          animation: amountFlyUp 0.8s ease-out forwards;
        }
      `}</style>

      {}
      <div className="relative flex items-center justify-center" style={{
    height: "11.5rem"
  }}>
        {}
        <svg className="absolute inset-0 w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet" style={{
    filter: !hasUserClicked ? "blur(2px)" : "none",
    transition: "filter 0.3s ease"
  }}>
          {txLines.map(line => {
    const active = isLineActive(line.id);
    return <g key={line.id}>
                {}
                <line x1={line.x1} y1={line.y1} x2={line.x2} y2={line.y2} stroke={active ? "rgba(161, 161, 170, 0.5)" : "rgba(161, 161, 170, 0)"} strokeWidth={active ? 0.8 : 0} style={{
      transition: "stroke 0.15s, stroke-width 0.15s"
    }} />
                {}
                {active && <circle r="2" fill="rgba(161, 161, 170, 0.8)" style={{
      offsetPath: `path('M ${line.x1} ${line.y1} L ${line.x2} ${line.y2}')`
    }} className="tx-bobble" />}
              </g>;
  })}
          {}
          {floatingAmounts.map(({id, amount, x, y}) => <text key={id} x={x} y={y} className="amount-fly-up" style={{
    fill: "rgb(34, 197, 94)",
    fontSize: "8px",
    fontWeight: 700,
    fontFamily: "ui-monospace, monospace",
    textAnchor: "middle",
    dominantBaseline: "middle"
  }}>
              +{amount.toLocaleString()}
            </text>)}
        </svg>

        {}
        <div className="absolute inset-0 flex items-center justify-center" style={{
    maskImage: "linear-gradient(to right, transparent, black 15%, black 35%, transparent 45%, transparent 55%, black 65%, black 85%, transparent)",
    WebkitMaskImage: "linear-gradient(to right, transparent, black 15%, black 35%, transparent 45%, transparent 55%, black 65%, black 85%, transparent)",
    filter: !hasUserClicked ? "blur(2px)" : "none",
    transition: "filter 0.3s ease"
  }}>
          {}
          <div className="absolute inset-0 flex items-center overflow-hidden">
            <div key={resetCount} className={`flex items-center timeline-scroll ${timelineStarted ? "timeline-scroll-running" : ""}`} style={{
    gap: "5rem"
  }}>
              {[...Array(34).keys()].map(i => i * 3).concat([...Array(34).keys()].map(i => i * 3)).map((h, i) => <div key={i} className="flex flex-col items-center flex-shrink-0">
                    <span className="font-mono text-zinc-300 dark:text-white/20 mb-2" style={{
    fontSize: "1rem",
    opacity: timelineStarted ? 1 : 0,
    filter: timelineStarted ? "blur(0)" : "blur(8px)",
    transition: "opacity 0.5s ease, filter 0.5s ease"
  }}>
                      {h}h
                    </span>
                    <div className="w-px h-3 bg-zinc-200 dark:bg-white/20" />
                  </div>)}
            </div>
          </div>

          {}
          <div className="absolute left-0 right-0 h-px bg-gradient-to-r from-transparent via-zinc-300 dark:via-white/30 to-transparent" />
        </div>

        {}
        <div className="absolute z-10" onClick={handleDiamondClick} style={{
    left: "50%",
    top: "50%",
    transform: "translate(-50%, -50%)",
    cursor: !hasUserClicked ? "pointer" : "default",
    filter: activeLines.length > 0 ? "drop-shadow(0 0 25px rgba(227, 89, 48, 0.7)) drop-shadow(0 0 10px rgba(255, 150, 50, 0.8))" : "none",
    transition: "filter 0.15s ease"
  }}>
          <svg width="138" height="138" viewBox="0 0 100 100">
            {diamondDots.map((dot, i) => <circle key={i} cx={dot.x} cy={dot.y} r={dot.size} fill={colorToRgba(accountColor, dot.opacity * 0.7)} style={{
    transition: "fill 0.3s ease"
  }} />)}
          </svg>
          {}
          {!hasUserClicked && <div className="text-zinc-400 dark:text-white/40 text-center" style={{
    fontSize: "1.15rem",
    position: "absolute",
    top: "100%",
    left: "50%",
    transform: "translateX(-50%)",
    marginTop: "0.5rem",
    whiteSpace: "nowrap"
  }}>
              Press to see the Rent lifecycle over time!
            </div>}
        </div>
      </div>

      {}
      <div style={{
    opacity: showControls ? 1 : 0,
    filter: showControls ? "blur(0px)" : "blur(8px)",
    transition: "opacity 0.5s ease, filter 0.5s ease",
    pointerEvents: showControls ? "auto" : "none"
  }}>
        {}
        <div className="flex justify-center mt-4">
          {}
          <div className="relative flex items-center justify-end mr-2" style={{
    width: "1.5rem",
    height: "2.2rem"
  }}>
            {activeArrows.map(arrowId => <span key={arrowId} className="arrow-up absolute" style={{
    color: "rgb(34, 197, 94)",
    fontSize: "1.7rem",
    right: 0,
    top: "50%",
    transform: "translateY(-50%)",
    lineHeight: 1
  }}>
                ↑
              </span>)}
            {flyingArrows.map(id => <span key={id} className="arrow-fly-up absolute" style={{
    color: "rgb(34, 197, 94)",
    fontSize: "1.7rem",
    right: 0,
    top: "50%",
    lineHeight: 1
  }}>
                ↑
              </span>)}
          </div>
          <div className="text-center">
            <div style={{
    height: "2.2rem",
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
              <span className="font-mono text-zinc-700 dark:text-white/80 transition-all duration-150" style={{
    fontSize: isHighlighted ? "1.9rem" : "1.7rem",
    fontWeight: isHighlighted ? 700 : 500,
    transformOrigin: "center",
    transform: isHighlighted ? "scale(1.05)" : "scale(1)",
    fontVariantNumeric: "tabular-nums",
    minWidth: "6.5rem",
    textAlign: "right"
  }}>
                {formatLamports(lamports)}
              </span>
              <span className="text-zinc-400 dark:text-white/40 transition-all duration-150 ml-1" style={{
    fontSize: isHighlighted ? "1.45rem" : "1.15rem",
    fontWeight: isHighlighted ? 800 : 400
  }}>
                lamports
              </span>
            </div>
            <div className="text-zinc-500 dark:text-white/50 uppercase tracking-wide" style={{
    fontSize: "0.9rem"
  }}>
              Rent Balance
            </div>
          </div>
        </div>

        {}
        <div className="flex justify-center gap-4 mt-3">
          <button onClick={handleReset} className="font-medium rounded-lg border backdrop-blur-sm transition-all btn-interactive" style={{
    padding: "0.5rem 1rem",
    fontSize: "0.85rem"
  }}>
            Back to Start
          </button>
          <button onClick={handleTopup} className={`rounded-lg border-none backdrop-blur-sm transition-all ${isButtonPressed ? "font-bold" : "font-medium"}`} style={{
    padding: "0.5rem 1rem",
    fontSize: isButtonPressed ? "0.95rem" : "0.85rem",
    transform: isButtonPressed ? "scale(1.15)" : "scale(1)",
    background: "#0066ff",
    color: "#fff"
  }}>
            Send Tx
          </button>
        </div>
      </div>
    </div>;
};

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

## Rent Sponsorship

<Accordion title="How does rent-exemption sponsorship work ?">
  Rent sponsorship is a built-in feature of the Light SDK’s that sponsors rent-exemption for all account types to reduce creation cost: mints, token accounts, and PDAs.
  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.

  | Account Creation Cost        | Light               | Standard Solana |
  | :--------------------------- | :------------------ | :-------------- |
  | **Mint account**             | **\~0.00001 SOL**   | \~0.0015 SOL    |
  | **Associated token account** | **\~0.00001 SOL**   | \~0.002 SOL     |
  | **PDA (100-byte)**           | **\~0.0000115 SOL** | \~0.0016 SOL    |

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

  Every Solana account requires a rent-exempt balance in SOL upon creation.
  A rent sponsor PDA by Light Protocol pays the rent-exemption cost for the account so account creators never lock up SOL for rent.

  **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).
  If you're building a wallet or dApp, set your application as the fee payer and your users never pay rent or top-ups.

  <Info>
    "Cold" accounts virtual rent balance below threshold (eg 24h without write bump) get auto-compressed.
    The cold account's state is cryptographically preserved on the Solana ledger.
    Users can load a cold account into hot state in-flight when using the account again.
  </Info>

  ```text theme={null}
      Rent sponsor PDA              Fee payer bumps
      pays rent-exemption           virtual rent on write
           │                              │
           ▼                              │
      ┌─────────┐                    ┌────┴────┐
      │ CREATE  │ ──────────────────►│   HOT   │◄───┐
      └─────────┘                    └────┬────┘    │
                                          │         │
                                    no writes for   │ client loads
                                    threshold       │ cold → hot
                                    (e.g. 24h)      │
                                          │         │
                                          ▼         │
                                     ┌─────────┐    │
                                     │  COLD   │────┘
                                     └─────────┘
  ```
</Accordion>

## Light Token Program

<Accordion title="What is light-token?">
  Light token is a high-performance token standard that is functionally equivalent to SPL, but stores mint and token accounts more efficiently. This reduces account creation cost while being more CU efficient than SPL on hot paths.

  **Creation Cost**

  |                   |     Light-Token |  SPL-Token |
  | :---------------- | --------------: | ---------: |
  | **Mint Account**  | **0.00001 SOL** | 0.0015 SOL |
  | **Token Account** | **0.00001 SOL** |  0.002 SOL |

  **CU Performance**

  |                          | Light-Token | SPL-Token |
  | :----------------------- | ----------: | --------: |
  | **ATA Creation**         |   **4,348** |    14,194 |
  | **Transfer**             |     **312** |     4,645 |
  | **Transfer** (rent-free) |   **1,885** |     4,645 |
</Accordion>

<Accordion title="Can I start using the Light Token Standard?">
  Yes! Light Token is deployed on devnet — start integrating today with our [Quickstart](/light-token/quickstart) and [Toolkits](/light-token/toolkits/overview).

  For production on mainnet, use [Compressed Tokens V1](/compressed-tokens/overview), which are live and supported by leading wallets such as Phantom and Backpack.
</Accordion>

<Accordion title="Do I need to change my client code significantly?">
  No. The `light-token-sdk` methods are a superset of the SPL-token API.
  Find examples below.

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

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

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

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

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

      <tr>
        <td>**Transfer**</td>
        <td>transferInterface()</td>
        <td>transferChecked()</td>
      </tr>

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

<Accordion title="Can light-token accounts hold SPL tokens?">
  Yes, light-token accounts can hold tokens from light, SPL, or Token 2022 mints.

  SPL tokens can be deposited into light-token accounts and withdrawn back to SPL token accounts via the `transferInterface` method.
</Accordion>

<Accordion title="Do light-token accounts require rent?">
  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.

  1. A rent sponsor PDA by Light Protocol pays the rent-exemption cost for the account.
  2. Transaction fee payers bump a virtual rent balance when writing to the account, which keeps the account "hot".
  3. "Cold" accounts virtual rent balance below threshold (eg 24h without write bump) get auto-compressed.
  4. The cold account's state is cryptographically preserved on the Solana ledger.
     Users can load a cold account into hot state in-flight when using the account
     again.

  <RentLifecycleVisualizer />
</Accordion>

<Accordion title="What happens if my light-token account runs out of rent?">
  The account is automatically compressed.
  Your tokens are cryptographically preserved as a compressed token account (rent-free).
  The account is loaded into hot account state in-flight when someone interacts with it again.
</Accordion>

<Accordion title="Does light-token support extensions?">
  Extensions are under development. Additional extensions can be requested.

  Coming soon:

  * MetadataPointer
  * TokenMetadata
  * InterestBearingConfig
  * GroupPointer
  * GroupMemberPointer
  * TokenGroup
  * TokenGroupMember
  * MintCloseAuthority
  * TransferFeeConfig
  * DefaultAccountState
  * PermanentDelegate
  * TransferHook
  * Pausable
  * ConfidentialTransferMint
  * ConfidentialTransferFeeConfig
  * ConfidentialMintBurn
</Accordion>

<Accordion title="What is the difference between light-token and compressed token?">
  * **light-token**: Solana account that holds token balances of light-mints, SPL or Token 22 mints.
  * **Compressed token**: Compressed account storing token data. Rent-free, for storage and distribution.
</Accordion>

***

## PDA Accounts

<Accordion title="What is a Light-PDA?">
  A standard Solana PDA with sponsored rent-exemption. Seeds, bump derivation, and `invoke_signed` work the same way. Your instruction handlers for reads, updates, and closes don't change.

  |                      |  Regular PDA |       Light-PDA |
  | :------------------- | -----------: | --------------: |
  | **100-byte account** | \~0.0016 SOL | \~0.0000115 SOL |
</Accordion>

<Accordion title="What is a compressed PDA?">
  A compressed account with a derived address. Programs invoke the Light System program instead of the System program to create and update compressed accounts. Compressed PDAs are always compressed and require a validity proof for every read and write.

  |                      |  Regular PDA | Compressed PDA |
  | :------------------- | -----------: | -------------: |
  | **100-byte account** | \~0.0016 SOL | \~0.000015 SOL |
</Accordion>

<Accordion title="When should I use Light-PDA vs compressed PDA?">
  |                     | Light-PDA                                                        | Compressed PDA                                                 |
  | :------------------ | :--------------------------------------------------------------- | :------------------------------------------------------------- |
  | **Storage**         | On-chain; auto-compresses when inactive                          | Always compressed                                              |
  | **Validity proof**  | Not required                                                     | Required for every read and write                              |
  | **Program changes** | Minimal changes. Leaves program logic mostly untouched.          | Custom Logic                                                   |
  | **Best for**        | Shared state: DeFi pools, vaults, config, program-owned accounts | Per-user state: profiles, credentials, DePIN nodes, nullifiers |

  <Card title="PDA accounts overview" icon="chevron-right" color="#0066ff" href="/pda/overview" horizontal />
</Accordion>

<Accordion title="Do I need to rewrite my Anchor program to use Light-PDA?">
  No. Add `compression_info: CompressionInfo` to your state struct, derive `LightAccount` and `LightAccounts`, and add `#[light_program]` above `#[program]`. Your instruction logic for reads, updates, and closes stays the same. The client prepends a load instruction if the account is cold.

  <Card title="Light-PDA guide" icon="chevron-right" color="#0066ff" href="/pda/light-pda/overview" horizontal />
</Accordion>

<Accordion title="Do Light-PDAs require rent?">
  The SDK sponsors rent-exemption for your Light-PDAs. After extended inactivity, the account compresses to cold state and the rent-exempt lamports return to the rent sponsor. The common path (hot accounts) has no extra overhead.

  1. A rent sponsor PDA by Light Protocol pays the rent-exemption cost for the account.
  2. Transaction fee payers bump a virtual rent balance when writing to the account, which keeps the account "hot".
  3. "Cold" accounts virtual rent balance below threshold (eg 24h without write bump) get auto-compressed.
  4. The cold account's state is cryptographically preserved on the Solana ledger.
     Users can load a cold account into hot state in-flight when using the account
     again.
</Accordion>

<Accordion title="What happens if my Light-PDA runs out of rent?">
  The account data is cryptographically preserved in compressed state. The client prepends a load instruction when someone interacts with it again — your program code doesn't change. Reads, updates, and closes work the same way regardless of whether the account is hot or cold.
</Accordion>

***

## I don't see the question I want answered

DM us [@lightprotocol on X (Twitter)](http://x.com/lightprotocol) or [Discord](https://discord.com/invite/rpddh53TeG).

<Card title="Learn Core Concepts" icon="graduation-cap" href="/learn/light-token-standard" />
