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

# FAQ

> Frequently asked questions about light-token.

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 = 58.2;
  const INITIAL_RENT = 6208;
  const TOPUP_LAMPORTS = 776;
  const TOPUP_THRESHOLD = 776;
  const COLD_THRESHOLD = 388;
  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 Config 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"}) => {
  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>
    </>;
};

#### What is light-token?

Light token is like SPL token stored more efficiently to reduce account creation cost while being more CU efficient than SPL on hot paths.
Users receive and send the same tokens.

| Creation Cost     | SPL                  | Light                 |
| :---------------- | :------------------- | :-------------------- |
| **Mint Account**  | \~1,500,000 lamports | **15,000** lamports   |
| **Token Account** | \~2,000,000 lamports | \~**11,000** lamports |

|                                   | Light Token CU | SPL-Token CU |
| :-------------------------------- | :------------- | :----------- |
| **ATA Creation**                  | 4,348          | 14,194       |
| **Transfer** (base path)          | 312            | 4,645        |
| **Transfer** (rent-free hot path) | 1,885          | 4,645        |

#### Can I start using the Light Token Standard?

Light token is currently deployed on devnet and undergoing security audits.

#### Do I need to change my client code significantly?

No. The `light-token-sdk` methods follow similar patterns to SPL.
Reveal the interface method in the code example and find a few examples below.

<CodeCompare
  firstCode={`// SPL create ATA
import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";

const ata = await getOrCreateAssociatedTokenAccount(
connection,
payer,
mint,
owner
);`}
  secondCode={`// light-token create ATA
import { createAtaInterface } from "@lightprotocol/compressed-token";

const ata = await createAtaInterface(
rpc,
payer,
mint,
owner
);`}
  firstLabel="SPL"
  secondLabel="light-token"
/>

<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>
  </tbody>
</table>

***

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

#### Do light-token accounts require rent?

Rent is paid over time when accounts are accessed via a custom rent config instead of the full rent-exemption upfront:

1. The rent-exemption for light-token account creation is sponsored by Light Protocol.
2. Transaction payer's pay rent <Tooltip tip="1 rent-epoch ≈ 1.5h ≈ 13,500 slots at 400ms per slot">per rent-epoch (388 lamports for 1.5h)</Tooltip> <br />
   to keep accounts "active".
3. "Inactive" accounts, where rent is below one epoch, are compressed <br />and the rent-exemption can be claimed by the rent sponsor.
4. Transfers to inactive accounts <Tooltip tip="Compressed state is stored in leaves of Merkle trees (≃ disk). light-token accounts are Solana accounts that store state on chain (≃ RAM). Load means to load state from 'disk' to 'RAM', i.e. decompress accounts.">"load" it with the same state </Tooltip> (decompress).

This way rent is automatically paid when accounts are used:

<table>
  <thead>
    <tr>
      <th style={{textAlign: 'left'}}>Event</th>
      <th style={{textAlign: 'left'}}>Total Cost</th>
      <th style={{textAlign: 'left'}}>Payer</th>
      <th style={{textAlign: 'left'}}>Time of Rent funded</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td><strong>Account Creation</strong></td>
      <td><strong><Tooltip tip="6,208 lamports (24h rent) + 11,000 lamports (compression cost & protocol incentive)">\~17,000 lamports</Tooltip></strong></td>
      <td>Transaction payer</td>
      <td>Funds 24h rent</td>
    </tr>

    <tr>
      <td><strong>Automatic Top ups</strong><br />(when rent \< 3h)</td>
      <td><strong>776 lamports</strong></td>
      <td>Transaction payer</td>
      <td>Funds 3h rent</td>
    </tr>

    <tr>
      <td><strong>Load Account</strong><br />(when inactive)</td>
      <td><strong><Tooltip tip="6,208 lamports (24h rent) + 11,000 lamports (compression cost & protocol incentive)">\~17,000 lamports</Tooltip></strong></td>
      <td>Transaction payer</td>
      <td>Funds 24h rent</td>
    </tr>
  </tbody>
</table>

<RentLifecycleVisualizer />

#### What happens if my light-token account runs out of rent?

The account is automatically compressed.
Your tokens are preserved as a compressed token account (rent-free).
The account is automatically decompressed when someone interacts with it again.

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

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

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

### Get Started

<CardGroup cols={2}>
  <Card title="Toolkits" icon="toolbox" href="/light-token/toolkits">
    Guides for dedicated use cases
  </Card>

  <Card title="Cookbook" icon="book" href="/light-token/cookbook">
    Step-by-step recipes
  </Card>
</CardGroup>
