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

# Create Mint Account wih Token Metadata

> Program and client guides to create a mint with token metadata. Includes step-by-step implementation and full code examples.

export const CodeRunnerEmbed = ({example, height = "450px"}) => {
  const baseUrl = process.env.CODE_RUNNER_URL || "http://localhost:3030";
  return <iframe src={`${baseUrl}/embed/${example}`} style={{
    width: "100%",
    height: height,
    border: "none",
    borderRadius: "8px",
    overflow: "hidden"
  }} allow="clipboard-write" title={`${example} code example`} />;
};

export const CodeCompare = ({firstCode = "", secondCode = "", firstLabel = "Light Token", secondLabel = "SPL"}) => {
  const [sliderPercent, setSliderPercent] = useState(0);
  const [isDragging, setIsDragging] = useState(false);
  const [isAnimating, setIsAnimating] = useState(false);
  const containerRef = useRef(null);
  const animationRef = useRef(null);
  const isLightMode = sliderPercent > 50;
  const highlightCode = code => {
    let escaped = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    const pattern = /(\/\/.*$)|(["'`])(?:(?!\2)[^\\]|\\.)*?\2|\b(const|let|var|await|async|import|from|export|return|if|else|function|class|new|throw|try|catch)\b|\.([a-zA-Z_][a-zA-Z0-9_]*)\b|\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/gm;
    return escaped.replace(pattern, (match, comment, stringQuote, keyword, property, func) => {
      if (comment) return `<span style="color:#6b7280;font-style:italic">${match}</span>`;
      if (stringQuote) return `<span style="color:#059669">${match}</span>`;
      if (keyword) return `<span style="color:#db2777">${match}</span>`;
      if (property) return `.<span style="color:#0891b2">${property}</span>`;
      if (func) return `<span style="color:#2563eb">${match}</span>`;
      return match;
    });
  };
  const animateTo = target => {
    if (animationRef.current) cancelAnimationFrame(animationRef.current);
    setIsAnimating(true);
    const start = sliderPercent;
    const startTime = performance.now();
    const duration = 400;
    const animate = currentTime => {
      const elapsed = currentTime - startTime;
      const progress = Math.min(elapsed / duration, 1);
      const eased = 1 - Math.pow(1 - progress, 3);
      const current = start + (target - start) * eased;
      setSliderPercent(current);
      if (progress < 1) {
        animationRef.current = requestAnimationFrame(animate);
      } else {
        setSliderPercent(target);
        setIsAnimating(false);
        animationRef.current = null;
      }
    };
    animationRef.current = requestAnimationFrame(animate);
  };
  const handleToggle = () => {
    animateTo(isLightMode ? 0 : 100);
  };
  const handleMouseDown = e => {
    if (isAnimating) {
      cancelAnimationFrame(animationRef.current);
      setIsAnimating(false);
    }
    e.preventDefault();
    setIsDragging(true);
  };
  const handleMouseUp = () => {
    setIsDragging(false);
  };
  const handleMouseMove = e => {
    if (!isDragging || !containerRef.current) return;
    const rect = containerRef.current.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const percent = Math.max(0, Math.min(100, x / rect.width * 100));
    setSliderPercent(percent);
  };
  const handleTouchMove = e => {
    if (!containerRef.current) return;
    if (isAnimating) {
      cancelAnimationFrame(animationRef.current);
      setIsAnimating(false);
    }
    const rect = containerRef.current.getBoundingClientRect();
    const x = e.touches[0].clientX - rect.left;
    const percent = Math.max(0, Math.min(100, x / rect.width * 100));
    setSliderPercent(percent);
  };
  const handleKeyDown = e => {
    if (e.key === "ArrowLeft") {
      setSliderPercent(p => Math.max(0, p - 5));
    } else if (e.key === "ArrowRight") {
      setSliderPercent(p => Math.min(100, p + 5));
    }
  };
  useEffect(() => {
    if (isDragging) {
      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);
      return () => {
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", handleMouseUp);
      };
    }
  }, [isDragging]);
  useEffect(() => {
    return () => {
      if (animationRef.current) cancelAnimationFrame(animationRef.current);
    };
  }, []);
  return <>
      <div className="rounded-3xl not-prose mt-4 backdrop-blur-xl border overflow-hidden" style={{
    fontFamily: 'Inter, sans-serif',
    borderColor: '#d4d4d8'
  }}>
        {}
        <div className="flex items-center justify-between px-4 py-3 border-b" style={{
    background: 'linear-gradient(to bottom, #f8f9fa, #f1f3f4)',
    borderColor: '#e4e4e7'
  }}>
          <span className="text-sm font-medium" style={{
    color: '#52525b'
  }}>
            {isLightMode ? secondLabel : firstLabel}
          </span>

          {}
          <div onClick={handleToggle} style={{
    position: 'relative',
    width: '56px',
    height: '28px',
    background: '#e0e0e0',
    borderRadius: '14px',
    boxShadow: 'inset -2px -2px 4px #ffffff, inset 2px 2px 4px #b0b0b0',
    cursor: 'pointer',
    transition: 'background 0.3s ease, box-shadow 0.3s ease'
  }}>
            {}
            <div style={{
    position: 'absolute',
    width: '24px',
    height: '24px',
    background: 'linear-gradient(145deg, #f5f5f5, #e0e0e0)',
    borderRadius: '12px',
    top: '2px',
    left: isLightMode ? '30px' : '2px',
    boxShadow: '-2px -2px 4px #ffffff, 2px 2px 4px #b0b0b0',
    transition: 'all 0.3s ease-in-out',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center'
  }}>
              {}
              <div style={{
    width: '6px',
    height: '6px',
    background: isLightMode ? '#0066ff' : '#999',
    borderRadius: '50%',
    boxShadow: isLightMode ? '0 0 8px 2px #0066ff' : '0 0 4px 1px rgba(0, 0, 0, 0.1)',
    transition: 'all 0.3s ease-in-out'
  }} />
            </div>
          </div>
        </div>

        {}
        <div ref={containerRef} className="p-0" style={{
    cursor: isDragging ? "grabbing" : "default"
  }} onTouchMove={handleTouchMove} tabIndex={0} onKeyDown={handleKeyDown} role="slider" aria-valuenow={sliderPercent} aria-valuemin={0} aria-valuemax={100} aria-label="Code comparison slider">
          <div className="relative" style={{
    minHeight: "140px",
    overflowX: "auto"
  }}>
            <div style={{
    display: "grid"
  }}>
              {}
              <pre className="m-0 p-4 text-zinc-700 dark:text-white/80 bg-transparent" style={{
    gridArea: "1/1",
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
    fontSize: "13px",
    lineHeight: "1.6",
    whiteSpace: "pre",
    zIndex: 1
  }} dangerouslySetInnerHTML={{
    __html: highlightCode(firstCode)
  }} />

              {}
              <pre className="m-0 p-4 text-zinc-700 dark:text-white/80 bg-white dark:bg-zinc-900" style={{
    gridArea: "1/1",
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
    fontSize: "13px",
    lineHeight: "1.6",
    whiteSpace: "pre",
    zIndex: 2,
    clipPath: `inset(0 ${100 - sliderPercent}% 0 0)`
  }} dangerouslySetInnerHTML={{
    __html: highlightCode(secondCode)
  }} />
            </div>

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

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

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

export const CompressibleRentCalculator = () => {
  const [hours, setHours] = useState(24);
  const [lamportsPerWrite, setLamportsPerWrite] = useState(776);
  const [showCustomHours, setShowCustomHours] = useState(false);
  const [showCustomLamports, setShowCustomLamports] = useState(false);
  const [showFormula, setShowFormula] = useState(false);
  const DATA_LEN = 260;
  const BASE_RENT = 128;
  const LAMPORTS_PER_BYTE_PER_EPOCH = 1;
  const MINUTES_PER_EPOCH = 90;
  const COMPRESSION_INCENTIVE = 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_INCENTIVE;
  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 = [776];
  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 === 776 ? '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_incentive<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_incentive = {COMPRESSION_INCENTIVE.toLocaleString()} lamports
            </div>}
        </div>
      </div>
    </div>;
};

***

1. Mint accounts uniquely represent a token on Solana and store its global metadata.
2. Mints for light-token accounts are compressed accounts and rent-free.

## Try It Live

<CodeRunnerEmbed example="create-mint" height="450px" />

## Get Started

<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 T22 `createMint`
    * Otherwise it defaults to `CTOKEN_PROGRAM_ID` → creates a light-token mint

    You can use the same interface regardless of mint type.

    Compare to SPL:

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

const mint = await createMint(
connection,
payer,
mintAuthority,
freezeAuthority,
decimals
);`}
      secondCode={`// light-token createMint
import { createMintInterface } from "@lightprotocol/compressed-token";

const { mint } = await createMintInterface(
rpc,
payer,
mintAuthority,
freezeAuthority,
decimals,
mintKeypair
);`}
      firstLabel="SPL"
      secondLabel="light-token"
    />

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

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

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

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

              Install the CLI globally:

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

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

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

              Install the CLI globally:

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

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

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

              Install the CLI globally:

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

        ```bash theme={null}
        # Start local test-validator in separate terminal
        light test-validator
        ```

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

        <CodeGroup>
          ```typescript Action theme={null}
          import { Keypair } from "@solana/web3.js";
          import { createRpc } from "@lightprotocol/stateless.js";
          import { createMintInterface, createTokenMetadata } from "@lightprotocol/compressed-token";

          async function main() {
          const rpc = createRpc();
          const payer = Keypair.generate();
          const sig = await rpc.requestAirdrop(payer.publicKey, 10e9);
          await rpc.confirmTransaction(sig);

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

          console.log("Mint:", mint.toBase58());
          console.log("Tx:", transactionSignature);
          }

          main().catch(console.error);

          ```

          ```typescript Instruction theme={null}
          import "dotenv/config";
          import { Keypair, ComputeBudgetProgram, PublicKey } from "@solana/web3.js";
          import {
              createRpc,
              buildAndSignTx,
              sendAndConfirmTx,
              getBatchAddressTreeInfo,
              selectStateTreeInfo,
              CTOKEN_PROGRAM_ID,
              DerivationMode,
          } 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()],
                  CTOKEN_PROGRAM_ID
              );
          }

          const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
          const payer = Keypair.fromSecretKey(
              new Uint8Array(
                  JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
              )
          );

          (async function () {
              const rpc = createRpc(RPC_URL);

              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 }],
                  DerivationMode.compressible
              );

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

              const { blockhash } = await rpc.getLatestBlockhash();
              const tx = buildAndSignTx(
                  [ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }), ix],
                  payer,
                  blockhash,
                  [mintSigner]
              );
              const signature = await sendAndConfirmTx(rpc, tx, { skipPreflight: true });

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

  <Tab title="Rust Client">
    The example creates a light-mint with token metadata.

    1. Derive the mint address from the mint signer and address tree

    2. Fetch a <Tooltip tip="Validity proofs are 128 byte zero-knowledge proofs (ZKP). Your RPC generates the validity proof for you.">validity proof</Tooltip> from your RPC that proves the address does not exist yet.

    3. Configure mint and your token metadata (name, symbol, URI, additional metadata)

    4. Build the instruction with `CreateCMint::new()` and send the transaction.

    ```rust theme={null}
    use light_ctoken_sdk::ctoken::CreateCMint;

    let create_cmint = CreateCMint::new(
        params,
        mint_signer.pubkey(),
        payer.pubkey(),
        address_tree.tree,
        output_queue,
    );
    let instruction = create_cmint.instruction()?;
    ```

    <Steps>
      <Step>
        ### Prerequisites

        <Accordion title="Dependencies">
          ```toml Cargo.toml theme={null}
          [dependencies]
          light-compressed-token-sdk = "0.1"
          light-client = "0.1"
          light-ctoken-types = "0.1"
          solana-sdk = "2.2"
          borsh = "0.10"
          tokio = { version = "1.36", features = ["full"] }

          [dev-dependencies]
          light-program-test = "0.1"  # For in-memory tests with LiteSVM
          ```
        </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@alpha
                  ```
                </Tab>

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

                <Tab title="pnpm">
                  ```bash theme={null}
                  pnpm add -g @lightprotocol/zk-compression-cli@alpha
                  ```
                </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 Mint with Token Metadata

        ```rust theme={null}
        use light_client::indexer::{AddressWithTree, Indexer};
        use light_client::rpc::{LightClient, LightClientConfig, Rpc};
        use light_ctoken_sdk::ctoken::{CreateCMint, CreateCMintParams};
        use light_ctoken_interface::instructions::extensions::token_metadata::TokenMetadataInstructionData;
        use light_ctoken_interface::instructions::extensions::ExtensionInstructionData;
        use light_ctoken_interface::state::AdditionalMetadata;
        use serde_json;
        use solana_sdk::{bs58, pubkey::Pubkey, signature::Keypair, signer::Signer};
        use std::convert::TryFrom;
        use std::env;
        use std::fs;

        #[tokio::test(flavor = "multi_thread")]
        async fn test_create_compressed_mint_with_metadata() {
            dotenvy::dotenv().ok();

            let keypair_path = env::var("KEYPAIR_PATH")
                .unwrap_or_else(|_| format!("{}/.config/solana/id.json", env::var("HOME").unwrap()));
            let payer = load_keypair(&keypair_path).expect("Failed to load keypair");

            let api_key = env::var("api_key") // Set api_key in your .env
                .expect("api_key environment variable must be set");

            let config = LightClientConfig::devnet(
                Some("https://devnet.helius-rpc.com".to_string()),
                Some(api_key),
            );
            let mut rpc = LightClient::new_with_retry(config, None)
                .await
                .expect("Failed to initialize LightClient");

            // Create compressed mint with metadata
            let (mint_pda, compression_address) = create_compressed_mint(&mut rpc, &payer, 9).await;

            println!("\n=== Created Compressed Mint ===");
            println!("Mint PDA: {}", mint_pda);
            println!("Compression Address: {}", bs58::encode(compression_address).into_string());
            println!("Decimals: 9");
            println!("Name: Example Token");
            println!("Symbol: EXT");
            println!("URI: https://example.com/metadata.json");
        }

        pub async fn create_compressed_mint<R: Rpc + Indexer>(
            rpc: &mut R,
            payer: &Keypair,
            decimals: u8,
        ) -> (Pubkey, [u8; 32]) {
            let mint_signer = Keypair::new();
            let address_tree = rpc.get_address_tree_v2();

            // Fetch active state trees for devnet
            let _ = rpc.get_latest_active_state_trees().await;
            let output_pubkey = match rpc
                .get_random_state_tree_info()
                .ok()
                .or_else(|| rpc.get_random_state_tree_info_v1().ok())
            {
                Some(info) => info
                    .get_output_pubkey()
                    .expect("Invalid state tree type for output"),
                None => {
                    let queues = rpc
                        .indexer_mut()
                        .expect("IndexerNotInitialized")
                        .get_queue_info(None)
                        .await
                        .expect("Failed to fetch queue info")
                        .value
                        .queues;
                    queues
                        .get(0)
                        .map(|q| q.queue)
                        .expect("NoStateTreesAvailable: no active state trees returned")
                }
            };

            // Derive compression address
            let compression_address = light_ctoken_sdk::ctoken::derive_cmint_compressed_address(
                &mint_signer.pubkey(),
                &address_tree.tree,
            );

            let mint_pda = light_ctoken_sdk::ctoken::find_cmint_address(&mint_signer.pubkey()).0;

            // Get validity proof for the address
            let rpc_result = rpc
                .get_validity_proof(
                    vec![],
                    vec![AddressWithTree {
                        address: compression_address,
                        tree: address_tree.tree,
                    }],
                    None,
                )
                .await
                .unwrap()
                .value;

            // Build params with token metadata
            let params = CreateCMintParams {
                decimals,
                address_merkle_tree_root_index: rpc_result.addresses[0].root_index,
                mint_authority: payer.pubkey(),
                proof: rpc_result.proof.0.unwrap(),
                compression_address,
                mint: mint_pda,
                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"compressed".to_vec(),
                        }]),
                    },
                )]),
            };

            // Create instruction
            let create_cmint = CreateCMint::new(
                params,
                mint_signer.pubkey(),
                payer.pubkey(),
                address_tree.tree,
                output_pubkey,
            );
            let instruction = create_cmint.instruction().unwrap();

            // Send transaction
            rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_signer])
                .await
                .unwrap();

            (mint_pda, compression_address)
        }

        fn load_keypair(path: &str) -> Result<Keypair, Box<dyn std::error::Error>> {
            let path = if path.starts_with("~") {
                path.replace("~", &env::var("HOME").unwrap_or_default())
            } else {
                path.to_string()
            };
            let file = fs::read_to_string(&path)?;
            let bytes: Vec<u8> = serde_json::from_str(&file)?;
            Ok(Keypair::try_from(&bytes[..])?)
        }
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Program Guide">
    <Note>
      Find [a full code example at the end](#full-code-example).
    </Note>

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

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

        let token_metadata = ExtensionInstructionData::TokenMetadata(
            TokenMetadataInstructionData {
                update_authority: Some(authority.to_bytes().into()),
                name: b"My Token".to_vec(),
                symbol: b"MTK".to_vec(),
                uri: b"https://example.com/metadata.json".to_vec(),
                additional_metadata: Some(vec![
                    AdditionalMetadata {
                        key: b"category".to_vec(),
                        value: b"utility".to_vec(),
                    },
                ]),
            },
        );
        ```

        <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

        Set `decimals`, `mint_authority`, `freeze_authority`, and pass the `token_metadata` from the previous step.

        ```rust theme={null}
        use light_ctoken_sdk::ctoken::CreateCMintParams;

        let params = CreateCMintParams {
            decimals: data.decimals,
            address_merkle_tree_root_index: data.address_merkle_tree_root_index,
            mint_authority: data.mint_authority,
            proof: data.proof,
            compression_address: data.compression_address,
            mint: data.mint,
            freeze_authority: data.freeze_authority,
            extensions: data.extensions,
        };
        ```

        <Info>
          The client passes a validity proof that proves the light-mint address does not exist in the address tree where it will be stored. You can safely ignore `compression_address` and `address_merkle_tree_root_index`. The client passes these for proof verification.
        </Info>
      </Step>

      <Step>
        ### System Accounts

        Include system accounts such as the Light System Program required to interact with compressed state.
        The client includes them in the instruction.

        <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 Compressed 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 Compressed 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_ctoken_sdk::ctoken::SystemAccountInfos;

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

      <Step>
        ### Build Account Infos and CPI the light token program

        1. Pass the required accounts
        2. Include `params` and `system_accounts` from the previous steps
        3. 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_ctoken_sdk::ctoken::CreateCMintCpi;

            CreateCMintCpi {
                mint_seed: mint_seed.clone(),
                authority: authority.clone(),
                payer: payer.clone(),
                address_tree: address_tree.clone(),
                output_queue: output_queue.clone(),
                system_accounts,
                cpi_context: None,
                cpi_context_account: None,
                params,
            }
            .invoke()?;
            ```
          </Tab>

          <Tab title="invoke_signed (PDA mint_seed)">
            ```rust theme={null}
            use light_ctoken_sdk::ctoken::CreateCMintCpi;

            let account_infos = CreateCMintCpi {
                mint_seed: mint_seed.clone(),
                authority: authority.clone(),
                payer: payer.clone(),
                address_tree: address_tree.clone(),
                output_queue: output_queue.clone(),
                system_accounts,
                cpi_context: None,
                cpi_context_account: None,
                params,
            };

            let signer_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[bump]];
            account_infos.invoke_signed(&[signer_seeds])?;
            ```
          </Tab>

          <Tab title="invoke_signed (Two PDA signers)">
            ```rust theme={null}
            use light_ctoken_sdk::ctoken::CreateCMintCpi;

            let account_infos = CreateCMintCpi {
                mint_seed: mint_seed.clone(),
                authority: authority.clone(),
                payer: payer.clone(),
                address_tree: address_tree.clone(),
                output_queue: output_queue.clone(),
                system_accounts,
                cpi_context: None,
                cpi_context_account: None,
                params,
            };

            let mint_seed_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[mint_seed_bump]];
            let authority_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[authority_bump]];
            account_infos.invoke_signed(&[mint_seed_seeds, authority_seeds])?;
            ```
          </Tab>
        </Tabs>
      </Step>
    </Steps>

    # Full Code Example

    <Info>
      Find the source code
      [here](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-tests/sdk-ctoken-test/src/create_cmint.rs).
    </Info>

    ```rust expandable theme={null}
    use borsh::{BorshDeserialize, BorshSerialize};
    use light_ctoken_sdk::{
        ctoken::{
            CreateCMintCpi, CreateCMintParams, ExtensionInstructionData, SystemAccountInfos,
        },
        CompressedProof,
    };
    use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};

    use crate::ID;

    /// PDA seed for mint signer in invoke_signed variant
    pub const MINT_SIGNER_SEED: &[u8] = b"mint_signer";

    /// Instruction data for create compressed mint
    #[derive(BorshSerialize, BorshDeserialize, Debug)]
    pub struct CreateCmintData {
        pub decimals: u8,
        pub address_merkle_tree_root_index: u16,
        pub mint_authority: Pubkey,
        pub proof: CompressedProof,
        pub compression_address: [u8; 32],
        pub mint: Pubkey,
        pub freeze_authority: Option<Pubkey>,
        pub extensions: Option<Vec<ExtensionInstructionData>>,
    }

    /// Handler for creating a compressed mint (invoke)
    ///
    /// Uses the CreateCMintCpi builder pattern. This demonstrates how to:
    /// 1. Build the CreateCMintParams struct from instruction data
    /// 2. Build the CreateCMintCpi with accounts
    /// 3. Call invoke() which handles instruction building and CPI
    ///
    /// Account order:
    /// - accounts[0]: compressed_token_program (for CPI)
    /// - accounts[1]: light_system_program
    /// - accounts[2]: mint_seed (signer)
    /// - accounts[3]: payer (signer, also authority)
    /// - accounts[4]: payer again (fee_payer in SDK)
    /// - accounts[5]: cpi_authority_pda
    /// - accounts[6]: registered_program_pda
    /// - accounts[7]: account_compression_authority
    /// - accounts[8]: account_compression_program
    /// - accounts[9]: system_program
    /// - accounts[10]: output_queue
    /// - accounts[11]: address_tree
    /// - accounts[12] (optional): cpi_context_account
    pub fn process_create_cmint(
        accounts: &[AccountInfo],
        data: CreateCmintData,
    ) -> Result<(), ProgramError> {
        if accounts.len() < 12 {
            return Err(ProgramError::NotEnoughAccountKeys);
        }

        // Build the params
        let params = CreateCMintParams {
            decimals: data.decimals,
            address_merkle_tree_root_index: data.address_merkle_tree_root_index,
            mint_authority: data.mint_authority,
            proof: data.proof,
            compression_address: data.compression_address,
            mint: data.mint,
            freeze_authority: data.freeze_authority,
            extensions: data.extensions,
        };

        // Build system accounts struct
        let system_accounts = SystemAccountInfos {
            light_system_program: accounts[1].clone(),
            cpi_authority_pda: accounts[5].clone(),
            registered_program_pda: accounts[6].clone(),
            account_compression_authority: accounts[7].clone(),
            account_compression_program: accounts[8].clone(),
            system_program: accounts[9].clone(),
        };

        // Build the account infos struct
        // In this case, payer == authority (accounts[3])
        CreateCMintCpi {
            mint_seed: accounts[2].clone(),
            authority: accounts[3].clone(),
            payer: accounts[3].clone(),
            address_tree: accounts[11].clone(),
            output_queue: accounts[10].clone(),
            system_accounts,
            cpi_context: None,
            cpi_context_account: None,
            params,
        }
        .invoke()?;

        Ok(())
    }

    /// Handler for creating a compressed mint with PDA mint seed (invoke_signed)
    ///
    /// Uses the CreateCMintCpi builder pattern with invoke_signed.
    /// The mint_seed is a PDA derived from this program.
    ///
    /// Account order:
    /// - accounts[0]: compressed_token_program (for CPI)
    /// - accounts[1]: light_system_program
    /// - accounts[2]: mint_seed (PDA, not signer - program signs)
    /// - accounts[3]: payer (signer, also authority)
    /// - accounts[4]: payer again (fee_payer in SDK)
    /// - accounts[5]: cpi_authority_pda
    /// - accounts[6]: registered_program_pda
    /// - accounts[7]: account_compression_authority
    /// - accounts[8]: account_compression_program
    /// - accounts[9]: system_program
    /// - accounts[10]: output_queue
    /// - accounts[11]: address_tree
    /// - accounts[12] (optional): cpi_context_account
    pub fn process_create_cmint_invoke_signed(
        accounts: &[AccountInfo],
        data: CreateCmintData,
    ) -> Result<(), ProgramError> {
        if accounts.len() < 12 {
            return Err(ProgramError::NotEnoughAccountKeys);
        }

        // Derive the PDA for the mint seed
        let (pda, bump) = Pubkey::find_program_address(&[MINT_SIGNER_SEED], &ID);

        // Verify the mint_seed account is the PDA we expect
        if &pda != accounts[2].key {
            return Err(ProgramError::InvalidSeeds);
        }

        // Build the params
        let params = CreateCMintParams {
            decimals: data.decimals,
            address_merkle_tree_root_index: data.address_merkle_tree_root_index,
            mint_authority: data.mint_authority,
            proof: data.proof,
            compression_address: data.compression_address,
            mint: data.mint,
            freeze_authority: data.freeze_authority,
            extensions: data.extensions,
        };

        // Build system accounts struct
        let system_accounts = SystemAccountInfos {
            light_system_program: accounts[1].clone(),
            cpi_authority_pda: accounts[5].clone(),
            registered_program_pda: accounts[6].clone(),
            account_compression_authority: accounts[7].clone(),
            account_compression_program: accounts[8].clone(),
            system_program: accounts[9].clone(),
        };

        // Build the account infos struct
        // In this case, payer == authority (accounts[3])
        let account_infos = CreateCMintCpi {
            mint_seed: accounts[2].clone(),
            authority: accounts[3].clone(),
            payer: accounts[3].clone(),
            address_tree: accounts[11].clone(),
            output_queue: accounts[10].clone(),
            system_accounts,
            cpi_context: None,
            cpi_context_account: None,
            params,
        };

        // Invoke with PDA signing
        let signer_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[bump]];
        account_infos.invoke_signed(&[signer_seeds])?;

        Ok(())
    }

    /// Handler for creating a compressed mint with PDA mint seed AND PDA authority (invoke_signed)
    ///
    /// Uses the SDK's CreateCMintCpi with separate authority and payer accounts.
    /// Both mint_seed and authority are PDAs signed by this program.
    ///
    /// Account order:
    /// - accounts[0]: compressed_token_program (for CPI)
    /// - accounts[1]: light_system_program
    /// - accounts[2]: mint_seed (PDA from MINT_SIGNER_SEED, not signer - program signs)
    /// - accounts[3]: authority (PDA from MINT_AUTHORITY_SEED, not signer - program signs)
    /// - accounts[4]: fee_payer (signer)
    /// - accounts[5]: cpi_authority_pda
    /// - accounts[6]: registered_program_pda
    /// - accounts[7]: account_compression_authority
    /// - accounts[8]: account_compression_program
    /// - accounts[9]: system_program
    /// - accounts[10]: output_queue
    /// - accounts[11]: address_tree
    /// - accounts[12] (optional): cpi_context_account
    pub fn process_create_cmint_with_pda_authority(
        accounts: &[AccountInfo],
        data: CreateCmintData,
    ) -> Result<(), ProgramError> {
        use crate::mint_to::MINT_AUTHORITY_SEED;

        if accounts.len() < 12 {
            return Err(ProgramError::NotEnoughAccountKeys);
        }

        // Derive the PDA for the mint seed
        let (mint_seed_pda, mint_seed_bump) =
            Pubkey::find_program_address(&[MINT_SIGNER_SEED], &ID);

        // Derive the PDA for the authority
        let (authority_pda, authority_bump) = Pubkey::find_program_address(&[MINT_AUTHORITY_SEED], &ID);

        // Verify the mint_seed account is the PDA we expect
        if &mint_seed_pda != accounts[2].key {
            return Err(ProgramError::InvalidSeeds);
        }

        // Verify the authority account is the PDA we expect
        if &authority_pda != accounts[3].key {
            return Err(ProgramError::InvalidSeeds);
        }

        // Build the params - authority is the PDA
        let params = CreateCMintParams {
            decimals: data.decimals,
            address_merkle_tree_root_index: data.address_merkle_tree_root_index,
            mint_authority: authority_pda, // Use the derived PDA as authority
            proof: data.proof,
            compression_address: data.compression_address,
            mint: data.mint,
            freeze_authority: data.freeze_authority,
            extensions: data.extensions,
        };

        // Build system accounts struct
        let system_accounts = SystemAccountInfos {
            light_system_program: accounts[1].clone(),
            cpi_authority_pda: accounts[5].clone(),
            registered_program_pda: accounts[6].clone(),
            account_compression_authority: accounts[7].clone(),
            account_compression_program: accounts[8].clone(),
            system_program: accounts[9].clone(),
        };

        // Build the account infos struct using SDK
        let account_infos = CreateCMintCpi {
            mint_seed: accounts[2].clone(),
            authority: accounts[3].clone(),
            payer: accounts[4].clone(),
            address_tree: accounts[11].clone(),
            output_queue: accounts[10].clone(),
            system_accounts,
            cpi_context: None,
            cpi_context_account: None,
            params,
        };

        // Invoke with both PDAs signing
        let mint_seed_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[mint_seed_bump]];
        let authority_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[authority_bump]];
        account_infos.invoke_signed(&[mint_seed_seeds, authority_seeds])?;

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

# Next Steps

<Card title="Learn how to mint tokens to light-token accounts." icon="chevron-right" color="#0066ff" href="/light-token/cookbook/mint-to" horizontal />
