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

# Create Mint Account with Token Metadata

> Create a Light mint with token metadata, or create SPL and Token 2022 mints with interface PDA for interoperability.

export const lightCreateMintMetadataCpiCode = ["use light_token::instruction::CreateMintCpi;", "", "let extensions = Some(vec![", "    ExtensionInstructionData::TokenMetadata(", "        TokenMetadataInstructionData {", "            update_authority: Some(authority.key.to_bytes().into()),", '            name: b"Example Token".to_vec(),', '            symbol: b"EXT".to_vec(),', '            uri: b"https://example.com/metadata.json".to_vec(),', "            additional_metadata: None,", "        },", "    ),", "]);", "", "CreateMintCpi {", "    mint_seed: mint_seed.clone(),", "    authority: authority.clone(),", "    payer: payer.clone(),", "    address_tree: address_tree.clone(),", "    output_queue: output_queue.clone(),", "    compressible_config: compressible_config.clone(),", "    mint: mint.clone(),", "    rent_sponsor: rent_sponsor.clone(),", "    system_accounts,", "    cpi_context: None,", "    cpi_context_account: None,", "    params, // includes extensions", "}", ".invoke()?"].join("\n");

export const splCreateMintMetadataCpiCode = ["use spl_token_2022::instruction::initialize_mint;", "use spl_token_metadata_interface::instruction::initialize as init_metadata;", "", "let ix_mint = initialize_mint(", "    &spl_token_2022::id(),", "    &mint.pubkey(),", "    &mint_authority,", "    Some(&freeze_authority),", "    decimals,", ")?;", "invoke(&ix_mint, &[mint.clone(), rent_sysvar.clone()])?;", "", "let ix_meta = init_metadata(", "    &spl_token_2022::id(),", "    &mint.pubkey(),", "    &update_authority,", "    &mint.pubkey(),", "    &mint_authority,", "    name, symbol, uri,", ")?;", "invoke(&ix_meta, &[mint, update_auth])?;"].join("\n");

export const lightCreateMintCpiCode = ["use light_token::instruction::CreateMintCpi;", "", "CreateMintCpi {", "    mint_seed: mint_seed.clone(),", "    authority: authority.clone(),", "    payer: payer.clone(),", "    address_tree: address_tree.clone(),", "    output_queue: output_queue.clone(),", "    compressible_config: compressible_config.clone(),", "    mint: mint.clone(),", "    rent_sponsor: rent_sponsor.clone(),", "    system_accounts,", "    cpi_context: None,", "    cpi_context_account: None,", "    params,", "}", ".invoke()?"].join("\n");

export const splCreateMintCpiCode = ["use spl_token::instruction::initialize_mint;", "", "let ix = initialize_mint(", "    &spl_token::id(),", "    &mint.pubkey(),", "    &mint_authority,", "    Some(&freeze_authority),", "    decimals,", ")?;", "", "invoke(&ix, &[mint, rent_sysvar])?;"].join("\n");

export const lightCreateMintMetadataMacroCode = ["#[light_account(init,", "    mint::signer = mint_signer,", "    mint::authority = fee_payer,", "    mint::decimals = 9,", "    mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],", "    mint::bump = params.mint_signer_bump,", "    mint::name = params.name.clone(),", "    mint::symbol = params.symbol.clone(),", "    mint::uri = params.uri.clone(),", "    mint::update_authority = authority", ")]", "pub mint: UncheckedAccount<'info>,"].join("\n");

export const splCreateMintMetadataMacroCode = ["#[account(", "    init,", "    payer = fee_payer,", "    mint::decimals = 9,", "    mint::authority = fee_payer,", "    extensions::metadata_pointer::authority = fee_payer,", "    extensions::metadata_pointer::metadata_address = mint_account,", ")]", "pub mint_account: InterfaceAccount<'info, Mint>,", "", "// Metadata requires a separate CPI:", "token_metadata_initialize(", "    cpi_ctx,", "    params.name,", "    params.symbol,", "    params.uri,", ")?;"].join("\n");

export const lightCreateMintMacroCode = ["#[light_account(init,", "    mint::signer = mint_signer,", "    mint::authority = fee_payer,", "    mint::decimals = 9,", "    mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],", "    mint::bump = params.mint_signer_bump", ")]", "pub mint: UncheckedAccount<'info>,"].join("\n");

export const splCreateMintMacroCode = ["#[account(", "    init,", "    payer = fee_payer,", "    mint::decimals = 9,", "    mint::authority = fee_payer,", ")]", "pub mint: InterfaceAccount<'info, Mint>,"].join("\n");

export const lightCreateMintRustCode = ["use light_token::instruction::CreateMint;", "", "let ix = CreateMint::new(", "    params,", "    mint_seed.pubkey(),", "    payer.pubkey(),", "    address_tree.tree,", "    output_queue,", ")", ".instruction()?;"].join("\n");

export const splCreateMintRustCode = ["use spl_token::instruction::initialize_mint;", "", "let ix = initialize_mint(", "    &spl_token::id(),", "    &mint.pubkey(),", "    &mint_authority,", "    Some(&freeze_authority),", "    decimals,", ")?;"].join("\n");

export const lightCreateMintCode = ['import { createMintInterface } from "@lightprotocol/compressed-token";', "", "const { mint } = await createMintInterface(", "  rpc,", "  payer,", "  mintAuthority,", "  freezeAuthority,", "  decimals,", "  mintKeypair", ");"].join("\n");

export const splCreateMintCode = ['import { createMint } from "@solana/spl-token";', "", "const mint = await createMint(", "  connection,", "  payer,", "  mintAuthority,", "  freezeAuthority,", "  decimals", ");"].join("\n");

export const CodeCompare = ({firstCode = "", secondCode = "", firstLabel = "Light Token", secondLabel = "SPL", language = "javascript"}) => {
  const [sliderPercent, setSliderPercent] = useState(100);
  const [isDragging, setIsDragging] = useState(false);
  const [isAnimating, setIsAnimating] = useState(false);
  const [copied, setCopied] = useState(false);
  const containerRef = useRef(null);
  const animationRef = useRef(null);
  const firstPreRef = useRef(null);
  const secondPreRef = useRef(null);
  const [containerHeight, setContainerHeight] = useState(null);
  const showingFirst = sliderPercent > 50;
  const handleCopy = async () => {
    const codeToCopy = showingFirst ? firstCode : secondCode;
    await navigator.clipboard.writeText(codeToCopy);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };
  const highlightCode = code => {
    let escaped = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    if (language === "rust") {
      const rustPattern = /(\/\/.*$)|(["'])(?:(?!\2)[^\\]|\\.)*?\2|\b(use|let|mut|pub|fn|struct|impl|enum|mod|const|static|trait|type|where|for|in|if|else|match|loop|while|return|self|Self|true|false|Some|None|Ok|Err|Result|Option|vec!)\b|::([a-zA-Z_][a-zA-Z0-9_]*)|&amp;([a-zA-Z_][a-zA-Z0-9_]*)|\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()|(\?)/gm;
      return escaped.replace(rustPattern, (match, comment, stringQuote, keyword, pathSegment, reference, func, questionMark) => {
        if (comment) return `<span style="color:#6b7280;font-style:italic">${match}</span>`;
        if (stringQuote) return `<span style="color:#059669">${match}</span>`;
        if (keyword) return `<span style="color:#db2777">${match}</span>`;
        if (pathSegment) return `::<span style="color:#0891b2">${pathSegment}</span>`;
        if (reference) return `&amp;<span style="color:#6366f1">${reference}</span>`;
        if (func) return `<span style="color:#2563eb">${match}</span>`;
        if (questionMark) return `<span style="color:#db2777">?</span>`;
        return match;
      });
    }
    const pattern = /(\/\/.*$)|(["'`])(?:(?!\2)[^\\]|\\.)*?\2|\b(const|let|var|await|async|import|from|export|return|if|else|function|class|new|throw|try|catch)\b|\.([a-zA-Z_][a-zA-Z0-9_]*)\b|\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/gm;
    return escaped.replace(pattern, (match, comment, stringQuote, keyword, property, func) => {
      if (comment) return `<span style="color:#6b7280;font-style:italic">${match}</span>`;
      if (stringQuote) return `<span style="color:#059669">${match}</span>`;
      if (keyword) return `<span style="color:#db2777">${match}</span>`;
      if (property) return `.<span style="color:#0891b2">${property}</span>`;
      if (func) return `<span style="color:#2563eb">${match}</span>`;
      return match;
    });
  };
  const animateTo = target => {
    if (animationRef.current) cancelAnimationFrame(animationRef.current);
    setIsAnimating(true);
    const start = sliderPercent;
    const startTime = performance.now();
    const duration = 400;
    const animate = currentTime => {
      const elapsed = currentTime - startTime;
      const progress = Math.min(elapsed / duration, 1);
      const eased = 1 - Math.pow(1 - progress, 3);
      const current = start + (target - start) * eased;
      setSliderPercent(current);
      if (progress < 1) {
        animationRef.current = requestAnimationFrame(animate);
      } else {
        setSliderPercent(target);
        setIsAnimating(false);
        animationRef.current = null;
      }
    };
    animationRef.current = requestAnimationFrame(animate);
  };
  const handleToggle = () => {
    animateTo(showingFirst ? 0 : 100);
  };
  const handleMouseDown = e => {
    if (isAnimating) {
      cancelAnimationFrame(animationRef.current);
      setIsAnimating(false);
    }
    e.preventDefault();
    setIsDragging(true);
  };
  const handleMouseUp = () => {
    setIsDragging(false);
  };
  const handleMouseMove = e => {
    if (!isDragging || !containerRef.current) return;
    const rect = containerRef.current.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const percent = Math.max(0, Math.min(100, x / rect.width * 100));
    setSliderPercent(percent);
  };
  const handleTouchMove = e => {
    if (!containerRef.current) return;
    if (isAnimating) {
      cancelAnimationFrame(animationRef.current);
      setIsAnimating(false);
    }
    const rect = containerRef.current.getBoundingClientRect();
    const x = e.touches[0].clientX - rect.left;
    const percent = Math.max(0, Math.min(100, x / rect.width * 100));
    setSliderPercent(percent);
  };
  const handleKeyDown = e => {
    if (e.key === "ArrowLeft") {
      setSliderPercent(p => Math.max(0, p - 5));
    } else if (e.key === "ArrowRight") {
      setSliderPercent(p => Math.min(100, p + 5));
    }
  };
  useEffect(() => {
    if (isDragging) {
      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);
      return () => {
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", handleMouseUp);
      };
    }
  }, [isDragging]);
  useEffect(() => {
    return () => {
      if (animationRef.current) cancelAnimationFrame(animationRef.current);
    };
  }, []);
  useEffect(() => {
    const activeRef = showingFirst ? firstPreRef : secondPreRef;
    if (activeRef.current) {
      setContainerHeight(activeRef.current.scrollHeight);
    }
  }, [showingFirst]);
  return <>
      <div className="rounded-3xl not-prose mt-4 backdrop-blur-xl border overflow-hidden border-zinc-300 dark:border-zinc-700" style={{
    fontFamily: "Inter, sans-serif"
  }}>
        {}
        <div className="flex items-center justify-between px-4 py-3 border-b border-zinc-200 dark:border-zinc-700 bg-gray-50 dark:bg-zinc-900">
          <span className="text-sm font-medium text-zinc-600 dark:text-zinc-300">
            {showingFirst ? firstLabel : secondLabel}
          </span>

          <div className="flex items-center gap-3">
            {}
            <button onClick={handleCopy} className="p-1.5 rounded hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors text-zinc-500 dark:text-zinc-400" title="Copy code" style={{
    background: "transparent",
    border: "none",
    cursor: "pointer"
  }}>
              {copied ? <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2">
                  <path d="M20 6L9 17l-5-5" />
                </svg> : <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
                  <path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
                  <path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
                </svg>}
            </button>

            {}
            <div onClick={handleToggle} className="bg-zinc-200 dark:bg-zinc-600" style={{
    position: "relative",
    width: "56px",
    height: "28px",
    borderRadius: "14px",
    boxShadow: "inset -2px -2px 4px rgba(255,255,255,0.3), inset 2px 2px 4px rgba(0,0,0,0.1)",
    cursor: "pointer",
    transition: "all 0.3s ease"
  }}>
              {}
              <div className="bg-white dark:bg-zinc-300" style={{
    position: "absolute",
    width: "24px",
    height: "24px",
    borderRadius: "12px",
    top: "2px",
    left: showingFirst ? "30px" : "2px",
    boxShadow: "0 2px 4px rgba(0,0,0,0.2)",
    transition: "all 0.3s ease-in-out",
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
                {}
                <div style={{
    width: "6px",
    height: "6px",
    background: showingFirst ? "#0066ff" : "#999",
    borderRadius: "50%",
    boxShadow: showingFirst ? "0 0 5px 1px rgba(0, 102, 255, 0.6)" : "0 0 4px 1px rgba(0, 0, 0, 0.1)",
    transition: "all 0.3s ease-in-out"
  }} />
              </div>
            </div>
          </div>
        </div>

        {}
        <div ref={containerRef} className="p-0" style={{
    cursor: isDragging ? "grabbing" : "default"
  }} onTouchMove={handleTouchMove} tabIndex={0} onKeyDown={handleKeyDown} role="slider" aria-valuenow={sliderPercent} aria-valuemin={0} aria-valuemax={100} aria-label="Code comparison slider">
          <div className="relative" style={{
    minHeight: "140px",
    overflow: "hidden",
    height: containerHeight ? `${containerHeight}px` : "auto",
    transition: "height 0.3s ease"
  }}>
            <div style={{
    position: "relative"
  }}>
              {}
              <pre ref={secondPreRef} className="m-0 p-4 text-zinc-700 dark:text-white/80 bg-transparent" style={{
    position: showingFirst ? "absolute" : "relative",
    top: 0,
    left: 0,
    right: 0,
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
    fontSize: "13px",
    lineHeight: "1.6",
    whiteSpace: "pre",
    zIndex: 1
  }} dangerouslySetInnerHTML={{
    __html: highlightCode(secondCode)
  }} />

              {}
              <pre ref={firstPreRef} className="m-0 p-4 text-zinc-700 dark:text-white/80 bg-white dark:bg-zinc-900" style={{
    position: showingFirst ? "relative" : "absolute",
    top: 0,
    left: 0,
    right: 0,
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
    fontSize: "13px",
    lineHeight: "1.6",
    whiteSpace: "pre",
    zIndex: 2,
    clipPath: `inset(0 ${100 - sliderPercent}% 0 0)`
  }} dangerouslySetInnerHTML={{
    __html: highlightCode(firstCode)
  }} />
            </div>

            {}
            <div className="absolute top-0 bottom-0 flex items-center justify-center pointer-events-none" style={{
    left: `${sliderPercent}%`,
    transform: "translateX(-50%)",
    zIndex: 30
  }}>
              <div className="absolute top-0 bottom-0 w-px bg-zinc-400 dark:bg-white/30" />

              <div className="absolute top-0 bottom-0" style={{
    right: "50%",
    width: "60px",
    background: "linear-gradient(to left, rgba(0, 102, 255, 0.15) 0%, transparent 100%)"
  }} />

              {}
              <div onMouseDown={handleMouseDown} className="pointer-events-auto cursor-grab flex items-center justify-center gap-px transition-transform" style={{
    width: "20px",
    height: "32px",
    borderRadius: "4px",
    background: "#f8fafc",
    border: "1px solid #d1d5db",
    boxShadow: "0 1px 2px rgba(0,0,0,0.05)",
    transform: isDragging ? "scale(1.08)" : "scale(1)"
  }}>
                <div className="flex flex-col gap-0.5">
                  {[0, 1, 2].map(i => <div key={i} style={{
    width: "3px",
    height: "3px",
    borderRadius: "50%",
    background: "#0066ff"
  }} />)}
                </div>
                <div className="flex flex-col gap-0.5">
                  {[0, 1, 2].map(i => <div key={i} style={{
    width: "3px",
    height: "3px",
    borderRadius: "50%",
    background: "#0066ff"
  }} />)}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </>;
};

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

***

1. Mint accounts uniquely represent a token on Solana and store its global metadata.
2. Light mints are on-chain accounts like SPL mints, but the light token program sponsors the rent-exemption cost for you.
3. [Add an interface PDA](/light-token/cookbook/add-interface-pda) to existing SPL or Token 2022 mints for interoperability with Light Token. The interface PDA holds SPL or Token 2022 tokens when wrapped to Light Token.

<Accordion title="Light Rent Config Explained">
  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="Agent skill">
  Install or view [dedicated agent skills](/ai-tools/overview#agent-skills).

  ```
  npx skills add Lightprotocol/skills
  ```

  Install orchestrator agent skill or view [skill.md](https://www.zkcompression.com/skill.md):

  ```bash theme={null}
  npx skills add https://zkcompression.com
  ```
</Accordion>

<Tabs>
  <Tab title="TypeScript Client">
    `createMintInterface` is a unified interface that dispatches to different mint creation paths based on programId:

    * `TOKEN_PROGRAM_ID` or `TOKEN_2022_PROGRAM_ID` → delegates to SPL or Token 2022 `createMint`
    * Otherwise it defaults to `LIGHT_TOKEN_PROGRAM_ID` → creates a Light Token mint

    You can use the same interface regardless of mint type.

    Compare to SPL:

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

    <Info>
      Find the source code
      [here](https://github.com/Lightprotocol/light-protocol/blob/main/js/compressed-token/src/v3/actions/create-mint-interface.ts).
    </Info>

    <Tabs>
      <Tab title="Guide">
        <Steps>
          <Step>
            ### Create Mint with Token Metadata

            <Accordion title="Installation">
              <Tabs>
                <Tab title="npm">
                  Install packages in your working directory:

                  ```bash theme={null}
                  npm install @lightprotocol/stateless.js@^0.23.0 \
                              @lightprotocol/compressed-token@^0.23.0
                  ```

                  Install the CLI globally:

                  ```bash theme={null}
                  npm install -g @lightprotocol/zk-compression-cli
                  ```
                </Tab>

                <Tab title="yarn">
                  Install packages in your working directory:

                  ```bash theme={null}
                  yarn add @lightprotocol/stateless.js@^0.23.0 \
                           @lightprotocol/compressed-token@^0.23.0
                  ```

                  Install the CLI globally:

                  ```bash theme={null}
                  yarn global add @lightprotocol/zk-compression-cli
                  ```
                </Tab>

                <Tab title="pnpm">
                  Install packages in your working directory:

                  ```bash theme={null}
                  pnpm add @lightprotocol/stateless.js@^0.23.0 \
                           @lightprotocol/compressed-token@^0.23.0
                  ```

                  Install the CLI globally:

                  ```bash theme={null}
                  pnpm add -g @lightprotocol/zk-compression-cli
                  ```
                </Tab>

                <Tab title="SDK 2.0 (token-interface)">
                  Install packages in your working directory:

                  ```bash theme={null}
                  # npm
                  npm install @lightprotocol/stateless.js@^0.23.0 \
                              @lightprotocol/token-interface@^0.1.2

                  # yarn
                  yarn add @lightprotocol/stateless.js@^0.23.0 \
                           @lightprotocol/token-interface@^0.1.2

                  # pnpm
                  pnpm add @lightprotocol/stateless.js@^0.23.0 \
                           @lightprotocol/token-interface@^0.1.2
                  ```

                  Install the CLI globally:

                  ```bash theme={null}
                  npm install -g @lightprotocol/zk-compression-cli
                  ```
                </Tab>
              </Tabs>
            </Accordion>

            <Tabs>
              <Tab title="Localnet">
                ```bash theme={null}
                # start local test-validator in a separate terminal
                light test-validator
                ```

                <Note>
                  In the code examples, use `createRpc()` without arguments for localnet.
                </Note>
              </Tab>

              <Tab title="Devnet">
                Get an API key from [Helius](https://helius.dev) and add to `.env`:

                ```bash title=".env" theme={null}
                API_KEY=<your-helius-api-key>
                ```

                <Note>
                  In the code examples, use `createRpc(RPC_URL)` with the devnet URL.
                </Note>
              </Tab>
            </Tabs>

            <Note>
              The `mintAuthority` must be a `Signer` for light-mints but can be just a
              `PublicKey` for SPL/Token 2022.
            </Note>

            <Tabs>
              <Tab title="Action">
                ```typescript theme={null}
                import "dotenv/config";
                import { Keypair } from "@solana/web3.js";
                import { createRpc } from "@lightprotocol/stateless.js";
                import {
                    createMintInterface,
                    createTokenMetadata,
                } from "@lightprotocol/compressed-token";
                import { homedir } from "os";
                import { readFileSync } from "fs";

                // devnet:
                // const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
                // const rpc = createRpc(RPC_URL);
                // localnet:
                const rpc = createRpc();

                const payer = Keypair.fromSecretKey(
                    new Uint8Array(
                        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
                    )
                );

                (async function () {
                    const { mint, transactionSignature } = await createMintInterface(
                        rpc,
                        payer,
                        payer,
                        null,
                        9,
                        undefined, // keypair
                        undefined, // confirmOptions (default)
                        undefined, // programId (LIGHT_TOKEN_PROGRAM_ID)
                        createTokenMetadata(
                            "Example Token",
                            "EXT",
                            "https://example.com/metadata.json"
                        )
                    );

                    console.log("Mint:", mint.toBase58());
                    console.log("Tx:", transactionSignature);
                })();
                ```
              </Tab>

              <Tab title="Instruction">
                ```typescript theme={null}
                import "dotenv/config";
                import {
                    Keypair,
                    ComputeBudgetProgram,
                    PublicKey,
                    Transaction,
                    sendAndConfirmTransaction,
                } from "@solana/web3.js";
                import {
                    createRpc,
                    getBatchAddressTreeInfo,
                    selectStateTreeInfo,
                    LIGHT_TOKEN_PROGRAM_ID,
                } from "@lightprotocol/stateless.js";
                import {
                    createMintInstruction,
                    createTokenMetadata,
                } from "@lightprotocol/compressed-token";
                import { homedir } from "os";
                import { readFileSync } from "fs";

                const COMPRESSED_MINT_SEED = Buffer.from("compressed_mint");

                function findMintAddress(mintSigner: PublicKey): [PublicKey, number] {
                    return PublicKey.findProgramAddressSync(
                        [COMPRESSED_MINT_SEED, mintSigner.toBuffer()],
                        LIGHT_TOKEN_PROGRAM_ID
                    );
                }

                // devnet:
                // const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
                // const rpc = createRpc(RPC_URL);
                // localnet:
                const rpc = createRpc();

                const payer = Keypair.fromSecretKey(
                    new Uint8Array(
                        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
                    )
                );

                (async function () {
                    const mintSigner = Keypair.generate();
                    const addressTreeInfo = getBatchAddressTreeInfo();
                    const stateTreeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos());
                    const [mintPda] = findMintAddress(mintSigner.publicKey);

                    const validityProof = await rpc.getValidityProofV2(
                        [],
                        [{ address: mintPda.toBytes(), treeInfo: addressTreeInfo }]
                    );

                    const ix = createMintInstruction(
                        mintSigner.publicKey,
                        9,
                        payer.publicKey,
                        null,
                        payer.publicKey,
                        validityProof,
                        addressTreeInfo,
                        stateTreeInfo,
                        createTokenMetadata(
                            "Example Token",
                            "EXT",
                            "https://example.com/metadata.json"
                        )
                    );

                    const tx = new Transaction().add(
                        ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
                        ix
                    );
                    const signature = await sendAndConfirmTransaction(rpc, tx, [
                        payer,
                        mintSigner,
                    ]);

                    console.log("Mint:", mintPda.toBase58());
                    console.log("Tx:", signature);
                })();
                ```
              </Tab>

              <Tab title="SDK 2.0 (token-interface)">
                ```typescript theme={null}
                import { Buffer } from "buffer";
                import {
                  Keypair,
                  PublicKey,
                  Transaction,
                  sendAndConfirmTransaction,
                } from "@solana/web3.js";
                import { createRpc, LIGHT_TOKEN_PROGRAM_ID } from "@lightprotocol/stateless.js";
                import { createMintInstructions } from "@lightprotocol/token-interface";

                const COMPRESSED_MINT_SEED = Buffer.from("compressed_mint");
                const rpc = createRpc();
                const payer = Keypair.fromSecretKey(/* ... */);

                function findMintAddress(mintSigner: PublicKey): [PublicKey, number] {
                  return PublicKey.findProgramAddressSync(
                    [COMPRESSED_MINT_SEED, mintSigner.toBuffer()],
                    LIGHT_TOKEN_PROGRAM_ID
                  );
                }

                const mintSigner = Keypair.generate();
                const [mintPda] = findMintAddress(mintSigner.publicKey);
                const instructions = await createMintInstructions({
                  rpc,
                  payer: payer.publicKey,
                  keypair: mintSigner,
                  decimals: 9,
                  mintAuthority: payer.publicKey,
                  tokenProgramId: LIGHT_TOKEN_PROGRAM_ID,
                });

                const tx = new Transaction().add(...instructions);
                const signature = await sendAndConfirmTransaction(rpc, tx, [payer, mintSigner]);
                console.log("Mint:", mintPda.toBase58());
                console.log("Tx:", signature);
                ```
              </Tab>
            </Tabs>
          </Step>
        </Steps>
      </Tab>

      <Tab title="AI Prompt">
        <Prompt description="Add rent-free mint with token metadata" actions={["copy", "cursor"]}>
          {`---
                    description: Add rent-free mint with token metadata
                    allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
                    ---

                    ## Add rent-free mint with token metadata

                    Context:
                    - Guide: https://zkcompression.com/light-token/cookbook/create-mint
                    - Skills and resources index: https://zkcompression.com/skill.md
                    - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
                    - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js

                    SPL equivalent: createMint() → Light Token: createMintInterface()

                    ### 1. Index project
                    - Grep \`@solana/spl-token|Connection|Keypair|createMint\` across src/
                    - Glob \`**/*.ts\` for project structure
                    - Identify: RPC setup, existing mint logic, entry point for mint creation
                    - Task subagent (Grep/Read/WebFetch) if project has multiple packages to scan in parallel

                    ### 2. Read references
                    - WebFetch the guide above — follow the TypeScript Client tab
                    - WebFetch skill.md — check for a dedicated skill and resources matching this task
                    - TaskCreate one todo per phase below to track progress

                    ### 3. Clarify intention
                    - AskUserQuestion: what is the goal? (new feature, migrate existing SPL code, add alongside existing)
                    - AskUserQuestion: does the project already have mint operations to extend, or is this greenfield?
                    - Summarize findings and wait for user confirmation before implementing

                    ### 4. Create plan
                    - Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
                    - Verify existing connection/signer setup is compatible with the cookbook prerequisites
                    - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
                    - Present the plan to the user for approval before proceeding

                    ### 5. Implement
                    - Add deps if missing: Bash \`npm install @lightprotocol/compressed-token @lightprotocol/stateless.js\`
                    - Follow the cookbook guide and the approved plan
                    - Write/Edit to create or modify files
                    - TaskUpdate to mark each step done

                    ### 6. Verify
                    - Bash \`tsc --noEmit\`
                    - Bash run existing test suite if present
                    - TaskUpdate to mark complete

                    ### Tools
                    - mcp__zkcompression__SearchLightProtocol("<query>") for API details
                    - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
                    - Task subagent with Grep/Read/WebFetch for parallel lookups
                    - TaskList to check remaining work`}
        </Prompt>

        ```text theme={null}
        ---
        description: Add rent-free mint with token metadata
        allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
        ---

        ## Add rent-free mint with token metadata

        Context:
        - Guide: https://zkcompression.com/light-token/cookbook/create-mint
        - Skills and resources index: https://zkcompression.com/skill.md
        - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
        - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js

        SPL equivalent: createMint() → Light Token: createMintInterface()

        ### 1. Index project
        - Grep `@solana/spl-token|Connection|Keypair|createMint` across src/
        - Glob `**/*.ts` for project structure
        - Identify: RPC setup, existing mint logic, entry point for mint creation
        - Task subagent (Grep/Read/WebFetch) if project has multiple packages to scan in parallel

        ### 2. Read references
        - WebFetch the guide above — follow the TypeScript Client tab
        - WebFetch skill.md — check for a dedicated skill and resources matching this task
        - TaskCreate one todo per phase below to track progress

        ### 3. Clarify intention
        - AskUserQuestion: what is the goal? (new feature, migrate existing SPL code, add alongside existing)
        - AskUserQuestion: does the project already have mint operations to extend, or is this greenfield?
        - Summarize findings and wait for user confirmation before implementing

        ### 4. Create plan
        - Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
        - Verify existing connection/signer setup is compatible with the cookbook prerequisites
        - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
        - Present the plan to the user for approval before proceeding

        ### 5. Implement
        - Add deps if missing: Bash `npm install @lightprotocol/compressed-token @lightprotocol/stateless.js`
        - Follow the cookbook guide and the approved plan
        - Write/Edit to create or modify files
        - TaskUpdate to mark each step done

        ### 6. Verify
        - Bash `tsc --noEmit`
        - Bash run existing test suite if present
        - TaskUpdate to mark complete

        ### Tools
        - mcp__zkcompression__SearchLightProtocol("<query>") for API details
        - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
        - Task subagent with Grep/Read/WebFetch for parallel lookups
        - TaskList to check remaining work
        ```
      </Tab>
    </Tabs>

    ## Create SPL mint with interface PDA

    Pass `TOKEN_PROGRAM_ID` to create a standard SPL mint with an interface PDA in one transaction for interoperability with Light Token.

    <Tabs>
      <Tab title="Action">
        ```typescript theme={null}
        import "dotenv/config";
        import { Keypair } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";
        import { createMintInterface } from "@lightprotocol/compressed-token";
        import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
        import { homedir } from "os";
        import { readFileSync } from "fs";

        // devnet:
        const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
        const rpc = createRpc(RPC_URL);
        // localnet:
        // const rpc = createRpc();

        const payer = Keypair.fromSecretKey(
            new Uint8Array(
                JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
            )
        );

        (async function () {
            // Creates SPL mint + SPL Interface PDA in one transaction
            const mintKeypair = Keypair.generate();
            const { mint, transactionSignature } = await createMintInterface(
                rpc,
                payer,
                payer,
                null,
                9,
                mintKeypair,
                undefined,
                TOKEN_PROGRAM_ID
            );

            console.log("Mint:", mint.toBase58());
            console.log("Tx:", transactionSignature);
        })();
        ```
      </Tab>

      <Tab title="Instruction">
        ```typescript theme={null}
        import "dotenv/config";
        import {
            Keypair,
            SystemProgram,
            Transaction,
            sendAndConfirmTransaction,
        } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";
        import { LightTokenProgram } from "@lightprotocol/compressed-token";
        import {
            MINT_SIZE,
            TOKEN_PROGRAM_ID,
            createInitializeMint2Instruction,
        } from "@solana/spl-token";
        import { homedir } from "os";
        import { readFileSync } from "fs";

        // devnet:
        const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
        const rpc = createRpc(RPC_URL);
        // localnet:
        // const rpc = createRpc();

        const payer = Keypair.fromSecretKey(
            new Uint8Array(
                JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
            )
        );

        (async function () {
            const mintKeypair = Keypair.generate();
            const decimals = 9;

            // Get rent for mint account
            const rentExemptBalance = await rpc.getMinimumBalanceForRentExemption(MINT_SIZE);

            // Instruction 1: Create mint account
            const createMintAccountIx = SystemProgram.createAccount({
                fromPubkey: payer.publicKey,
                lamports: rentExemptBalance,
                newAccountPubkey: mintKeypair.publicKey,
                programId: TOKEN_PROGRAM_ID,
                space: MINT_SIZE,
            });

            // Instruction 2: Initialize mint
            const initializeMintIx = createInitializeMint2Instruction(
                mintKeypair.publicKey,
                decimals,
                payer.publicKey, // mint authority
                null, // freeze authority
                TOKEN_PROGRAM_ID
            );

            // Instruction 3: Create SPL Interface PDA (enables compression for this mint)
            const createSplInterfaceIx = await LightTokenProgram.createSplInterface({
                feePayer: payer.publicKey,
                mint: mintKeypair.publicKey,
                tokenProgramId: TOKEN_PROGRAM_ID,
            });

            const tx = new Transaction().add(
                createMintAccountIx,
                initializeMintIx,
                createSplInterfaceIx
            );

            const signature = await sendAndConfirmTransaction(rpc, tx, [
                payer,
                mintKeypair,
            ]);

            console.log("Mint:", mintKeypair.publicKey.toBase58());
            console.log("Tx:", signature);
        })();
        ```
      </Tab>

      <Tab title="SDK 2.0 (token-interface)">
        ```typescript theme={null}
        import { Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";
        import { createMintInstructions } from "@lightprotocol/token-interface";
        import { TOKEN_PROGRAM_ID } from "@solana/spl-token";

        const rpc = createRpc();
        const payer = Keypair.fromSecretKey(/* ... */);
        const mintKeypair = Keypair.generate();

        const instructions = await createMintInstructions({
          rpc,
          payer: payer.publicKey,
          keypair: mintKeypair,
          decimals: 9,
          mintAuthority: payer.publicKey,
          tokenProgramId: TOKEN_PROGRAM_ID,
        });

        const tx = new Transaction().add(...instructions);
        const signature = await sendAndConfirmTransaction(rpc, tx, [payer, mintKeypair]);
        console.log("Mint:", mintKeypair.publicKey.toBase58());
        console.log("Tx:", signature);
        ```
      </Tab>
    </Tabs>

    ## Create Token 2022 mint with interface PDA

    Pass `TOKEN_2022_PROGRAM_ID` to create a Token-2022 mint with an interface PDA in one transaction for interoperability with Light Token.

    <Tabs>
      <Tab title="Action">
        ```typescript theme={null}
        import "dotenv/config";
        import { Keypair } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";
        import { createMintInterface } from "@lightprotocol/compressed-token";
        import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
        import { homedir } from "os";
        import { readFileSync } from "fs";

        // devnet:
        const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
        const rpc = createRpc(RPC_URL);
        // localnet:
        // const rpc = createRpc();

        const payer = Keypair.fromSecretKey(
            new Uint8Array(
                JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
            )
        );

        (async function () {
            // Creates Token-2022 mint and SPL interface PDA in one transaction
            // SPL interface PDA holds Token-2022 tokens when wrapped to light-token
            const mintKeypair = Keypair.generate();
            const { mint, transactionSignature } = await createMintInterface(
                rpc,
                payer,
                payer,
                null,
                9,
                mintKeypair,
                undefined,
                TOKEN_2022_PROGRAM_ID
            );

            console.log("Mint:", mint.toBase58());
            console.log("Tx:", transactionSignature);
        })();
        ```
      </Tab>

      <Tab title="Instruction">
        ```typescript theme={null}
        import "dotenv/config";
        import {
            Keypair,
            SystemProgram,
            Transaction,
            sendAndConfirmTransaction,
        } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";
        import { LightTokenProgram } from "@lightprotocol/compressed-token";
        import {
            MINT_SIZE,
            TOKEN_2022_PROGRAM_ID,
            createInitializeMint2Instruction,
        } from "@solana/spl-token";
        import { homedir } from "os";
        import { readFileSync } from "fs";

        // devnet:
        const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
        const rpc = createRpc(RPC_URL);
        // localnet:
        // const rpc = createRpc();

        const payer = Keypair.fromSecretKey(
            new Uint8Array(
                JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
            )
        );

        (async function () {
            const mintKeypair = Keypair.generate();
            const decimals = 9;

            // Get rent for mint account
            const rentExemptBalance =
                await rpc.getMinimumBalanceForRentExemption(MINT_SIZE);

            // Instruction 1: Create mint account
            const createMintAccountIx = SystemProgram.createAccount({
                fromPubkey: payer.publicKey,
                lamports: rentExemptBalance,
                newAccountPubkey: mintKeypair.publicKey,
                programId: TOKEN_2022_PROGRAM_ID,
                space: MINT_SIZE,
            });

            // Instruction 2: Initialize mint
            const initializeMintIx = createInitializeMint2Instruction(
                mintKeypair.publicKey,
                decimals,
                payer.publicKey, // mint authority
                null, // freeze authority
                TOKEN_2022_PROGRAM_ID
            );

            // Instruction 3: Create SPL interface PDA
            // Holds Token-2022 tokens when wrapped to light-token
            const createSplInterfaceIx = await LightTokenProgram.createSplInterface({
                feePayer: payer.publicKey,
                mint: mintKeypair.publicKey,
                tokenProgramId: TOKEN_2022_PROGRAM_ID,
            });

            const tx = new Transaction().add(
                createMintAccountIx,
                initializeMintIx,
                createSplInterfaceIx
            );

            const signature = await sendAndConfirmTransaction(rpc, tx, [
                payer,
                mintKeypair,
            ]);

            console.log("Mint:", mintKeypair.publicKey.toBase58());
            console.log("Tx:", signature);
        })();
        ```
      </Tab>

      <Tab title="SDK 2.0 (token-interface)">
        ```typescript theme={null}
        import { Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";
        import { createMintInstructions } from "@lightprotocol/token-interface";
        import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";

        const rpc = createRpc();
        const payer = Keypair.fromSecretKey(/* ... */);
        const mintKeypair = Keypair.generate();

        const instructions = await createMintInstructions({
          rpc,
          payer: payer.publicKey,
          keypair: mintKeypair,
          decimals: 9,
          mintAuthority: payer.publicKey,
          tokenProgramId: TOKEN_2022_PROGRAM_ID,
        });

        const tx = new Transaction().add(...instructions);
        const signature = await sendAndConfirmTransaction(rpc, tx, [payer, mintKeypair]);
        console.log("Mint:", mintKeypair.publicKey.toBase58());
        console.log("Tx:", signature);
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Rust Client">
    `CreateMint` creates an on-chain mint account that can optionally include token metadata.
    The instruction also writes a compressed mint address to the address Merkle tree, which preserves the mint state when the on-chain account is compressed.

    Compare to SPL:

    <CodeCompare firstCode={lightCreateMintRustCode} secondCode={splCreateMintRustCode} firstLabel="light-token" secondLabel="SPL" language="rust" />

    <Tabs>
      <Tab title="Guide">
        <Steps>
          <Step>
            ### Prerequisites

            <Accordion title="Dependencies">
              ```toml Cargo.toml theme={null}
              [dependencies]
              light-token = "0.23.0"
              light-client = { version = "0.23.0", features = ["v2"] }
              solana-sdk = "2"
              borsh = "0.10.4"
              tokio = { version = "1", features = ["full"] }
              ```
            </Accordion>

            <Accordion title="Developer Environment">
              <Tabs>
                <Tab title="In-Memory (LightProgramTest)">
                  Test with Lite-SVM (...)

                  ```bash theme={null}
                  # Initialize project
                  cargo init my-light-project
                  cd my-light-project

                  # Run tests
                  cargo test
                  ```

                  ```rust theme={null}
                  use light_program_test::{LightProgramTest, ProgramTestConfig};
                  use solana_sdk::signer::Signer;

                  #[tokio::test]
                  async fn test_example() {
                      // In-memory test environment 
                      let mut rpc = LightProgramTest::new(ProgramTestConfig::default())
                          .await
                          .unwrap();

                      let payer = rpc.get_payer().insecure_clone();
                      println!("Payer: {}", payer.pubkey());
                  }
                  ```
                </Tab>

                <Tab title="Localnet (LightClient)">
                  Connects to a local test validator.

                  <Tabs>
                    <Tab title="npm">
                      ```bash theme={null}
                      npm install -g @lightprotocol/zk-compression-cli
                      ```
                    </Tab>

                    <Tab title="yarn">
                      ```bash theme={null}
                      yarn global add @lightprotocol/zk-compression-cli
                      ```
                    </Tab>

                    <Tab title="pnpm">
                      ```bash theme={null}
                      pnpm add -g @lightprotocol/zk-compression-cli
                      ```
                    </Tab>
                  </Tabs>

                  ```bash theme={null}
                  # Initialize project
                  cargo init my-light-project
                  cd my-light-project

                  # Start local test validator (in separate terminal)
                  light test-validator
                  ```

                  ```rust theme={null}
                  use light_client::rpc::{LightClient, LightClientConfig, Rpc};

                  #[tokio::main]
                  async fn main() -> Result<(), Box<dyn std::error::Error>> {
                      // Connects to http://localhost:8899
                      let rpc = LightClient::new(LightClientConfig::local()).await?;

                      let slot = rpc.get_slot().await?;
                      println!("Current slot: {}", slot);

                      Ok(())
                  }
                  ```
                </Tab>

                <Tab title="Devnet (LightClient)">
                  Replace `<your-api-key>` with your actual API key. [Get your API key here](https://www.helius.dev/zk-compression).

                  ```rust theme={null}
                  use light_client::rpc::{LightClient, LightClientConfig, Rpc};

                  #[tokio::main]
                  async fn main() -> Result<(), Box<dyn std::error::Error>> {
                      let rpc_url = "https://devnet.helius-rpc.com?api-key=<your_api_key>";
                      let rpc = LightClient::new(
                          LightClientConfig::new(rpc_url.to_string(), None, None)
                      ).await?;

                      println!("Connected to Devnet");
                      Ok(())
                  }
                  ```
                </Tab>
              </Tabs>
            </Accordion>
          </Step>

          <Step>
            ### Create Light Token Mint with Token Metadata

            <Info>
              Find the source code [here](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/instruction/create_mint.rs).
            </Info>

            <Tabs>
              <Tab title="Action">
                ```rust theme={null}
                use light_client::rpc::Rpc;
                use light_token_client::actions::{CreateMint, TokenMetadata};
                use rust_client::setup_rpc_and_payer;
                use solana_sdk::signer::Signer;

                #[tokio::main]
                async fn main() -> Result<(), Box<dyn std::error::Error>> {
                    let (mut rpc, payer) = setup_rpc_and_payer().await;

                    let (signature, mint) = CreateMint {
                        decimals: 9,
                        freeze_authority: None,
                        token_metadata: Some(TokenMetadata {
                            name: "Example Token".to_string(),
                            symbol: "EXT".to_string(),
                            uri: "https://example.com/metadata.json".to_string(),
                            update_authority: Some(payer.pubkey()),
                            additional_metadata: Some(vec![("type".to_string(), "example".to_string())]),
                        }),
                        seed: None,
                    }
                    .execute(&mut rpc, &payer, &payer)
                    .await?;

                    let data = rpc.get_account(mint).await?;
                    println!("Mint: {mint} exists: {} Tx: {signature}", data.is_some());

                    Ok(())
                }
                ```
              </Tab>

              <Tab title="Instruction">
                ```rust theme={null}
                use light_client::{
                    indexer::{AddressWithTree, Indexer},
                    rpc::Rpc,
                };
                use light_program_test::{LightProgramTest, ProgramTestConfig};
                use light_token::instruction::{
                    derive_mint_compressed_address, find_mint_address, CreateMint, CreateMintParams,
                    DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP,
                };
                use light_token_interface::{
                    instructions::extensions::{
                        token_metadata::TokenMetadataInstructionData, ExtensionInstructionData,
                    },
                    state::AdditionalMetadata,
                };
                use solana_sdk::{signature::Keypair, signer::Signer};

                #[tokio::main]
                async fn main() -> Result<(), Box<dyn std::error::Error>> {
                    let mut rpc = LightProgramTest::new(ProgramTestConfig::new(false, None)).await?;

                    let payer = rpc.get_payer().insecure_clone();
                    let mint_seed = Keypair::new();
                    let decimals = 9u8;

                    // Get address tree to store compressed address for when mint turns inactive
                    // We must create a compressed address at creation to ensure the mint does not exist yet
                    let address_tree = rpc.get_address_tree_v2();
                    // Get state tree to store mint when inactive
                    let output_queue = rpc.get_random_state_tree_info().unwrap().queue;

                    // Derive mint addresses
                    let compression_address =
                        derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree);
                    let mint = find_mint_address(&mint_seed.pubkey()).0; // on-chain Mint PDA

                    // Fetch validity proof to proof address does not exist yet
                    let rpc_result = rpc
                        .get_validity_proof(
                            vec![],
                            vec![AddressWithTree {
                                address: compression_address,
                                tree: address_tree.tree,
                            }],
                            None,
                        )
                        .await
                        .unwrap()
                        .value;

                    // Build CreateMintParams with token metadata extension
                    let params = CreateMintParams {
                        decimals,
                        address_merkle_tree_root_index: rpc_result.addresses[0].root_index, // stores mint compressed address
                        mint_authority: payer.pubkey(),
                        proof: rpc_result.proof.0.unwrap(),
                        compression_address, // address for compression when mint turns inactive
                        mint,
                        bump: find_mint_address(&mint_seed.pubkey()).1,
                        freeze_authority: None,
                        extensions: Some(vec![ExtensionInstructionData::TokenMetadata(
                            TokenMetadataInstructionData {
                                update_authority: Some(payer.pubkey().to_bytes().into()),
                                name: b"Example Token".to_vec(),
                                symbol: b"EXT".to_vec(),
                                uri: b"https://example.com/metadata.json".to_vec(),
                                additional_metadata: Some(vec![AdditionalMetadata {
                                    key: b"type".to_vec(),
                                    value: b"example".to_vec(),
                                }]),
                            },
                        )]),
                        rent_payment: DEFAULT_RENT_PAYMENT, // 24h of rent
                        write_top_up: DEFAULT_WRITE_TOP_UP, // 3h of rent
                    };

                    // Build and send instruction (mint_seed must sign)
                    let create_mint_instruction = CreateMint::new(
                        params,
                        mint_seed.pubkey(),
                        payer.pubkey(),
                        address_tree.tree,
                        output_queue,
                    )
                    .instruction()?;

                    let sig = rpc
                        .create_and_send_transaction(&[create_mint_instruction], &payer.pubkey(), &[&payer, &mint_seed])
                        .await?;

                    let data = rpc.get_account(mint).await?;
                    println!("Mint: {} exists: {} Tx: {sig}", mint, data.is_some());

                    Ok(())
                }
                ```
              </Tab>
            </Tabs>
          </Step>
        </Steps>
      </Tab>

      <Tab title="AI Prompt">
        <Prompt description="Create rent-free mint with token metadata" actions={["copy", "cursor"]}>
          {`---
                    description: Create rent-free mint with token metadata
                    allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
                    ---

                    ## Create rent-free mint with token metadata

                    Context:
                    - Guide: https://zkcompression.com/light-token/cookbook/create-mint
                    - Skills and resources index: https://zkcompression.com/skill.md
                    - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
                    - Crates: light-token-client (actions), light-token (instructions), light-client (RPC)

                    SPL equivalent: spl_token::instruction::initialize_mint → Light Token: CreateMint

                    ### 1. Index project
                    - Grep \`light_token::|light_token_client::|solana_sdk|Keypair|async|CreateMint|initialize_mint\` across src/
                    - Glob \`**/*.rs\` for project structure
                    - Identify: RPC setup, existing token ops, entry point for mint creation
                    - Check Cargo.toml for existing light-* dependencies and solana-sdk version
                    - Task subagent (Grep/Read/WebFetch) if project has multiple crates to scan in parallel

                    ### 2. Read references
                    - WebFetch the guide above — follow the Rust Client tab
                    - WebFetch skill.md — check for a dedicated skill and resources matching this task
                    - TaskCreate one todo per phase below to track progress

                    ### 3. Clarify intention
                    - AskUserQuestion: what is the goal? (new feature, migrate existing SPL code, add alongside existing)
                    - AskUserQuestion: does the project already have mint operations to extend, or is this greenfield?
                    - AskUserQuestion: action-level API (high-level, fewer lines) or instruction-level API (low-level, full control)?
                    - Summarize findings and wait for user confirmation before implementing

                    ### 4. Create plan
                    - Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
                    - Verify existing Rpc/signer setup is compatible with the cookbook prerequisites (light_client::rpc::Rpc, solana_sdk::signature::Keypair)
                    - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
                    - Present the plan to the user for approval before proceeding

                    ### 5. Implement
                    - Add deps if missing: Bash \`cargo add light-token-client light-token light-client --features light-client/v2\`
                    - Follow the cookbook guide and the approved plan
                    - Write/Edit to create or modify files
                    - TaskUpdate to mark each step done

                    ### 6. Verify
                    - Bash \`cargo check\`
                    - Bash \`cargo test\` if tests exist
                    - TaskUpdate to mark complete

                    ### Tools
                    - mcp__zkcompression__SearchLightProtocol("<query>") for API details
                    - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
                    - Task subagent with Grep/Read/WebFetch for parallel lookups
                    - TaskList to check remaining work`}
        </Prompt>

        ```text theme={null}
        ---
        description: Create rent-free mint with token metadata
        allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
        ---

        ## Create rent-free mint with token metadata

        Context:
        - Guide: https://zkcompression.com/light-token/cookbook/create-mint
        - Skills and resources index: https://zkcompression.com/skill.md
        - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
        - Crates: light-token-client (actions), light-token (instructions), light-client (RPC)

        SPL equivalent: spl_token::instruction::initialize_mint → Light Token: CreateMint

        ### 1. Index project
        - Grep `light_token::|light_token_client::|solana_sdk|Keypair|async|CreateMint|initialize_mint` across src/
        - Glob `**/*.rs` for project structure
        - Identify: RPC setup, existing token ops, entry point for mint creation
        - Check Cargo.toml for existing light-* dependencies and solana-sdk version
        - Task subagent (Grep/Read/WebFetch) if project has multiple crates to scan in parallel

        ### 2. Read references
        - WebFetch the guide above — follow the Rust Client tab
        - WebFetch skill.md — check for a dedicated skill and resources matching this task
        - TaskCreate one todo per phase below to track progress

        ### 3. Clarify intention
        - AskUserQuestion: what is the goal? (new feature, migrate existing SPL code, add alongside existing)
        - AskUserQuestion: does the project already have mint operations to extend, or is this greenfield?
        - AskUserQuestion: action-level API (high-level, fewer lines) or instruction-level API (low-level, full control)?
        - Summarize findings and wait for user confirmation before implementing

        ### 4. Create plan
        - Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
        - Verify existing Rpc/signer setup is compatible with the cookbook prerequisites (light_client::rpc::Rpc, solana_sdk::signature::Keypair)
        - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
        - Present the plan to the user for approval before proceeding

        ### 5. Implement
        - Add deps if missing: Bash `cargo add light-token-client light-token light-client --features light-client/v2`
        - Follow the cookbook guide and the approved plan
        - Write/Edit to create or modify files
        - TaskUpdate to mark each step done

        ### 6. Verify
        - Bash `cargo check`
        - Bash `cargo test` if tests exist
        - TaskUpdate to mark complete

        ### Tools
        - mcp__zkcompression__SearchLightProtocol("<query>") for API details
        - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
        - Task subagent with Grep/Read/WebFetch for parallel lookups
        - TaskList to check remaining work
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Program">
    <Tabs>
      <Tab title="CPI">
        <Tabs>
          <Tab title="Guide">
            Compare to SPL:

            <Tabs>
              <Tab title="Mint">
                <CodeCompare firstCode={lightCreateMintCpiCode} secondCode={splCreateMintCpiCode} firstLabel="light-token" secondLabel="SPL" language="rust" />
              </Tab>

              <Tab title="With Token Metadata">
                <CodeCompare firstCode={lightCreateMintMetadataCpiCode} secondCode={splCreateMintMetadataCpiCode} firstLabel="light-token" secondLabel="SPL Token 2022" language="rust" />
              </Tab>
            </Tabs>

            <Note>
              Find [a full code example at the end](#full-code-example).
            </Note>

            <Steps>
              <Step>
                ### Configure Token Metadata

                ```rust theme={null}
                use light_token_interface::instructions::extensions::{
                    token_metadata::TokenMetadataInstructionData, ExtensionInstructionData,
                };

                let extensions = Some(vec![ExtensionInstructionData::TokenMetadata(
                    TokenMetadataInstructionData {
                        update_authority: Some(authority.key.to_bytes().into()),
                        name: b"Example Token".to_vec(),
                        symbol: b"EXT".to_vec(),
                        uri: b"https://example.com/metadata.json".to_vec(),
                        additional_metadata: None,
                    },
                )]);
                ```

                <Note>
                  **Fields must be set at light-mint creation.** Standard fields (`name`,
                  `symbol`, `uri`) can be updated by `update_authority`. For
                  `additional_metadata`, only existing keys can be modified or removed. New keys
                  cannot be added after creation.
                </Note>
              </Step>

              <Step>
                ### Configure Mint

                Configure mint parameters including `decimals`, authorities, rent settings, and pass `extensions` from the previous step.

                ```rust theme={null}
                use light_token::instruction::CreateMintParams;

                let params = CreateMintParams {
                    decimals,
                    address_merkle_tree_root_index,
                    mint_authority: *ctx.accounts.authority.key,
                    proof,
                    compression_address,
                    mint,
                    bump,
                    freeze_authority,
                    extensions,
                    rent_payment: rent_payment.unwrap_or(16),    // ~24 hours rent
                    write_top_up: write_top_up.unwrap_or(766),   // ~3 hours rent
                };
                ```

                <Info>
                  The address of the mint account is stored in an address Merkle tree
                  , which is maintained by the protocol.
                  The client passes a validity proof that proves the mint address does not
                  exist yet.
                </Info>
              </Step>

              <Step>
                ### System Accounts

                Include system accounts such as the Light System Program
                to verify the proof and write the mint address to the address tree.

                <Accordion title="System Accounts List">
                  <table>
                    <colgroup>
                      <col style={{width: '5%'}} />

                      <col style={{width: '30%', textAlign: 'left'}} />

                      <col style={{width: '65%'}} />
                    </colgroup>

                    <thead>
                      <tr>
                        <th style={{textAlign: 'left'}} />

                        <th style={{textAlign: 'left'}}>Account</th>
                        <th style={{textAlign: 'left'}}>Description</th>
                      </tr>
                    </thead>

                    <tbody>
                      <tr>
                        <td>1</td>
                        <td style={{textAlign: 'left'}}><strong><Tooltip tip="SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7" cta="Program ID" href="https://solscan.io/account/SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7">Light System Program</Tooltip></strong></td>
                        <td>Verifies validity proofs and executes compressed account state transitions.</td>
                      </tr>

                      <tr>
                        <td>2</td>
                        <td style={{textAlign: 'left'}}><strong>CPI Authority PDA</strong></td>
                        <td>PDA that authorizes CPIs from the Light Token Program to the Light System Program.</td>
                      </tr>

                      <tr>
                        <td>3</td>
                        <td style={{textAlign: 'left'}}><strong>Registered Program PDA</strong></td>
                        <td>Proves the Light Token Program is registered to use compression.</td>
                      </tr>

                      <tr>
                        <td>4</td>
                        <td style={{textAlign: 'left'}}><strong><Tooltip tip="HZH7qSLcpAeDqCopVU4e5XkhT9j3JFsQiq8CmruY3aru" cta="PDA" href="https://solscan.io/account/HZH7qSLcpAeDqCopVU4e5XkhT9j3JFsQiq8CmruY3aru">Account Compression Authority</Tooltip></strong></td>
                        <td>Signs CPI calls from the Light System Program to the Account Compression Program.</td>
                      </tr>

                      <tr>
                        <td>5</td>
                        <td style={{textAlign: 'left'}}><strong><Tooltip tip="compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq" cta="Program ID" href="https://solscan.io/account/compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq">Account Compression Program</Tooltip></strong></td>
                        <td>Writes to state and address Merkle tree accounts.</td>
                      </tr>

                      <tr>
                        <td>6</td>
                        <td style={{textAlign: 'left'}}><strong><Tooltip tip="11111111111111111111111111111111" cta="Program ID" href="https://solscan.io/account/11111111111111111111111111111111">System Program</Tooltip></strong></td>
                        <td>Solana System Program.</td>
                      </tr>
                    </tbody>
                  </table>
                </Accordion>

                ```rust theme={null}
                use light_token::instruction::SystemAccountInfos;

                let system_accounts = SystemAccountInfos {
                    light_system_program: ctx.accounts.light_system_program.to_account_info(),
                    cpi_authority_pda: ctx.accounts.cpi_authority_pda.to_account_info(),
                    registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(),
                    account_compression_authority: ctx.accounts.account_compression_authority.to_account_info(),
                    account_compression_program: ctx.accounts.account_compression_program.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                };
                ```
              </Step>

              <Step>
                ### Build Account Infos and CPI the Light Token Program

                Use `invoke` or `invoke_signed`:

                * When `mint_seed` is an external keypair, use `invoke`.
                * When `mint_seed` is a PDA, use `invoke_signed` with its seeds.
                * When both `mint_seed` and `authority` are PDAs, use `invoke_signed` with both seeds.

                <Tabs>
                  <Tab title="invoke (External signer)">
                    ```rust theme={null}
                    use light_token::instruction::CreateMintCpi;

                    CreateMintCpi::new(
                        mint_seed.clone(),
                        authority.clone(),
                        payer.clone(),
                        address_tree.clone(), // stores address
                        output_queue.clone(), // stores account when inactive
                        compressible_config.clone(), // rent settings
                        mint.clone(),
                        rent_sponsor.clone(),
                        system_accounts,
                        params,
                    )
                    .invoke()
                    ```
                  </Tab>

                  <Tab title="invoke_signed (PDA mint seed)">
                    ```rust theme={null}
                    use light_token::instruction::CreateMintCpi;

                    let signer_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[bump]];

                    CreateMintCpi::new(
                        mint_seed.clone(),
                        authority.clone(),
                        payer.clone(),
                        address_tree.clone(),
                        output_queue.clone(),
                        compressible_config.clone(),
                        mint.clone(),
                        rent_sponsor.clone(),
                        system_accounts,
                        params,
                    )
                    .invoke_signed(&[signer_seeds])
                    ```
                  </Tab>

                  <Tab title="invoke_signed (Two PDA signers)">
                    ```rust theme={null}
                    use light_token::instruction::CreateMintCpi;

                    let mint_seed_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[mint_seed_bump]];
                    let authority_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[authority_bump]];

                    CreateMintCpi::new(
                        mint_seed.clone(),
                        authority.clone(),
                        payer.clone(),
                        address_tree.clone(),
                        output_queue.clone(),
                        compressible_config.clone(),
                        mint.clone(),
                        rent_sponsor.clone(),
                        system_accounts,
                        params,
                    )
                    .invoke_signed(&[mint_seed_seeds, authority_seeds])
                    ```
                  </Tab>
                </Tabs>
              </Step>
            </Steps>

            # Full Code Example

            <Tabs>
              <Tab title="Anchor">
                <Info>
                  View the [source code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/instruction/create_mint.rs) and [full example](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-mint) with shared test utilities.
                </Info>

                <CodeGroup>
                  ```rust lib.rs theme={null}
                  #![allow(unexpected_cfgs, deprecated)]

                  use anchor_lang::prelude::*;
                  use light_token::instruction::{
                      CreateMintCpi, CreateMintParams, SystemAccountInfos, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP,
                  };
                  use light_token::{CompressedProof, ExtensionInstructionData, TokenMetadataInstructionData};

                  declare_id!("A1rJEoepgKYWZYZ8KVFpxgeeRGwBrU7xk8S39srjVkUX");

                  /// Token metadata parameters for creating a mint with metadata.
                  #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
                  pub struct TokenMetadataParams {
                      pub name: Vec<u8>,
                      pub symbol: Vec<u8>,
                      pub uri: Vec<u8>,
                      pub update_authority: Option<Pubkey>,
                  }

                  #[program]
                  pub mod light_token_anchor_create_mint {
                      use super::*;

                      pub fn create_mint(
                          ctx: Context<CreateMintAccounts>,
                          decimals: u8,
                          address_merkle_tree_root_index: u16,
                          compression_address: [u8; 32],
                          proof: CompressedProof,
                          freeze_authority: Option<Pubkey>,
                          bump: u8,
                          rent_payment: Option<u8>,
                          write_top_up: Option<u32>,
                          metadata: Option<TokenMetadataParams>,
                      ) -> Result<()> {
                          let mint = light_token::instruction::find_mint_address(ctx.accounts.mint_seed.key).0;

                          let extensions = metadata.map(|m| {
                              vec![ExtensionInstructionData::TokenMetadata(
                                  TokenMetadataInstructionData {
                                      update_authority: m
                                          .update_authority
                                          .map(|p| p.to_bytes().into()),
                                      name: m.name,
                                      symbol: m.symbol,
                                      uri: m.uri,
                                      additional_metadata: None,
                                  },
                              )]
                          });

                          let params = CreateMintParams {
                              decimals,
                              address_merkle_tree_root_index,
                              mint_authority: *ctx.accounts.authority.key,
                              proof,
                              compression_address,
                              mint,
                              bump,
                              freeze_authority,
                              extensions,
                              rent_payment: rent_payment.unwrap_or(DEFAULT_RENT_PAYMENT),
                              write_top_up: write_top_up.unwrap_or(DEFAULT_WRITE_TOP_UP),
                          };

                          let system_accounts = SystemAccountInfos {
                              light_system_program: ctx.accounts.light_system_program.to_account_info(),
                              cpi_authority_pda: ctx.accounts.cpi_authority_pda.to_account_info(),
                              registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(),
                              account_compression_authority: ctx.accounts.account_compression_authority.to_account_info(),
                              account_compression_program: ctx.accounts.account_compression_program.to_account_info(),
                              system_program: ctx.accounts.system_program.to_account_info(),
                          };

                          CreateMintCpi {
                              mint_seed: ctx.accounts.mint_seed.to_account_info(),
                              authority: ctx.accounts.authority.to_account_info(),
                              payer: ctx.accounts.payer.to_account_info(),
                              address_tree: ctx.accounts.address_tree.to_account_info(),
                              output_queue: ctx.accounts.output_queue.to_account_info(),
                              compressible_config: ctx.accounts.compressible_config.to_account_info(),
                              mint: ctx.accounts.mint.to_account_info(),
                              rent_sponsor: ctx.accounts.rent_sponsor.to_account_info(),
                              system_accounts,
                              cpi_context: None,
                              cpi_context_account: None,
                              params,
                          }
                          .invoke()?;
                          Ok(())
                      }
                  }

                  #[derive(Accounts)]
                  pub struct CreateMintAccounts<'info> {
                      /// CHECK: Light token program for CPI
                      pub light_token_program: AccountInfo<'info>,
                      pub mint_seed: Signer<'info>,
                      /// CHECK: Validated by light-token CPI
                      pub authority: AccountInfo<'info>,
                      #[account(mut)]
                      pub payer: Signer<'info>,
                      /// CHECK: Validated by light-token CPI
                      #[account(mut)]
                      pub address_tree: AccountInfo<'info>,
                      /// CHECK: Validated by light-token CPI
                      #[account(mut)]
                      pub output_queue: AccountInfo<'info>,
                      /// CHECK: Validated by light-token CPI
                      pub light_system_program: AccountInfo<'info>,
                      /// CHECK: Validated by light-token CPI
                      pub cpi_authority_pda: AccountInfo<'info>,
                      /// CHECK: Validated by light-token CPI
                      pub registered_program_pda: AccountInfo<'info>,
                      /// CHECK: Validated by light-token CPI
                      pub account_compression_authority: AccountInfo<'info>,
                      /// CHECK: Validated by light-token CPI
                      pub account_compression_program: AccountInfo<'info>,
                      pub system_program: Program<'info, System>,
                      /// CHECK: Validated by light-token CPI - use light_token::token::config_pda()
                      pub compressible_config: AccountInfo<'info>,
                      /// CHECK: Validated by light-token CPI - derived from find_mint_address(mint_seed)
                      #[account(mut)]
                      pub mint: AccountInfo<'info>,
                      /// CHECK: Validated by light-token CPI - use light_token::token::rent_sponsor_pda()
                      #[account(mut)]
                      pub rent_sponsor: AccountInfo<'info>,
                  }
                  ```

                  ```rust test.rs theme={null}
                  use anchor_lang::{InstructionData, ToAccountMetas};
                  use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc};
                  use light_token_anchor_create_mint::{accounts, instruction::CreateMint, ID};
                  use light_token::instruction::{
                      config_pda, derive_mint_compressed_address, find_mint_address, rent_sponsor_pda,
                      SystemAccounts, LIGHT_TOKEN_PROGRAM_ID, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP,
                  };
                  use anchor_lang::system_program;
                  use solana_sdk::{
                      instruction::Instruction,
                      signature::Keypair,
                      signer::Signer,
                  };

                  #[tokio::test]
                  async fn test_create_mint() {
                      let config =
                          ProgramTestConfig::new_v2(true, Some(vec![("light_token_anchor_create_mint", ID)]));
                      let mut rpc = LightProgramTest::new(config).await.unwrap();
                      let payer = rpc.get_payer().insecure_clone();

                      let mint_seed = Keypair::new();
                      let mint_authority = payer.pubkey();
                      let decimals = 9u8;

                      let address_tree = rpc.get_address_tree_v2();
                      let output_queue = rpc.get_random_state_tree_info().unwrap().queue;

                      let compression_address =
                          derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree);

                      let (mint_pda, bump) = find_mint_address(&mint_seed.pubkey());

                      let rpc_result = rpc
                          .get_validity_proof(
                              vec![],
                              vec![light_client::indexer::AddressWithTree {
                                  address: compression_address,
                                  tree: address_tree.tree,
                              }],
                              None,
                          )
                          .await
                          .unwrap()
                          .value;

                      let system_accounts = SystemAccounts::default();

                      // Call the anchor program to create mint
                      let ix = Instruction {
                          program_id: ID,
                          accounts: accounts::CreateMintAccounts {
                              light_token_program: LIGHT_TOKEN_PROGRAM_ID,
                              mint_seed: mint_seed.pubkey(),
                              authority: mint_authority,
                              payer: payer.pubkey(),
                              address_tree: address_tree.tree,
                              output_queue,
                              light_system_program: system_accounts.light_system_program,
                              cpi_authority_pda: system_accounts.cpi_authority_pda,
                              registered_program_pda: system_accounts.registered_program_pda,
                              account_compression_authority: system_accounts.account_compression_authority,
                              account_compression_program: system_accounts.account_compression_program,
                              system_program: system_program::ID,
                              compressible_config: config_pda(),
                              mint: mint_pda,
                              rent_sponsor: rent_sponsor_pda(),
                          }
                          .to_account_metas(Some(true)),
                          data: CreateMint {
                              decimals,
                              address_merkle_tree_root_index: rpc_result.addresses[0].root_index,
                              compression_address: compression_address.into(),
                              proof: rpc_result.proof.0.unwrap(),
                              freeze_authority: None,
                              bump,
                              rent_payment: Some(DEFAULT_RENT_PAYMENT),
                              write_top_up: Some(DEFAULT_WRITE_TOP_UP),
                              metadata: None,
                          }
                          .data(),
                      };

                      let sig = rpc
                          .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer, &mint_seed])
                          .await
                          .unwrap();

                      let compressed_account = rpc
                          .get_compressed_account(compression_address, None)
                          .await
                          .unwrap()
                          .value;

                      assert!(compressed_account.is_some(), "Light-mint should exist");
                      println!("Tx: {}", sig);
                  }
                  ```
                </CodeGroup>
              </Tab>
            </Tabs>
          </Tab>

          <Tab title="AI Prompt">
            <Prompt description="Add create-mint CPI to an Anchor program" actions={["copy", "cursor"]}>
              {`---
                            description: Add create-mint CPI to an Anchor program
                            allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
                            ---

                            ## Add create-mint CPI to an Anchor program

                            Context:
                            - Guide: https://zkcompression.com/light-token/cookbook/create-mint
                            - Skills and resources index: https://zkcompression.com/skill.md
                            - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
                            - Crate: light-token (CreateMintCpi, CreateMintParams, SystemAccountInfos)
                            - Example: https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-mint

                            Key CPI struct: \`light_token::instruction::CreateMintCpi\`

                            ### 1. Index project
                            - Grep \`declare_id|#\[program\]|anchor_lang|Account<|Pubkey|invoke|mint|decimals|authority|metadata\` across src/
                            - Glob \`**/*.rs\` and \`**/Cargo.toml\` for project structure
                            - Identify: program ID, existing instructions, account structs, mint seeds
                            - Read Cargo.toml — note existing dependencies and framework version
                            - Task subagent (Grep/Read/WebFetch) if project has multiple crates to scan in parallel

                            ### 2. Read references
                            - WebFetch the guide above — review the CPI tab under Program: token metadata config, mint params, system accounts, invoke patterns
                            - WebFetch skill.md — check for a dedicated skill and resources matching this task
                            - TaskCreate one todo per phase below to track progress

                            ### 3. Clarify intention
                            - AskUserQuestion: what is the goal? (add create-mint to existing program, new program from scratch, migrate from SPL create_mint)
                            - AskUserQuestion: should the mint seed be an external keypair or a PDA? (determines invoke vs invoke_signed)
                            - AskUserQuestion: do you need token metadata (name, symbol, uri)?
                            - Summarize findings and wait for user confirmation before implementing

                            ### 4. Create plan
                            - Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
                            - Follow the guide's step order: Configure Token Metadata → Configure Mint Params → System Accounts → Build CPI and invoke
                            - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
                            - Present the plan to the user for approval before proceeding

                            ### 5. Implement
                            - Add deps if missing: Bash \`cargo add light-token anchor-lang@0.31\`
                            - Follow the guide and the approved plan
                            - Write/Edit to create or modify files
                            - TaskUpdate to mark each step done

                            ### 6. Verify
                            - Bash \`anchor build\`
                            - Bash \`anchor test\` if tests exist
                            - TaskUpdate to mark complete

                            ### Tools
                            - mcp__zkcompression__SearchLightProtocol("<query>") for API details
                            - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
                            - Task subagent with Grep/Read/WebFetch for parallel lookups
                            - TaskList to check remaining work`}
            </Prompt>

            ```text theme={null}
            ---
            description: Add create-mint CPI to an Anchor program
            allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
            ---

            ## Add create-mint CPI to an Anchor program

            Context:
            - Guide: https://zkcompression.com/light-token/cookbook/create-mint
            - Skills and resources index: https://zkcompression.com/skill.md
            - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
            - Crate: light-token (CreateMintCpi, CreateMintParams, SystemAccountInfos)
            - Example: https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-mint

            Key CPI struct: `light_token::instruction::CreateMintCpi`

            ### 1. Index project
            - Grep `declare_id|#\[program\]|anchor_lang|Account<|Pubkey|invoke|mint|decimals|authority|metadata` across src/
            - Glob `**/*.rs` and `**/Cargo.toml` for project structure
            - Identify: program ID, existing instructions, account structs, mint seeds
            - Read Cargo.toml — note existing dependencies and framework version
            - Task subagent (Grep/Read/WebFetch) if project has multiple crates to scan in parallel

            ### 2. Read references
            - WebFetch the guide above — review the CPI tab under Program: token metadata config, mint params, system accounts, invoke patterns
            - WebFetch skill.md — check for a dedicated skill and resources matching this task
            - TaskCreate one todo per phase below to track progress

            ### 3. Clarify intention
            - AskUserQuestion: what is the goal? (add create-mint to existing program, new program from scratch, migrate from SPL create_mint)
            - AskUserQuestion: should the mint seed be an external keypair or a PDA? (determines invoke vs invoke_signed)
            - AskUserQuestion: do you need token metadata (name, symbol, uri)?
            - Summarize findings and wait for user confirmation before implementing

            ### 4. Create plan
            - Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
            - Follow the guide's step order: Configure Token Metadata → Configure Mint Params → System Accounts → Build CPI and invoke
            - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
            - Present the plan to the user for approval before proceeding

            ### 5. Implement
            - Add deps if missing: Bash `cargo add light-token anchor-lang@0.31`
            - Follow the guide and the approved plan
            - Write/Edit to create or modify files
            - TaskUpdate to mark each step done

            ### 6. Verify
            - Bash `anchor build`
            - Bash `anchor test` if tests exist
            - TaskUpdate to mark complete

            ### Tools
            - mcp__zkcompression__SearchLightProtocol("<query>") for API details
            - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
            - Task subagent with Grep/Read/WebFetch for parallel lookups
            - TaskList to check remaining work
            ```
          </Tab>
        </Tabs>
      </Tab>

      <Tab title="Anchor Macros">
        <Tabs>
          <Tab title="Guide">
            Compare to SPL:

            <Tabs>
              <Tab title="Mint">
                <CodeCompare firstCode={lightCreateMintMacroCode} secondCode={splCreateMintMacroCode} firstLabel="light-token" secondLabel="SPL" language="rust" />
              </Tab>

              <Tab title="With Token Metadata">
                <CodeCompare firstCode={lightCreateMintMetadataMacroCode} secondCode={splCreateMintMetadataMacroCode} firstLabel="light-token" secondLabel="SPL Token 2022" language="rust" />
              </Tab>
            </Tabs>

            <Note>
              Find [a full code example at the end](#full-code-example).
            </Note>

            <Steps>
              <Step>
                ### Dependencies

                ```toml theme={null}
                [dependencies]
                light-sdk = { version = "0.23.0", features = ["anchor", "v2", "cpi-context"] }
                light-sdk-macros = "0.23.0"
                light-compressible = "0.6.0"
                anchor-lang = "0.31"
                ```
              </Step>

              <Step>
                ### Program

                Add `#[light_program]` above `#[program]`:

                ```rust theme={null}
                use light_sdk_macros::light_program;

                #[light_program]
                #[program]
                pub mod my_program {
                    use super::*;

                    pub fn create_mint<'info>(
                        ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>,
                        params: CreateMintParams,
                    ) -> Result<()> {
                        Ok(())
                    }
                }
                ```
              </Step>

              <Step>
                ### Accounts struct

                Derive `LightAccounts` on your `Accounts` struct and add `#[light_account(...)]` next to `#[account(...)]`.

                <Tabs>
                  <Tab title="Mint">
                    ```rust theme={null}
                    /// CHECK: Validated by light-token CPI
                    #[account(mut)]
                    #[light_account(init,
                        mint::signer = mint_signer,
                        mint::authority = fee_payer,
                        mint::decimals = 9,
                        mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
                        mint::bump = params.mint_signer_bump
                    )]
                    pub mint: UncheckedAccount<'info>,
                    ```
                  </Tab>

                  <Tab title="Mint with metadata">
                    ```rust theme={null}
                    /// CHECK: Validated by light-token CPI
                    #[account(mut)]
                    #[light_account(init,
                        mint::signer = mint_signer,
                        mint::authority = fee_payer,
                        mint::decimals = 9,
                        mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
                        mint::bump = params.mint_signer_bump,
                        mint::name = params.name.clone(),
                        mint::symbol = params.symbol.clone(),
                        mint::uri = params.uri.clone(),
                        mint::update_authority = authority,
                        mint::additional_metadata = params.additional_metadata.clone()
                    )]
                    pub mint: UncheckedAccount<'info>,
                    ```
                  </Tab>
                </Tabs>
              </Step>
            </Steps>

            # Full code example

            <Info>
              View the [source code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/instruction/create_mint.rs) and [full example](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint) with shared test utilities.
            </Info>

            <CodeGroup>
              ```rust lib.rs theme={null}
              #![allow(deprecated)]

              use anchor_lang::prelude::*;
              use light_account::{
                  derive_light_cpi_signer, light_program, CreateAccountsProof, CpiSigner, LightAccounts,
              };

              declare_id!("HVmVqSJyMejBeUigePMSfX4aENJzCGHNxAJuT2PDMPRx");

              pub const LIGHT_CPI_SIGNER: CpiSigner =
                  derive_light_cpi_signer!("HVmVqSJyMejBeUigePMSfX4aENJzCGHNxAJuT2PDMPRx");

              pub const MINT_SIGNER_SEED: &[u8] = b"mint_signer";

              #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
              pub struct CreateMintParams {
                  pub create_accounts_proof: CreateAccountsProof,
                  pub mint_signer_bump: u8,
              }

              #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
              pub struct CreateMintWithMetadataParams {
                  pub create_accounts_proof: CreateAccountsProof,
                  pub mint_signer_bump: u8,
                  pub name: Vec<u8>,
                  pub symbol: Vec<u8>,
                  pub uri: Vec<u8>,
              }

              #[derive(Accounts, LightAccounts)]
              #[instruction(params: CreateMintParams)]
              pub struct CreateMint<'info> {
                  #[account(mut)]
                  pub fee_payer: Signer<'info>,

                  pub authority: Signer<'info>,

                  /// CHECK: PDA derived from authority
                  #[account(
                      seeds = [MINT_SIGNER_SEED, authority.key().as_ref()],
                      bump,
                  )]
                  pub mint_signer: UncheckedAccount<'info>,

                  /// CHECK: Initialized by light_mint CPI
                  #[account(mut)]
                  #[light_account(init,
                      mint::signer = mint_signer,
                      mint::authority = fee_payer,
                      mint::decimals = 9,
                      mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
                      mint::bump = params.mint_signer_bump
                  )]
                  pub mint: UncheckedAccount<'info>,

                  /// CHECK: Compression config PDA
                  pub compression_config: AccountInfo<'info>,

                  /// CHECK: Light Token config
                  pub light_token_config: AccountInfo<'info>,

                  /// CHECK: Rent sponsor
                  #[account(mut)]
                  pub light_token_rent_sponsor: AccountInfo<'info>,

                  /// CHECK: Light Token program
                  pub light_token_program: AccountInfo<'info>,

                  /// CHECK: Light Token CPI authority
                  pub light_token_cpi_authority: AccountInfo<'info>,

                  pub system_program: Program<'info, System>,
              }

              #[derive(Accounts, LightAccounts)]
              #[instruction(params: CreateMintWithMetadataParams)]
              pub struct CreateMintWithMetadata<'info> {
                  #[account(mut)]
                  pub fee_payer: Signer<'info>,

                  pub authority: Signer<'info>,

                  /// CHECK: PDA derived from authority
                  #[account(
                      seeds = [MINT_SIGNER_SEED, authority.key().as_ref()],
                      bump,
                  )]
                  pub mint_signer: UncheckedAccount<'info>,

                  /// CHECK: Initialized by light_mint CPI
                  #[account(mut)]
                  #[light_account(init,
                      mint::signer = mint_signer,
                      mint::authority = fee_payer,
                      mint::decimals = 9,
                      mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
                      mint::bump = params.mint_signer_bump,
                      mint::name = params.name.clone(),
                      mint::symbol = params.symbol.clone(),
                      mint::uri = params.uri.clone(),
                      mint::update_authority = authority
                  )]
                  pub mint: UncheckedAccount<'info>,

                  /// CHECK: Compression config PDA
                  pub compression_config: AccountInfo<'info>,

                  /// CHECK: Light Token config
                  pub light_token_config: AccountInfo<'info>,

                  /// CHECK: Rent sponsor
                  #[account(mut)]
                  pub light_token_rent_sponsor: AccountInfo<'info>,

                  /// CHECK: Light Token program
                  pub light_token_program: AccountInfo<'info>,

                  /// CHECK: Light Token CPI authority
                  pub light_token_cpi_authority: AccountInfo<'info>,

                  pub system_program: Program<'info, System>,
              }

              #[light_program]
              #[program]
              pub mod light_token_macro_create_mint {
                  use super::*;

                  #[allow(unused_variables)]
                  pub fn create_mint<'info>(
                      ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>,
                      params: CreateMintParams,
                  ) -> Result<()> {
                      Ok(())
                  }

                  #[allow(unused_variables)]
                  pub fn create_mint_with_metadata<'info>(
                      ctx: Context<'_, '_, '_, 'info, CreateMintWithMetadata<'info>>,
                      params: CreateMintWithMetadataParams,
                  ) -> Result<()> {
                      Ok(())
                  }
              }
              ```

              ```rust test.rs theme={null}
              use anchor_lang::{prelude::borsh, InstructionData, ToAccountMetas};
              use light_client::interface::{
                  get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig,
              };
              use light_program_test::{
                  program_test::{setup_mock_program_data, LightProgramTest},
                  ProgramTestConfig, Rpc,
              };
              use light_account::{derive_rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID};
              use light_token::instruction::{find_mint_address, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR};
              use solana_instruction::Instruction;
              use solana_keypair::Keypair;
              use solana_pubkey::Pubkey;
              use solana_signer::Signer;

              use light_token_macro_create_mint::MINT_SIGNER_SEED;

              async fn setup() -> (light_program_test::LightProgramTest, Keypair, Pubkey, Pubkey) {
                  let program_id = light_token_macro_create_mint::ID;
                  let mut config =
                      ProgramTestConfig::new_v2(true, Some(vec![("light_token_macro_create_mint", program_id)]));
                  config = config.with_light_protocol_events();

                  let mut rpc = LightProgramTest::new(config).await.unwrap();
                  let payer = rpc.get_payer().insecure_clone();

                  let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id);

                  let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id);

                  let (init_config_ix, config_pda) = InitializeRentFreeConfig::new(
                      &program_id,
                      &payer.pubkey(),
                      &program_data_pda,
                      rent_sponsor,
                      payer.pubkey(),
                  )
                  .build();

                  rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer])
                      .await
                      .expect("Initialize config should succeed");

                  (rpc, payer, config_pda, program_id)
              }

              #[tokio::test]
              async fn test_create_mint() {
                  use light_token_macro_create_mint::CreateMintParams;

                  let (mut rpc, payer, config_pda, program_id) = setup().await;

                  let authority = Keypair::new();
                  let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address(
                      &[MINT_SIGNER_SEED, authority.pubkey().as_ref()],
                      &program_id,
                  );
                  let (mint_pda, _) = find_mint_address(&mint_signer_pda);

                  let proof_result = get_create_accounts_proof(
                      &rpc,
                      &program_id,
                      vec![CreateAccountsProofInput::mint(mint_signer_pda)],
                  )
                  .await
                  .unwrap();

                  let accounts = light_token_macro_create_mint::accounts::CreateMint {
                      fee_payer: payer.pubkey(),
                      authority: authority.pubkey(),
                      mint_signer: mint_signer_pda,
                      mint: mint_pda,
                      compression_config: config_pda,
                      light_token_config: LIGHT_TOKEN_CONFIG,
                      light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR,
                      light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(),
                      light_token_cpi_authority: light_token::constants::LIGHT_TOKEN_CPI_AUTHORITY,
                      system_program: solana_sdk::system_program::ID,
                  };

                  let instruction_data = light_token_macro_create_mint::instruction::CreateMint {
                      params: CreateMintParams {
                          create_accounts_proof: proof_result.create_accounts_proof,
                          mint_signer_bump,
                      },
                  };

                  let instruction = Instruction {
                      program_id,
                      accounts: [
                          accounts.to_account_metas(None),
                          proof_result.remaining_accounts,
                      ]
                      .concat(),
                      data: instruction_data.data(),
                  };

                  rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority])
                      .await
                      .expect("CreateMint should succeed");

                  let mint_account = rpc
                      .get_account(mint_pda)
                      .await
                      .unwrap()
                      .expect("Mint should exist on-chain");

                  use light_token_interface::state::Mint;
                  let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..])
                      .expect("Failed to deserialize Mint");

                  assert_eq!(mint.base.decimals, 9);
                  assert_eq!(
                      mint.base.mint_authority,
                      Some(payer.pubkey().to_bytes().into()),
                  );
              }

              #[tokio::test]
              async fn test_create_mint_with_metadata() {
                  use light_token_macro_create_mint::CreateMintWithMetadataParams;

                  let (mut rpc, payer, config_pda, program_id) = setup().await;

                  let authority = Keypair::new();
                  let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address(
                      &[MINT_SIGNER_SEED, authority.pubkey().as_ref()],
                      &program_id,
                  );
                  let (mint_pda, _) = find_mint_address(&mint_signer_pda);

                  let proof_result = get_create_accounts_proof(
                      &rpc,
                      &program_id,
                      vec![CreateAccountsProofInput::mint(mint_signer_pda)],
                  )
                  .await
                  .unwrap();

                  let accounts = light_token_macro_create_mint::accounts::CreateMintWithMetadata {
                      fee_payer: payer.pubkey(),
                      authority: authority.pubkey(),
                      mint_signer: mint_signer_pda,
                      mint: mint_pda,
                      compression_config: config_pda,
                      light_token_config: LIGHT_TOKEN_CONFIG,
                      light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR,
                      light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(),
                      light_token_cpi_authority: light_token::constants::LIGHT_TOKEN_CPI_AUTHORITY,
                      system_program: solana_sdk::system_program::ID,
                  };

                  let instruction_data = light_token_macro_create_mint::instruction::CreateMintWithMetadata {
                      params: CreateMintWithMetadataParams {
                          create_accounts_proof: proof_result.create_accounts_proof,
                          mint_signer_bump,
                          name: b"Test Token".to_vec(),
                          symbol: b"TST".to_vec(),
                          uri: b"https://example.com/metadata.json".to_vec(),
                      },
                  };

                  let instruction = Instruction {
                      program_id,
                      accounts: [
                          accounts.to_account_metas(None),
                          proof_result.remaining_accounts,
                      ]
                      .concat(),
                      data: instruction_data.data(),
                  };

                  rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority])
                      .await
                      .expect("CreateMintWithMetadata should succeed");

                  let mint_account = rpc
                      .get_account(mint_pda)
                      .await
                      .unwrap()
                      .expect("Mint should exist on-chain");

                  use light_token_interface::state::Mint;
                  let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..])
                      .expect("Failed to deserialize Mint");

                  assert_eq!(mint.base.decimals, 9);
                  assert_eq!(
                      mint.base.mint_authority,
                      Some(payer.pubkey().to_bytes().into()),
                  );
              }
              ```
            </CodeGroup>
          </Tab>

          <Tab title="AI Prompt">
            <Prompt description="Create a rent-free mint with Anchor macros" actions={["copy", "cursor"]}>
              {`---
                            description: Create a rent-free mint with Anchor macros
                            allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
                            ---

                            ## Create a rent-free mint with Anchor macros

                            Context:
                            - Guide: https://zkcompression.com/light-token/cookbook/create-mint
                            - Skills and resources index: https://zkcompression.com/skill.md
                            - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
                            - Crates: light-sdk, light-sdk-macros, light-compressible, anchor-lang
                            - Example: https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint

                            Key macros: \`#[light_program]\`, \`LightAccounts\`, \`#[light_account(init, mint::...)]\`

                            ### 1. Index project
                            - Grep \`#\[program\]|anchor_lang|Account<|Accounts|seeds|init|payer|mint\` across src/
                            - Glob \`**/*.rs\` and \`**/Cargo.toml\` for project structure
                            - Identify: existing program module, account structs, mint patterns, PDA seeds
                            - Read Cargo.toml — note existing dependencies and framework version
                            - Task subagent (Grep/Read/WebFetch) if project has multiple crates to scan in parallel

                            ### 2. Read references
                            - WebFetch the guide above — review the Anchor Macros tab under Program
                            - WebFetch skill.md — check for a dedicated skill and resources matching this task
                            - TaskCreate one todo per phase below to track progress

                            ### 3. Clarify intention
                            - AskUserQuestion: what is the goal? (new program from scratch, add rent-free mint to existing program, migrate from SPL create_mint)
                            - AskUserQuestion: do you need token metadata (name, symbol, uri)?
                            - Summarize findings and wait for user confirmation before implementing

                            ### 4. Create plan
                            - Based on steps 1–3, draft an implementation plan
                            - Follow the guide's step order: Dependencies → Program Module (#[light_program]) → Accounts Struct (#[light_account(init, mint::...)])
                            - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
                            - Present the plan to the user for approval before proceeding

                            ### 5. Implement
                            - Add deps if missing: Bash \`cargo add light-sdk@0.23 --features anchor,v2,cpi-context\` and \`cargo add light-sdk-macros@0.23 light-compressible@0.6 anchor-lang@0.31\`
                            - Follow the guide and the approved plan
                            - Write/Edit to create or modify files
                            - TaskUpdate to mark each step done

                            ### 6. Verify
                            - Bash \`anchor build\`
                            - Bash \`anchor test\` if tests exist
                            - TaskUpdate to mark complete

                            ### Tools
                            - mcp__zkcompression__SearchLightProtocol("<query>") for API details
                            - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
                            - Task subagent with Grep/Read/WebFetch for parallel lookups
                            - TaskList to check remaining work`}
            </Prompt>

            ```text theme={null}
            ---
            description: Create a rent-free mint with Anchor macros
            allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
            ---

            ## Create a rent-free mint with Anchor macros

            Context:
            - Guide: https://zkcompression.com/light-token/cookbook/create-mint
            - Skills and resources index: https://zkcompression.com/skill.md
            - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
            - Crates: light-sdk, light-sdk-macros, light-compressible, anchor-lang
            - Example: https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint

            Key macros: `#[light_program]`, `LightAccounts`, `#[light_account(init, mint::...)]`

            ### 1. Index project
            - Grep `#\[program\]|anchor_lang|Account<|Accounts|seeds|init|payer|mint` across src/
            - Glob `**/*.rs` and `**/Cargo.toml` for project structure
            - Identify: existing program module, account structs, mint patterns, PDA seeds
            - Read Cargo.toml — note existing dependencies and framework version
            - Task subagent (Grep/Read/WebFetch) if project has multiple crates to scan in parallel

            ### 2. Read references
            - WebFetch the guide above — review the Anchor Macros tab under Program
            - WebFetch skill.md — check for a dedicated skill and resources matching this task
            - TaskCreate one todo per phase below to track progress

            ### 3. Clarify intention
            - AskUserQuestion: what is the goal? (new program from scratch, add rent-free mint to existing program, migrate from SPL create_mint)
            - AskUserQuestion: do you need token metadata (name, symbol, uri)?
            - Summarize findings and wait for user confirmation before implementing

            ### 4. Create plan
            - Based on steps 1–3, draft an implementation plan
            - Follow the guide's step order: Dependencies → Program Module (#[light_program]) → Accounts Struct (#[light_account(init, mint::...)])
            - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
            - Present the plan to the user for approval before proceeding

            ### 5. Implement
            - Add deps if missing: Bash `cargo add light-sdk@0.23 --features anchor,v2,cpi-context` and `cargo add light-sdk-macros@0.23 light-compressible@0.6 anchor-lang@0.31`
            - Follow the guide and the approved plan
            - Write/Edit to create or modify files
            - TaskUpdate to mark each step done

            ### 6. Verify
            - Bash `anchor build`
            - Bash `anchor test` if tests exist
            - TaskUpdate to mark complete

            ### Tools
            - mcp__zkcompression__SearchLightProtocol("<query>") for API details
            - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
            - Task subagent with Grep/Read/WebFetch for parallel lookups
            - TaskList to check remaining work
            ```
          </Tab>
        </Tabs>
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

## Related Guides

<CardGroup cols={2}>
  <Card title="Add interface PDA" icon="link" href="/light-token/cookbook/add-interface-pda" horizontal />

  <Card title="Mint tokens" icon="coins" href="/light-token/cookbook/mint-to" horizontal />
</CardGroup>

***

## Didn't find what you were looking for?

<Callout type="info">
  Reach out! [Telegram](https://t.me/swen_light) | [email](mailto:support@lightprotocol.com) | [Discord](https://discord.com/invite/7cJ8BhAXhu)
</Callout>
