This tutorial shows how to mint NFTs using Metaplex with LazorKit's gasless infrastructure.
| Type | Gas Fees | Use Case |
|---|---|---|
| Regular NFT | Paymaster covers gas | High-value collectibles, 1/1s |
| Compressed NFT | Fully gasless | Mass distribution, gaming, loyalty programs |
A minting interface where users create standard Metaplex NFTs with gas fees covered by LazorKit.
Metaplex Umi requires a signer interface to build instructions, but LazorKit handles actual signing via passkey. We create a "dummy signer" that satisfies Umi's interface (see Cookbook Patterns - Metaplex Integration for the full explanation):
import { publicKey as umiPublicKey, Signer } from '@metaplex-foundation/umi';
function createDummySigner(walletAddress: string): Signer {
return {
publicKey: umiPublicKey(walletAddress),
signMessage: async () => new Uint8Array(64),
signTransaction: async (tx) => tx,
signAllTransactions: async (txs) => txs,
};
}import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import { createMetadataAccountV3, createMasterEditionV3 } from '@metaplex-foundation/mpl-token-metadata';
import { toWeb3JsInstruction } from '@metaplex-foundation/umi-web3js-adapters';
const buildMetaplexInstructions = (walletAddress: string, mintAddress: string) => {
const umi = createUmi(RPC_URL).use(mplTokenMetadata());
const dummySigner = createDummySigner(walletAddress);
umi.use(signerIdentity(dummySigner));
// Build metadata instruction
const metadataBuilder = createMetadataAccountV3(umi, {
mint: umiPublicKey(mintAddress),
mintAuthority: dummySigner,
payer: dummySigner,
data: {
name: 'My NFT',
symbol: 'LKCB',
uri: metadataUri,
sellerFeeBasisPoints: 0,
creators: [{ address: umiPublicKey(walletAddress), verified: false, share: 100 }],
},
});
// Build master edition instruction
const masterEditionBuilder = createMasterEditionV3(umi, {
mint: umiPublicKey(mintAddress),
updateAuthority: dummySigner,
mintAuthority: dummySigner,
payer: dummySigner,
});
// Convert to Web3.js format
return [
...metadataBuilder.getInstructions().map(toWeb3JsInstruction),
...masterEditionBuilder.getInstructions().map(toWeb3JsInstruction),
];
};LazorKit smart wallets use createAccountWithSeed for new accounts:
const seed = `nft-${Date.now()}`;
const mintAddress = await PublicKey.createWithSeed(
new PublicKey(wallet.smartWallet),
seed,
TOKEN_PROGRAM_ID
);
const createMintIx = SystemProgram.createAccountWithSeed({
fromPubkey: new PublicKey(wallet.smartWallet),
newAccountPubkey: mintAddress,
basePubkey: new PublicKey(wallet.smartWallet),
seed,
lamports: rentExemptBalance,
space: 82,
programId: TOKEN_PROGRAM_ID,
});import { processInstructionsForLazorKit } from '@/lib/lazorkit-utils';
const handleMint = async () => {
// Build all instructions
const instructions = [
createMintIx,
initializeMintIx,
createAtaIx,
mintToIx,
...metaplexInstructions,
];
// Process for LazorKit
const processed = processInstructionsForLazorKit(instructions, wallet.smartWallet);
// Send gasless
const signature = await signAndSendTransaction({
instructions: processed,
transactionOptions: { computeUnitLimit: 400_000 }
});
};Compressed NFTs are fully gasless - no rent costs since they don't create new accounts.
import { mintV1 } from '@metaplex-foundation/mpl-bubblegum';
const buildCNftInstruction = (walletAddress: string, merkleTree: string) => {
const umi = createUmi(RPC_URL).use(mplBubblegum());
const dummySigner = createDummySigner(walletAddress);
umi.use(signerIdentity(dummySigner));
const mintBuilder = mintV1(umi, {
leafOwner: umiPublicKey(walletAddress),
merkleTree: umiPublicKey(merkleTree),
metadata: {
name: 'My cNFT',
symbol: 'cLKCB',
uri: metadataUri,
sellerFeeBasisPoints: 0,
collection: none(),
creators: [{ address: umiPublicKey(walletAddress), verified: false, share: 100 }],
},
});
return mintBuilder.getInstructions().map(toWeb3JsInstruction);
};const handleCNftMint = async () => {
const instructions = buildCNftInstruction(wallet.smartWallet, MERKLE_TREE);
const signature = await signAndSendTransaction({
instructions,
transactionOptions: { computeUnitLimit: 400_000 }
});
// Extract Asset ID from transaction logs
const assetId = await extractCNftAssetId(signature);
console.log('cNFT minted! Asset ID:', assetId);
};async function extractCNftAssetId(signature: string): Promise<string> {
const tx = await connection.getTransaction(signature, {
commitment: 'confirmed',
maxSupportedTransactionVersion: 0,
});
for (const log of tx?.meta?.logMessages || []) {
const match = log.match(/Leaf asset ID: ([1-9A-HJ-NP-Za-km-z]{32,44})/);
if (match) return match[1];
}
return 'Unknown';
}Use DAS-compatible explorers:
https://orbmarkets.io/address/{ASSET_ID}?network=devnet
Or query via DAS API:
const response = await fetch('https://devnet.helius-rpc.com/?api-key=YOUR_KEY', {
method: 'POST',
body: JSON.stringify({
jsonrpc: '2.0',
method: 'getAsset',
params: { id: assetId }
})
});This cookbook provides a demo tree on Devnet:
Address: HiTxt5DJMYSpwZ7i3Kx5qzYsuAfEWMZMnyGCNokC7Y2u
Capacity: 16,384 cNFTs
- Example 06 README - Regular NFT implementation details
- Example 07 README - cNFT implementation details
- Example 06 Source Code
- Example 07 Source Code
- Live Demo - Regular NFT
- Live Demo - cNFT
- Metaplex Token Metadata Docs
- Metaplex Bubblegum Docs