noise -> signal

000

press any key to skip

Next.js launchpad for minting SPL Token-2022 tokens with on-chain metadata via Pinata IPFS

Problem

Creating Solana SPL tokens with metadata normally requires CLI tooling and manual handling of metadata pointers, rent-exempt account sizing, and IPFS uploads.

What it does

A single-page Next.js app where a user connects a Solana wallet, picks devnet/mainnet, fills a validated form (name, symbol, supply, decimals, optional image), and the app uploads the image plus metadata JSON to Pinata, then builds and sends a single Token-2022 transaction that creates the mint, initializes metadata pointer and metadata, creates a token account, and mints the initial supply. Created tokens are listed in a grid fetched from the owner's parsed token accounts with explorer links and copyable mint addresses (components/tokenGrid.tsx).

Architecture

[ui] Next.js App Router client (wallet adapter, token form, grid) (client)
[getUri] /api/getUri route handler (api)
[pinata] Pinata SDK (image + metadata JSON upload) (external)
[alchemy] Alchemy Solana RPC (devnet/mainnet) (external)
[wallet] Solana wallet (signs transaction client-side) (external)
[spl] Solana Token-2022 program (mint + metadata on-chain) (external)

ui ──▶ getUri · FormData with image + metadata JSON
getUri ──▶ pinata · upload file, then JSON; returns gateway URI
ui ──▶ wallet · partialSign mint/token account keypairs, sendTransaction
ui ──▶ spl · createAccount, metadata pointer, initialize mint/metadata, mintTo
ui ──▶ alchemy · Connection RPC calls (rent, blockhash, parsed token accounts)
tokenGrid ──▶ pinata · fetch tokenMetadata.uri for image

Simplified — box-and-arrow sketch of the repo structure, not a system diagram.

Decisions

  1. 01

    Token-2022 with on-chain metadata embedded in the mint

    Uses MetadataPointer extension and createInitializeInstruction so metadata lives at the mint itself, then packs the metadata to compute exact rent-exempt lamports (components/tokenDialog.tsx:84-128).

    Evidence: components/tokenDialog.tsx

  2. 02

    Server-side Pinata uploads behind a single API route

    PINATA_JWT is read only in utils/config.ts marked "server only", keeping the JWT out of the browser bundle while the client posts multipart FormData (app/api/getUri/route.ts, utils/config.ts).

    Evidence: utils/config.ts

  3. 03

    Atomic single transaction for the whole mint flow

    Mint account creation, metadata pointer, mint init, metadata init, token account creation, and mintTo are batched into one Transaction with client-side partialSign of the generated keypairs (components/tokenDialog.tsx:158-173).

    Evidence: components/tokenDialog.tsx

  4. 04

    Zod-validated form with coerced numeric inputs

    TokenCreationSchema bounds tokenName (max 32), symbol (max 10), decimals 0-9 and initialSupply up to 10M, resolving client-side via react-hook-form (lib/zod.ts).

    Evidence: lib/zod.ts

  5. 05

    Alchemy endpoints via NEXT_PUBLIC env keys

    Devnet/mainnet RPC URLs are built inline from NEXT_PUBLIC_ALCHEMY_API_KEY with a runtime network switch rather than a fixed cluster (app/page.tsx:27-33).

    Evidence: app/page.tsx

The hard parts

  • Correct Token-2022 space/rent calculation combining metadata extension, pointer extension and packed metadata length (components/tokenDialog.tsx:84-101)
  • Coordinating wallet signing of two ephemeral keypairs (mint, token account) with the user's wallet in one transaction (components/tokenDialog.tsx:171-173)
  • Enumerating owned Token-2022 accounts and fetching off-chain metadata URIs per token to render the grid (components/tokenGrid.tsx:33-55)

Known limitations

  • No tests, no CI, no Dockerfile, no license file in the repo
  • No .env.example; required env vars (NEXT_PUBLIC_ALCHEMY_API_KEY, PINATA_JWT, NEXT_PUBLIC_GATEWAY_URL) are only discoverable from code (app/page.tsx, utils/config.ts)
  • Gross error handling: every mint failure collapses to one generic toast, and upload failures just toast 'unable to fetch uri' (components/tokenDialog.tsx:70-72,178-180)
  • Token grid has no loading/empty states and races setState per account in an unawaited map; description column is never populated from the fetched URI (components/tokenGrid.tsx:35-55)
  • Initial supply entered as a raw number is minted without decimal scaling, so UI supply can mismatch expectations for decimals>0 (components/tokenDialog.tsx:149-156)
  • No deployment evidence: no homepage/vercel URL in README or manifest; package name is still 'my-app' (package.json)

Command palette

Jump to a section, project or link