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

# Core concepts of the Light Token program

> The Light Token Program is a high performance token program that reduces the cost of account creations by 200x, while being more CU efficient than SPL on hot paths.

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 CompressibleRentCalculator = () => {
  const [hours, setHours] = useState(24);
  const [lamportsPerWrite, setLamportsPerWrite] = useState(766);
  const [showCustomHours, setShowCustomHours] = useState(false);
  const [showCustomLamports, setShowCustomLamports] = useState(false);
  const [showFormula, setShowFormula] = useState(false);
  const DATA_LEN = 272;
  const BASE_RENT = 128;
  const LAMPORTS_PER_BYTE_PER_EPOCH = 1;
  const MINUTES_PER_EPOCH = 90;
  const COMPRESSION_COST = 11000;
  const LAMPORTS_PER_SOL = 1_000_000_000;
  const HOURS_MAX = 36;
  const LAMPORTS_MAX = 6400;
  const numEpochs = Math.ceil(hours * 60 / MINUTES_PER_EPOCH);
  const rentPerEpoch = BASE_RENT + DATA_LEN * LAMPORTS_PER_BYTE_PER_EPOCH;
  const totalPrepaidRent = rentPerEpoch * numEpochs;
  const totalCreationCost = totalPrepaidRent + COMPRESSION_COST;
  const handleHoursChange = value => {
    const num = Math.max(3, Math.min(168, Number.parseInt(value) || 3));
    setHours(num);
  };
  const handleLamportsChange = value => {
    const num = Math.max(0, Math.min(100000, Number.parseInt(value) || 0));
    setLamportsPerWrite(num);
  };
  const hoursPresets = [24];
  const lamportsPresets = [766];
  const SliderMarkers = ({max, step}) => {
    const marks = [];
    for (let i = step; i < max; i += step) {
      const percent = i / max * 100;
      marks.push(<div key={i} className="absolute top-1/2 -translate-y-1/2 w-px h-2 bg-zinc-300 dark:bg-white/30" style={{
        left: `${percent}%`
      }} />);
    }
    return <>{marks}</>;
  };
  return <div className="p-5 rounded-3xl not-prose mt-4 dark:bg-white/5 backdrop-blur-xl border border-black/[0.04] dark:border-white/10 shadow-lg" style={{
    fontFamily: "Inter, sans-serif"
  }}>
      <div className="space-y-5">
        {}
        <div className="space-y-2 px-3">
          <div className="flex justify-between items-center">
            <span className="text-sm text-zinc-700 dark:text-white/80">
              Prepaid Epochs in Hours
            </span>
            <div className="flex items-center gap-1.5">
              {hoursPresets.map(h => <button key={h} onClick={() => {
    setHours(h);
    setShowCustomHours(false);
  }} className={`px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all ${hours === h && !showCustomHours ? "bg-blue-500/20 border-blue-500/50 text-blue-600 dark:text-blue-400" : "bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]"}`}>
                  {h === 24 ? "Default" : `${h}h`}
                </button>)}
              {showCustomHours ? <input type="number" min="3" max="168" value={hours} onChange={e => handleHoursChange(e.target.value)} className="w-16 px-2 py-1 text-right text-xs font-mono font-medium bg-blue-500/10 dark:bg-blue-500/20 border border-blue-500/50 rounded-lg backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50" autoFocus /> : <button onClick={() => setShowCustomHours(true)} className="px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]">
                  Custom
                </button>}
            </div>
          </div>
          <div className="flex items-center">
            <span className="w-1/3 text-xs text-zinc-500 dark:text-white/50 whitespace-nowrap">
              ≈ {(hours * 60 / MINUTES_PER_EPOCH).toFixed(1)} epochs / {hours.toFixed(1)}h
            </span>
            <div className="w-2/3 relative">
              <SliderMarkers max={HOURS_MAX} step={2} />
              <input type="range" min="3" max={HOURS_MAX} value={Math.min(hours, HOURS_MAX)} onChange={e => {
    setHours(Number.parseInt(e.target.value));
    setShowCustomHours(false);
  }} className="relative w-full h-1.5 bg-black/[0.03] dark:bg-white/20 rounded-full appearance-none cursor-pointer backdrop-blur-sm z-10" />
            </div>
          </div>
        </div>

        {}
        <div className="space-y-2 px-3">
          <div className="flex justify-between items-center">
            <span className="text-sm text-zinc-700 dark:text-white/80">Lamports per Write</span>
            <div className="flex items-center gap-1.5">
              {lamportsPresets.map(l => <button key={l} onClick={() => {
    setLamportsPerWrite(l);
    setShowCustomLamports(false);
  }} className={`px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all ${lamportsPerWrite === l && !showCustomLamports ? "bg-blue-500/20 border-blue-500/50 text-blue-600 dark:text-blue-400" : "bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]"}`}>
                  {l === 766 ? "Default" : l.toLocaleString()}
                </button>)}
              {showCustomLamports ? <input type="number" min="0" max="100000" value={lamportsPerWrite} onChange={e => handleLamportsChange(e.target.value)} className="w-20 px-2 py-1 text-right text-xs font-mono font-medium bg-blue-500/10 dark:bg-blue-500/20 border border-blue-500/50 rounded-lg backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50" autoFocus /> : <button onClick={() => setShowCustomLamports(true)} className="px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]">
                  Custom
                </button>}
            </div>
          </div>
          <div className="flex items-center">
            <span className="w-1/3 text-xs text-zinc-500 dark:text-white/50 whitespace-nowrap">
              ≈ {(lamportsPerWrite / rentPerEpoch).toFixed(1)} epochs /{" "}
              {(lamportsPerWrite / rentPerEpoch * MINUTES_PER_EPOCH / 60).toFixed(1)}h
            </span>
            <div className="w-2/3 relative">
              <SliderMarkers max={LAMPORTS_MAX} step={800} />
              <input type="range" min="0" max={LAMPORTS_MAX} step="100" value={Math.min(lamportsPerWrite, LAMPORTS_MAX)} onChange={e => {
    setLamportsPerWrite(Number.parseInt(e.target.value));
    setShowCustomLamports(false);
  }} className="relative w-full h-1.5 bg-black/[0.03] dark:bg-white/20 rounded-full appearance-none cursor-pointer backdrop-blur-sm z-10" />
            </div>
          </div>
        </div>

        {}
        <div className="grid grid-cols-2 gap-3">
          <div className="p-4 bg-black/[0.015] dark:bg-white/5 backdrop-blur-md rounded-2xl text-center border border-black/[0.04] dark:border-white/10 shadow-sm">
            <div className="text-xs text-zinc-500 dark:text-white/50 mb-1 uppercase tracking-wide">
              Total Creation Cost
            </div>
            <div className="text-xl font-mono font-semibold text-zinc-900 dark:text-white">
              {totalCreationCost.toLocaleString()}
            </div>
            <div className="text-xs text-zinc-400 dark:text-white/40">lamports</div>
            <div className="text-xs text-zinc-500 dark:text-white/50 mt-1">
              ≈ {(totalCreationCost / LAMPORTS_PER_SOL).toFixed(6)} SOL
            </div>
          </div>

          <div className="p-4 bg-black/[0.015] dark:bg-white/5 backdrop-blur-md rounded-2xl text-center border border-black/[0.04] dark:border-white/10 shadow-sm">
            <div className="text-xs text-zinc-500 dark:text-white/50 mb-1 uppercase tracking-wide">
              Top-up Amount
            </div>
            <div className="text-xl font-mono font-semibold text-zinc-900 dark:text-white">
              {lamportsPerWrite.toLocaleString()}
            </div>
            <div className="text-xs text-zinc-400 dark:text-white/40">lamports</div>
            <div className="text-xs text-zinc-500 dark:text-white/50 mt-1">
              ≈ {(lamportsPerWrite / LAMPORTS_PER_SOL).toFixed(6)} SOL
            </div>
          </div>
        </div>

        {}
        <div className="pt-3 border-t border-black/[0.04] dark:border-white/10">
          <button onClick={() => setShowFormula(!showFormula)} className="flex items-center gap-2 text-xs text-zinc-500 dark:text-white/50 hover:text-zinc-700 dark:hover:text-white/70 transition-colors">
            <span className={`transition-transform ${showFormula ? "rotate-90" : ""}`}>▶</span>
            Show formula
          </button>
          {showFormula && <div className="text-xs font-mono text-zinc-500 dark:text-white/40 mt-3">
              <div className="text-zinc-600 dark:text-white/60 mb-2">
                Total cost for {DATA_LEN}-byte light-token account:
              </div>
              total_creation_cost = prepaid_rent + compression_cost
              <br />
              <br />
              rent_per_epoch = base_rent + (data_len × lamports_per_byte_per_epoch)
              <br />
              rent_per_epoch = {BASE_RENT} + ({DATA_LEN} × {LAMPORTS_PER_BYTE_PER_EPOCH}) ={" "}
              {rentPerEpoch} lamports
              <br />
              compression_cost = {COMPRESSION_COST.toLocaleString()} lamports
            </div>}
        </div>
      </div>
    </div>;
};

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

      <th style={{ width: "22%", textAlign: "left" }}>Account Type</th>
      <th style={{ width: "63%", textAlign: "left" }}>Key Features</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td style={{ textAlign: "left" }}>**[Mint Accounts](#mint-accounts)**</td>
      <td style={{ textAlign: "left" }}>Solana Account</td>

      <td style={{ textAlign: "left" }}>
        <ul>
          <li>Represents a unique mint and optionally can store token-metadata.</li>
          <li>Functionally equivalent to SPL mints.</li>
          <li>Rent-exemption cost covered by the Light Token Program</li>
        </ul>
      </td>
    </tr>

    <tr>
      <td style={{ textAlign: "left" }}>**[Token Accounts](#token-account)**</td>
      <td style={{ textAlign: "left" }}>Solana account</td>

      <td style={{ textAlign: "left" }}>
        <ul>
          <li>Stores token balances of mints (SPL, Token-2022, or Light)</li>
          <li>Rent-exemption cost covered by the Light Token Program</li>
        </ul>
      </td>
    </tr>
  </tbody>
</table>

## Rent Config by Light Token Program

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

# Mint Accounts

<Note>
  Light mints are on-chain accounts like SPL mints, but with rent-exemption paid for by the Token
  program, instead of the user.
</Note>

Light-mints **uniquely represent a token on Solana and store its global metadata**, similar to SPL mint accounts with few core differences:

1. Tokens created from light-mints are light-tokens.
2. Token metadata (name, symbol, URI) is stored as an extension in the struct.

| Creation Cost    |      Light Token |  SPL-Token |
| :--------------- | ---------------: | ---------: |
| **Mint Account** | **0.000091 SOL** | 0.0015 SOL |

<Tabs>
  <Tab title="Diagram">
    <Frame>
      <img src="https://mintcdn.com/luminouslabs-cc5545c6/readDj-_0WEtAzhA/images/mint-account-layout.png?fit=max&auto=format&n=readDj-_0WEtAzhA&q=85&s=fb641a8016725929acfb8c673e90a06e" alt="Diagram showing light-mint account structure with three components: Address (identifier for light-mint in purple box), Account (struct containing BaseMint with SPL-compatible fields, MintMetadata for program state, and optional Extensions for Token Metadata), and BaseMint (containing Supply, Decimals, Mint Authority, and Freeze Authority fields) with Token Metadata extension" width="6288" height="2776" data-path="images/mint-account-layout.png" />
    </Frame>
  </Tab>

  <Tab title="Source Code">
    ```rust theme={null}
    pub struct Mint {
        pub base: BaseMint,
        pub metadata: MintMetadata,
        /// Reserved bytes (16 bytes) for T22 layout compatibility.
        /// Positions account_type at offset 165: 82 (BaseMint) + 67 (metadata) + 16 (reserved) = 165.
        pub reserved: [u8; 16],
        /// Account type discriminator at byte 165 (1 = Mint, 2 = Account)
        pub account_type: u8,
        /// Compression info embedded directly in the mint
        pub compression: CompressionInfo,
        pub extensions: Option<Vec<ExtensionStruct>>,
    }
    ```
  </Tab>
</Tabs>

<Info>
  Find the [source code of light-mints
  here](https://github.com/Lightprotocol/light-protocol/blob/main/program-libs/token-interface/src/state/mint/compressed_mint.rs#L20).
</Info>

The `metadata` field is used by the Light Token Program to store the internal state of a light-mint.

The `BaseMint` field replicates the field layout and serialization format of [SPL Mint accounts](https://solana.com/docs/tokens#mint-account). The struct is serialized with Borsh to match the on-chain format of SPL tokens and mints.

Here is how light-mints and SPL mints compare:

<Tabs>
  <Tab title="Basemint vs SPL mint">
    <table>
      <thead>
        <tr>
          <th style={{width: '10%', textAlign: 'left'}}>Field</th>
          <th style={{width: '45%', textAlign: 'center'}}>Light-Mint</th>
          <th style={{width: '30%', textAlign: 'center'}}>SPL Mint</th>
        </tr>
      </thead>

      <tbody>
        <tr>
          <td>mint\_authority</td>
          <td style={{textAlign: 'center'}}>✓</td>
          <td style={{textAlign: 'center'}}>✓</td>
        </tr>

        <tr>
          <td>supply</td>
          <td style={{textAlign: 'center'}}>✓</td>
          <td style={{textAlign: 'center'}}>✓</td>
        </tr>

        <tr>
          <td>decimals</td>
          <td style={{textAlign: 'center'}}>✓</td>
          <td style={{textAlign: 'center'}}>✓</td>
        </tr>

        <tr>
          <td>is\_initialized</td>
          <td style={{textAlign: 'center'}}>✓</td>
          <td style={{textAlign: 'center'}}>✓</td>
        </tr>

        <tr>
          <td>freeze\_authority</td>
          <td style={{textAlign: 'center'}}>✓</td>
          <td style={{textAlign: 'center'}}>✓</td>
        </tr>

        <tr>
          <td>Light-Mint Data</td>
          <td style={{textAlign: 'center'}}>✓</td>
          <td style={{textAlign: 'center'}}>-</td>
        </tr>

        <tr>
          <td>Extensions</td>
          <td style={{textAlign: 'center'}}>✓</td>
          <td style={{textAlign: 'center'}}>via Token-2022</td>
        </tr>
      </tbody>
    </table>
  </Tab>

  <Tab title="BaseMint Struct">
    ```rust theme={null}
    pub struct BaseMint {
        /// Optional authority used to mint new tokens. The mint authority may only
        /// be provided during mint creation. If no mint authority is present
        /// then the mint has a fixed supply and no further tokens may be
        /// minted.
        pub mint_authority: Option<Pubkey>,
        /// Total supply of tokens.
        pub supply: u64,
        /// Number of base 10 digits to the right of the decimal place.
        pub decimals: u8,
        /// Is initialized - for SPL compatibility
        pub is_initialized: bool,
        /// Optional authority to freeze token accounts.
        pub freeze_authority: Option<Pubkey>,
    }
    ```
  </Tab>
</Tabs>

# Token Account

<Note>
  Light token accounts are on-chain accounts like SPL token accounts, but with rent-exemption paid
  for by the Token program, instead of the user.
</Note>

A light-token account holds token balances like SPL Token accounts:

* A wallet needs a light-token account for each light-mint, SPL mint, or Token 2022 mint it wants to hold, with the wallet address set as the light-token account owner.
* Each wallet can own multiple light-token accounts for the same light-mint.
* A light-token account can only have one owner and hold units of one light-mint.

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

Additionally Light Token is more compute-efficient than SPL on hot paths:

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

<Tabs>
  <Tab title="Diagram">
    <Frame>
      <img src="https://mintcdn.com/luminouslabs-cc5545c6/GrN8QAE9hGIT9r5Q/images/token-account-layout.png?fit=max&auto=format&n=GrN8QAE9hGIT9r5Q&q=85&s=03f28f31a61e6ff2aea963105a8880e9" alt="Diagram showing light-token Solana account structure with three components: Address (identifier for light-token account in purple box), Account (struct containing Data bytes, Executable flag, Lamports balance, and Owner set to Light Token Program), and AccountData (containing Mint, Owner, Amount, and Extensions fields)" width="6304" height="2527" data-path="images/token-account-layout.png" />
    </Frame>
  </Tab>

  <Tab title="Source Code">
    ```rust theme={null}
    /// Ctoken account structure (same as SPL Token Account but with extensions).
    pub struct Token {
        pub mint: Pubkey,
        pub owner: Pubkey,
        pub amount: u64,
        pub delegate: Option<Pubkey>,
        pub state: AccountState,
        pub is_native: Option<u64>,
        pub delegated_amount: u64,
        pub close_authority: Option<Pubkey>,
        pub account_type: u8,
        pub extensions: Option<Vec<ExtensionStruct>>,
    }
    ```
  </Tab>
</Tabs>

<Info>
  Find the [source code of light-tokens
  here](https://github.com/Lightprotocol/light-protocol/blob/main/program-libs/token-interface/src/state/token/token_struct.rs#L34).
</Info>

Light token accounts replicate the field layout and serialization format of [SPL Token accounts](https://solana.com/docs/tokens#token-account). The struct is serialized with Borsh to match the on-chain format of SPL tokens.

Here is how light-tokens and SPL tokens compare:

<table>
  <thead>
    <tr>
      <th style={{ width: "10%", textAlign: "left" }}>Field</th>
      <th style={{ width: "45%", textAlign: "center" }}>Light Token</th>
      <th style={{ width: "30%", textAlign: "center" }}>SPL Token Account</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>mint</td>
      <td style={{ textAlign: "center" }}>✓</td>
      <td style={{ textAlign: "center" }}>✓</td>
    </tr>

    <tr>
      <td>owner</td>
      <td style={{ textAlign: "center" }}>✓</td>
      <td style={{ textAlign: "center" }}>✓</td>
    </tr>

    <tr>
      <td>amount</td>
      <td style={{ textAlign: "center" }}>✓</td>
      <td style={{ textAlign: "center" }}>✓</td>
    </tr>

    <tr>
      <td>delegate</td>
      <td style={{ textAlign: "center" }}>✓</td>
      <td style={{ textAlign: "center" }}>✓</td>
    </tr>

    <tr>
      <td>state</td>
      <td style={{ textAlign: "center" }}>✓</td>
      <td style={{ textAlign: "center" }}>✓</td>
    </tr>

    <tr>
      <td>is\_native</td>
      <td style={{ textAlign: "center" }}>✓</td>
      <td style={{ textAlign: "center" }}>✓</td>
    </tr>

    <tr>
      <td>delegated\_amount</td>
      <td style={{ textAlign: "center" }}>✓</td>
      <td style={{ textAlign: "center" }}>✓</td>
    </tr>

    <tr>
      <td>close\_authority</td>
      <td style={{ textAlign: "center" }}>✓</td>
      <td style={{ textAlign: "center" }}>✓</td>
    </tr>

    <tr>
      <td>extensions</td>
      <td style={{ textAlign: "center" }}>✓</td>
      <td style={{ textAlign: "center" }}>via Token-2022</td>
    </tr>
  </tbody>
</table>

# Associated Light Token Account

**Associated light-token** accounts (light-ATAs) follow the same pattern as [associated token accounts](https://solana.com/docs/tokens#associated-token-account) (ATA):

* Each wallet needs its own light-token account to hold tokens from the same light-mint.
* The address for light-ATAs is deterministically derived with the owner's address, light token program ID, and mint address.

```rust theme={null}
let seeds = [
    owner.as_ref(),          // Wallet address (32 bytes)
    program_id.as_ref(),     // Light Token Program ID (32 bytes)
    mint.as_ref(),           // light-mint address (32 bytes)
    bump.as_ref(),           // Bump seed (1 byte)
];
```

<Info>
  Find the [source code to associated light-token accounts
  here](https://github.com/Lightprotocol/light-protocol/blob/main/programs/compressed-token/program/src/ctoken/create_ata.rs).
</Info>

# Compressed Token Account

Under the hood, compressed token accounts store token balance, owner, and other information of inactive light-tokens.

1. Light token accounts are automatically compressed/decompressed when active/inactive.
2. Any light-token or SPL token can be compressed/decompressed at will.

<Note>You can still use compressed tokens for [token distribution](/token-distribution).</Note>

<Tabs>
  <Tab title="Diagram">
    <Frame>
      <img src="https://mintcdn.com/luminouslabs-cc5545c6/71xq4qzgNsL3Pf0n/images/compressed-token-explainer.png?fit=max&auto=format&n=71xq4qzgNsL3Pf0n&q=85&s=c83ef5c3ce470595b6915f1978f017b8" alt="Diagram showing compressed token account structure with three components: Hash (identifier for compressed token account in purple box), Account (struct containing Data bytes, Executable flag, Lamports balance, and Address set to None), and AccountData (containing Mint, Owner, Amount, and Extensions fields)" width="6296" height="2558" data-path="images/compressed-token-explainer.png" />
    </Frame>
  </Tab>

  <Tab title="Source Code">
    ```rust theme={null}
    pub struct TokenData {
        pub mint: Pubkey,
        pub owner: Pubkey,
        pub amount: u64,
        pub delegate: Option<Pubkey>,
        pub state: u8,
        /// Extensions for the compressed token account
        pub tlv: Option<Vec<ExtensionStruct>>,
    }
    ```
  </Tab>
</Tabs>

# Next Steps

<Card title="Create Light Token Accounts" icon="chevron-right" color="#0066ff" href="/light-token/cookbook/create-token-account" horizontal />
