Documentation
FriendlyMinter docs
Build against the live Solana devnet. Connect a wallet, sign a request, and the deployer creates the on-chain Merkle tree for you.
Introduction
FriendlyMinter is a web app for minting compressed NFTs on Solana. It wraps the Bubblegum standard in a UI so you don't have to manage Merkle trees by hand.
The current deployment is a fully clickable frontend. All data flows through lib/mock-api.ts, which simulates network latency and a couple of failure modes. The real on-chain adapters sit behind the same interface, so swapping the data layer is a one-flag change.
Quick start
Run it locally in three commands:
git clone https://github.com/404Piyush/FriendlyMinter.git
cd FriendlyMinter
npm install && npm run devOpen http://localhost:3000. Connect Phantom (or any wallet) and switch it to Devnet. You can mint to your own wallet or just play with the UI in mock mode.
Environment variables
All vars are public (browser-readable). Copy .env.example to .env.local:
# .env.local
NEXT_PUBLIC_SOLANA_NETWORK=devnet
NEXT_PUBLIC_SOLANA_RPC_URL=
NEXT_PUBLIC_USE_MOCK_API=trueCore concepts
Compressed NFTs (cNFTs)
A cNFT is a 32-byte hash stored as a leaf inside a Merkle tree on-chain. The tree root is published in a single account. The actual metadata and image live off-chain in a ledger maintained by Solana validators.
Cost per mint is roughly an order of magnitude lower than a regular NFT, because you're not paying for a new account per mint. You are paying for tree rent up front.
Bubblegum
Bubblegum is the Metaplex program that defines the cNFT standard. FriendlyMinter uses it for tree creation, minting, and transfers. Read the official spec for protocol-level details.
Merkle tree config
Each collection is backed by one concurrentMerkle tree. The tree's parameters set its capacity and write throughput.
| Preset | Depth | Capacity | Use case |
|---|---|---|---|
| Tiny | 3 | ~8 | Test drops |
| Small | 10 | ~1K | Small collections |
| Medium | 14 | ~16K | PFP projects |
| Large | 17 | ~131K | Generative art |
| XL | 20 | ~1M | Event tickets, POAPs |
maxBufferSize sets how many concurrent writes the tree can absorb. Higher buffer = more parallel throughput but more rent. canopyDepth stores the upper levels of the tree in the transaction itself, which dramatically reduces the size of subsequent mint proofs.
CSV format
For bulk mints, drop a CSV with the schema below. The first column is required; everything else is optional. Column names match the standard Metaplex metadata spec.
name,description,image,attributes,external_url,animation_url
"#1 Welcome Mat","First in the series",https://example.com/1.png,"bg:purple;rarity:common",,
"#2 Welcome Mat","Second in the series",https://example.com/2.png,"bg:blue;rarity:rare",https://example.com,https://example.com/2.mp4
"#3 Welcome Mat","Third in the series",ipfs://QmXxx...,"bg:green;rarity:epic",,Attributes: use trait:value pairs separated by ;. For example bg:purple;rarity:epic produces two trait rows in the on-chain metadata.
Image URLs: FriendlyMinter auto-detects ipfs://, ar://, and https:// schemes. Pinata, Arweave, and most CDNs work out of the box.
API reference
The public API lives in two modules. Both are accessible from any client component:
lib/solana.ts
Connection management, network selection, and explorer URL helpers.
import {
getConnection,
getCurrentNetwork,
getExplorerUrl,
testSolanaConnection,
} from '@/lib/solana';
const conn = getConnection(); // singleton Connection for the current network
const network = getCurrentNetwork(); // 'devnet' | 'testnet' | 'mainnet-beta'
const ok = await testSolanaConnection();
const url = getExplorerUrl(signature); // explorer.solana.com/tx/<sig>?cluster=devnetlib/mock-api.ts
Mock data layer. Each method returns a promise that resolves after a configurable delay (NEXT_PUBLIC_MOCK_DELAY, default 1000ms).
import { mockAPI } from '@/lib/mock-api';
// Collections
const collections = await mockAPI.getCollections(userId);
const collection = await mockAPI.getCollection(id);
const created = await mockAPI.createCollection({ name, symbol, ... });
// Mint jobs
const job = await mockAPI.startMintJob(jobId);
await mockAPI.pauseMintJob(jobId);
await mockAPI.resumeMintJob(jobId);
// File uploads
const { url, cid } = await mockAPI.uploadFile(file);components/wallet
Drop-in wallet adapter wired to the configured network.
import { WalletButton } from '@/components/wallet/WalletButton';
import { useWallet } from '@solana/wallet-adapter-react';
export function MyComponent() {
const { connected, publicKey, connect, disconnect, signTransaction } = useWallet();
return <WalletButton />;
}Tech stack
Framework
Next.js 15 (App Router) + React 19
Language
TypeScript
Type checking
strict mode (tsconfig.json)
Styling
Tailwind CSS v4
Solana
@solana/wallet-adapter, @solana/web3.js
State
Zustand (client), TanStack Query (server)
Forms
React Hook Form + Zod
Toasts
Sonner
UI primitives
Custom shadcn-style components in src/components/ui
Ready to mint?
Connect a wallet and create your first collection. Test SOL on devnet.
Start a collection →