NFT Infrastructure Integration - Implementation Plan
Historical Document
This is the original detailed migration plan created in November 2025 for integrating external NFT packages into the monorepo.
Current Focus: See Arbitrum Development Plan for active development timeline.
This document remains valuable as a reference for:
- Migration approach and patterns
- Integration checklist
- Testing strategy
Date Created: 2025-11-09 Status: Reference document (see Arbitrum Development Plan for current work) Original Timeline: 3-4 weeks (15-20 working days) Team Size: 1-2 developers
Table of Contents
- Overview
- Phase 1: Validation & Setup
- Phase 2: Contract Package Migration
- Phase 3: Indexer App Migration
- Phase 4: Launchpad App Creation
- Phase 5: Studio Integration
- Phase 6: Testing & Documentation
- Rollout Strategy
- Monitoring & Maintenance
Overview
Timeline Summary
Week 1: Validation + Contract Migration
Week 2: Indexer + Launchpad Foundation
Week 3: Launchpad Features + Studio Integration
Week 4: Testing + Documentation + LaunchMilestones
| Milestone | Target Date | Deliverable |
|---|---|---|
| M1: Validation Complete | Day 3 | Contracts compile, tests pass |
| M2: Contracts in Monorepo | Day 7 | Deployed to testnet |
| M3: Indexer Running | Day 10 | API responding, events indexing |
| M4: Launchpad MVP | Day 15 | Can create collections |
| M5: Studio Integration | Day 17 | Button working |
| M6: Testing Complete | Day 19 | E2E tests passing |
| M7: Launch Ready | Day 20 | Documentation complete |
Phase 1: Validation & Setup
Duration: 3 days (Days 1-3) Goal: Validate existing code and set up infrastructure
Day 1: Contract Validation
Morning: Move Contracts from Backup
cd /Users/the_dusky/code/emprops/nft_investigation/emprops-hardhat
# 1. Move contracts from backup
cp -r backup/hardhat/contracts/OwnerTokenContract.sol contracts/
cp -r backup/hardhat/contracts/NFTContractFactoryContract.sol contracts/
cp -r backup/hardhat/contracts/SimpleAppContract.sol contracts/
cp -r backup/hardhat/contracts/SimpleAppInitializerContract.sol contracts/
cp -r backup/hardhat/contracts/interfaces contracts/
cp -r backup/hardhat/contracts/proxy contracts/
# 2. Install dependencies
pnpm install
# 3. Compile contracts
pnpm compileExpected Output:
Compiled 12 Solidity files successfully✅ Checkpoint: All contracts compile without errors
Afternoon: Run Tests
# Run full test suite
pnpm test
# If tests fail, review and fix
# Document any issues foundExpected Output:
OwnerTokenContract
✓ should initialize correctly
✓ should mint tokens via factory
✓ should transfer ownership
...
NFTContractFactoryContract
✓ should register implementations
✓ should create apps
✓ should predict addresses
...
SimpleAppContract
✓ should initialize minting params
✓ should mint tokens
✓ should respect max supply
...✅ Checkpoint: All tests pass or issues documented
Deliverables:
- [ ] Contracts moved to main folder
- [ ] Compilation successful
- [ ] Test results documented
- [ ] Any issues logged
Day 2: Indexer Validation & Database Setup
Morning: Test Ponder
cd /Users/the_dusky/code/emprops/nft_investigation/emprops-ponder
# 1. Install dependencies
pnpm install
# 2. Start dev server
pnpm devExpected Output:
✓ Server listening on http://localhost:42069
✓ Indexed block 1234567Test API Endpoints:
# Test API responds
curl http://localhost:42069/api/apps
# Expected: {"apps": []} or error about no contracts✅ Checkpoint: Ponder starts and API responds
Afternoon: Set Up Monorepo Database
cd /Users/the_dusky/code/emerge/emerge-turbo
# 1. Connect to PostgreSQL
psql $DATABASE_URLSQL Commands:
-- Create schemas for NFT packages
CREATE SCHEMA IF NOT EXISTS nft_contracts;
CREATE SCHEMA IF NOT EXISTS nft_indexer;
-- Grant permissions
GRANT ALL ON SCHEMA nft_contracts TO your_user;
GRANT ALL ON SCHEMA nft_indexer TO your_user;
-- Create contract deployments table
CREATE TABLE nft_contracts.contract_deployments (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
address VARCHAR(42) NOT NULL,
network VARCHAR(50) NOT NULL,
abi JSONB NOT NULL,
contract_type VARCHAR(100),
related_contract VARCHAR(255),
version VARCHAR(50),
deployed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deployer_address VARCHAR(42),
transaction_hash VARCHAR(66),
block_number BIGINT,
UNIQUE(name, network)
);✅ Checkpoint: Database schemas created
Deliverables:
- [ ] Ponder runs successfully
- [ ] Database schemas created
- [ ] Permissions set correctly
- [ ] Contract deployments table exists
Day 3: Environment Configuration
Morning: Create Environment Files
cd /Users/the_dusky/code/emerge/emerge-turbo
# Create NFT-specific environment file
touch .env.nftEdit .env.nft:
# Database
DATABASE_URL="postgresql://user:pass@localhost:5432/emerge_turbo"
NFT_CONTRACTS_DATABASE_URL="postgresql://user:pass@localhost:5432/emerge_turbo?schema=nft_contracts"
NFT_INDEXER_DATABASE_URL="postgresql://user:pass@localhost:5432/emerge_turbo?schema=nft_indexer"
# RPC URLs
PONDER_RPC_URL_84532="https://base-sepolia.g.alchemy.com/v2/YOUR_KEY"
ALCHEMY_API_KEY="YOUR_KEY"
# Deployment
DEPLOYER_PRIVATE_KEY="0x..." # Generate test wallet
TARGET_NETWORKS="baseSepolia"
DEFAULT_NETWORK="baseSepolia"
# API URLs
NFT_INDEXER_URL="http://localhost:3338"
NEXT_PUBLIC_NFT_INDEXER_URL="http://localhost:3338"
# Ports
PONDER_PORT="3338"
# WalletConnect
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="your_project_id"
# Chain
NEXT_PUBLIC_CHAIN_ID="84532"Afternoon: Get External Services
Alchemy Account
- Go to https://www.alchemy.com/
- Create account
- Create app for Base Sepolia
- Copy API key
WalletConnect Project
- Go to https://cloud.walletconnect.com/
- Create account
- Create project
- Copy Project ID
Test Wallet
bash# Generate test wallet cd emprops-hardhat pnpm account:generate # Save private key to .env.nftGet Testnet ETH
- Go to https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet
- Enter wallet address
- Claim testnet ETH
✅ Checkpoint: All services configured
Deliverables:
- [ ]
.env.nftfile created - [ ] Alchemy API key obtained
- [ ] WalletConnect Project ID obtained
- [ ] Test wallet funded with Sepolia ETH
- [ ] All credentials validated
Phase 1 Completion Checklist
- [ ] Contracts compile successfully
- [ ] Tests pass (or issues documented)
- [ ] Ponder runs successfully
- [ ] Database schemas created
- [ ] Environment configured
- [ ] External services set up
- [ ] Test wallet funded
Go/No-Go Decision: If all items checked, proceed to Phase 2
Phase 2: Contract Package Migration
Duration: 4 days (Days 4-7) Goal: Move contracts to monorepo and deploy to testnet
Day 4: Create Package Structure
Morning: Set Up Package
cd /Users/the_dusky/code/emerge/emerge-turbo
# Create package directory
mkdir -p packages/nft-contracts
# Copy from emprops-hardhat
cp -r /Users/the_dusky/code/emprops/nft_investigation/emprops-hardhat/* packages/nft-contracts/
# Navigate to package
cd packages/nft-contractsUpdate package.json:
{
"name": "nft-contracts",
"version": "0.1.0",
"private": true,
"scripts": {
"compile": "hardhat compile",
"test": "hardhat test",
"deploy": "hardhat deploy",
"deploy:baseSepolia": "hardhat deploy --network baseSepolia",
"db:init": "hardhat run scripts/db/init.ts",
"db:store-deployments": "hardhat run scripts/db/storeDeployments.ts"
},
"dependencies": {
"@openzeppelin/contracts": "5.0.2",
"@openzeppelin/contracts-upgradeable": "5.0.2",
"erc721a": "4.3.0",
"erc721a-upgradeable": "4.2.3",
"hardhat": "2.22.19",
"ethers": "6.6.0",
"pg": "8.11.3",
"viem": "^2.23.5"
},
"devDependencies": {
"@nomicfoundation/hardhat-ethers": "3.0.8",
"@nomicfoundation/hardhat-verify": "2.0.10",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"hardhat-deploy": "0.12.0",
"hardhat-gas-reporter": "2.2.1",
"typescript": "5.7.3"
}
}Afternoon: Update Configuration
Edit hardhat.config.ts:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-ethers";
import "hardhat-deploy";
// Load environment from monorepo root
import * as dotenv from "dotenv";
dotenv.config({ path: "../../.env.nft" });
const config: HardhatUserConfig = {
solidity: {
version: "0.8.24",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
hardhat: {
chainId: 1337,
},
baseSepolia: {
url: process.env.PONDER_RPC_URL_84532,
chainId: 84532,
accounts: process.env.DEPLOYER_PRIVATE_KEY ? [process.env.DEPLOYER_PRIVATE_KEY] : [],
},
},
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts",
},
};
export default config;Update Database Connection:Edit scripts/db/init.ts:
import { Pool } from 'pg';
import * as dotenv from 'dotenv';
dotenv.config({ path: '../../../../.env.nft' });
const pool = new Pool({
connectionString: process.env.NFT_CONTRACTS_DATABASE_URL,
});
// Rest of script...✅ Checkpoint: Package structure created
Deliverables:
- [ ] Package created in
packages/nft-contracts - [ ] Configuration updated for monorepo
- [ ] Database connection points to new schema
Day 5: Test in Monorepo
Morning: Install and Compile
cd /Users/the_dusky/code/emerge/emerge-turbo
# Install dependencies for all packages
pnpm install
# Compile contracts
pnpm --filter nft-contracts compileExpected Output:
Compiled 12 Solidity files successfullyAfternoon: Run Tests
# Run tests
pnpm --filter nft-contracts test
# Run with gas reporting
REPORT_GAS=true pnpm --filter nft-contracts test✅ Checkpoint: Everything works in monorepo context
Deliverables:
- [ ] Dependencies installed
- [ ] Contracts compile
- [ ] Tests pass
- [ ] Gas reports generated
Day 6: Deploy to Base Sepolia
Morning: Initialize Database
pnpm --filter nft-contracts db:initExpected Output:
✓ Database schema initialized
✓ contract_deployments table createdDeploy Contracts:
# Deploy all contracts
pnpm --filter nft-contracts deploy:baseSepoliaExpected Output:
📡 Starting base OwnerToken deployment...
🔑 Deployer: 0x1234...
📦 OwnerToken implementation deployed to: 0xABCD...
📦 OwnerToken proxy deployed to: 0xEFGH...
👔 Auto-created OwnerToken ProxyAdmin Contract at: 0xIJKL...
✅ OwnerToken deployment complete!
📡 Starting NFTContractFactory deployment...
... (similar output)
✅ All contracts deployed successfully!Afternoon: Store Deployments
# Store deployment info in database
pnpm --filter nft-contracts db:store-deploymentsVerify in Database:
-- Connect to database
psql $DATABASE_URL
-- Check deployments
SELECT name, address, network FROM nft_contracts.contract_deployments;
-- Expected output:
-- OwnerTokenImplContract | 0xABCD... | baseSepolia
-- OwnerTokenProxyContract | 0xEFGH... | baseSepolia
-- NFTContractFactoryImplContract | 0x1234... | baseSepolia
-- NFTContractFactoryProxyContract | 0x5678... | baseSepolia
-- SimpleAppImplContract | 0x9ABC... | baseSepolia✅ Checkpoint: Contracts deployed to testnet
Deliverables:
- [ ] Database initialized
- [ ] Contracts deployed to Base Sepolia
- [ ] Deployment info stored in database
- [ ] Contract addresses documented
Day 7: Verify and Test On-Chain
Morning: Verify Contracts on Basescan
# Verify contracts
pnpm --filter nft-contracts verify --network baseSepoliaCheck on Basescan:
- Go to https://sepolia.basescan.org
- Search for contract addresses
- Verify contracts are verified and readable
Afternoon: Test Contract Interactions
Create test script: packages/nft-contracts/scripts/test-deployment.ts
import { ethers } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Testing with account:", deployer.address);
// Get deployed contracts
const ownerTokenAddress = "0x..."; // From database
const factoryAddress = "0x..."; // From database
const simpleAppImplAddress = "0x..."; // From database
// Get contract instances
const ownerToken = await ethers.getContractAt("OwnerTokenContract", ownerTokenAddress);
const factory = await ethers.getContractAt("NFTContractFactoryContract", factoryAddress);
// Test 1: Set factory on OwnerToken
console.log("Setting factory...");
const tx1 = await ownerToken.setFactory(factoryAddress);
await tx1.wait();
console.log("✓ Factory set");
// Test 2: Initialize factory with owner token
console.log("Initializing factory with owner token...");
const tx2 = await factory.initializeWithOwnerToken(ownerTokenAddress);
await tx2.wait();
console.log("✓ Factory initialized");
// Test 3: Register SimpleApp implementation
console.log("Registering SimpleApp implementation...");
const initializerAddress = "0x..."; // Deploy SimpleAppInitializerContract first
const tx3 = await factory.registerImplementation(simpleAppImplAddress, initializerAddress);
await tx3.wait();
console.log("✓ Implementation registered");
console.log("\n✅ All tests passed!");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});Run test:
pnpm --filter nft-contracts hardhat run scripts/test-deployment.ts --network baseSepolia✅ Checkpoint: Contracts working on testnet
Deliverables:
- [ ] Contracts verified on Basescan
- [ ] Factory configured
- [ ] Implementation registered
- [ ] On-chain tests passing
Phase 2 Completion Checklist
- [ ] Package in monorepo structure
- [ ] Contracts compile in monorepo
- [ ] Tests pass in monorepo
- [ ] Deployed to Base Sepolia
- [ ] Deployment info in database
- [ ] Contracts verified on Basescan
- [ ] On-chain configuration complete
Go/No-Go Decision: If all items checked, proceed to Phase 3
Phase 3: Indexer App Migration
Duration: 3 days (Days 8-10) Goal: Get Ponder indexing contracts in monorepo
Day 8: Create Indexer App
Morning: Set Up App Structure
cd /Users/the_dusky/code/emerge/emerge-turbo
# Create app directory
mkdir -p apps/nft-indexer
# Copy from emprops-ponder
cp -r /Users/the_dusky/code/emprops/nft_investigation/emprops-ponder/* apps/nft-indexer/
# Navigate to app
cd apps/nft-indexerUpdate package.json:
{
"name": "nft-indexer",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "ponder dev",
"start": "ponder start",
"codegen": "ponder codegen"
},
"dependencies": {
"@types/pg": "^8.11.11",
"drizzle-orm": "^0.39.3",
"hono": "^4.5.0",
"pg": "^8.13.3",
"ponder": "^0.9.17",
"socket.io": "^4.7.4",
"viem": "^2.21.3"
}
}Afternoon: Update Configuration
Edit ponder.config.ts:
import { createConfig } from "@ponder/core";
import { http } from "viem";
import { getContractsFromRegistry } from "./src/utils/db";
import * as dotenv from 'dotenv';
dotenv.config({ path: '../../.env.nft' });
// Load contracts from database
const contracts = await getContractsFromRegistry();
export default createConfig({
networks: {
baseSepolia: {
chainId: 84532,
transport: http(process.env.PONDER_RPC_URL_84532)
}
},
contracts: {
OwnerToken: {
abi: contracts.OwnerTokenProxyContract.abi,
address: contracts.OwnerTokenProxyContract.address as `0x${string}`,
network: "baseSepolia",
startBlock: contracts.OwnerTokenProxyContract.startBlock || 0
},
NFTContractFactory: {
abi: contracts.NFTContractFactoryProxyContract.abi,
address: contracts.NFTContractFactoryProxyContract.address as `0x${string}`,
network: "baseSepolia",
startBlock: contracts.NFTContractFactoryProxyContract.startBlock || 0
}
}
});Update Database Connection:Edit src/utils/db.ts:
import pg from 'pg';
import * as dotenv from 'dotenv';
dotenv.config({ path: '../../../../.env.nft' });
const pool = new pg.Pool({
connectionString: process.env.NFT_CONTRACTS_DATABASE_URL
});
export async function getContractsFromRegistry() {
const result = await pool.query(`
SELECT name, address, abi, block_number as "startBlock"
FROM contract_deployments
WHERE network = $1
`, ['baseSepolia']);
const contracts: Record<string, any> = {};
for (const row of result.rows) {
contracts[row.name] = {
address: row.address,
abi: row.abi,
startBlock: row.startBlock || 0
};
}
return contracts;
}Update Ponder Schema Connection:Edit ponder.schema.ts - add note about schema:
// Note: Ponder will use NFT_INDEXER_DATABASE_URL which points to nft_indexer schema
import { onchainTable, relations, primaryKey} from "ponder"
// Rest of schema unchanged...✅ Checkpoint: Indexer configured for monorepo
Deliverables:
- [ ] App created in
apps/nft-indexer - [ ] Configuration updated
- [ ] Database connections point to correct schemas
Day 9: Test Indexer Locally
Morning: Install and Start
cd /Users/the_dusky/code/emerge/emerge-turbo
# Install dependencies
pnpm install
# Start indexer
pnpm --filter nft-indexer devExpected Output:
✓ Connected to database (nft_indexer schema)
✓ Loaded contracts from registry
✓ Monitoring OwnerToken at 0xABCD...
✓ Monitoring NFTContractFactory at 0xEFGH...
✓ Server listening on http://localhost:3338
✓ Syncing from block 12345678...Afternoon: Test API and WebSocket
Test API Endpoints:
# Test owner tokens
curl http://localhost:3338/api/owner-tokens
# Test apps
curl http://localhost:3338/api/apps
# Test contract info
curl http://localhost:3338/api/contract-info/NFTContractFactoryProxyContractTest WebSocket:Create test file: apps/nft-indexer/test/test-socket.ts
import { io } from 'socket.io-client';
const socket = io('http://localhost:3338');
socket.on('connect', () => {
console.log('✓ Connected to WebSocket');
});
socket.on('emprops:app:created', (data) => {
console.log('📦 App created:', data);
});
socket.on('emprops:appToken:minted', (data) => {
console.log('🎨 Token minted:', data);
});
console.log('Listening for events...');Run test:
cd apps/nft-indexer
npx tsx test/test-socket.ts✅ Checkpoint: Indexer running and responsive
Deliverables:
- [ ] Indexer starts successfully
- [ ] API endpoints respond
- [ ] WebSocket connects
- [ ] Database tables created by Ponder
Day 10: Create Test Collection On-Chain
Morning: Deploy Test Collection
Create script: packages/nft-contracts/scripts/create-test-collection.ts
import { ethers } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
const factory = await ethers.getContractAt("NFTContractFactoryContract", "0x..."); // From database
console.log("Creating test collection...");
// Encode init data
const initData = ethers.AbiCoder.defaultAbiCoder().encode(
["uint256", "uint256", "uint256", "uint256"],
[
100, // maxSupply
ethers.parseEther("0.001"), // mintPrice (0.001 ETH)
10, // maxPerMint
Math.floor(Date.now() / 1000) + 60 // startDateTime (1 minute from now)
]
);
const appType = ethers.keccak256(ethers.toUtf8Bytes("SIMPLE_APP_V1"));
const tx = await factory.createNFTContract(
appType,
"test-collection-1", // appId
"Test Collection", // name
"TEST", // symbol
initData
);
const receipt = await tx.wait();
console.log("✓ Collection created in tx:", receipt.hash);
// Find AppCreated event
const event = receipt.logs.find(log => {
try {
const parsed = factory.interface.parseLog(log);
return parsed?.name === 'AppCreated';
} catch {
return false;
}
});
if (event) {
const parsed = factory.interface.parseLog(event);
console.log("✓ Collection address:", parsed.args.app);
console.log("✓ Owner token ID:", parsed.args.ownerTokenId);
}
}
main().catch(console.error);Run script:
pnpm --filter nft-contracts hardhat run scripts/create-test-collection.ts --network baseSepoliaAfternoon: Verify Indexing
Check Ponder logs:
✓ Indexed block 12345679
✓ Processed event: AppCreated
✓ Created app record: 0x...
✓ Minted owner token: 0Check API:
# Should now return the created collection
curl http://localhost:3338/api/apps
# Should show the minted owner token
curl http://localhost:3338/api/owner-tokensCheck WebSocket:
- Test socket script should have logged the event
✅ Checkpoint: End-to-end flow working
Deliverables:
- [ ] Test collection created on-chain
- [ ] Ponder indexed the event
- [ ] API returns collection data
- [ ] WebSocket emitted event
Phase 3 Completion Checklist
- [ ] Indexer app in monorepo
- [ ] Configuration updated
- [ ] Indexer running on port 3338
- [ ] API endpoints working
- [ ] WebSocket server working
- [ ] Test collection indexed
- [ ] Events flowing end-to-end
Go/No-Go Decision: If all items checked, proceed to Phase 4
Phase 4: Launchpad App Creation
Duration: 5 days (Days 11-15) Goal: Build NFT launchpad Next.js app
Day 11: Create Next.js App
Morning: Set Up Next.js App
cd /Users/the_dusky/code/emerge/emerge-turbo/apps
# Create Next.js app
npx create-next-app@latest nft-launchpad --typescript --tailwind --app-router --no-src-dir
cd nft-launchpadInstall Web3 Dependencies:
pnpm add wagmi viem@2.x @tanstack/react-query
pnpm add @rainbow-me/rainbowkit
pnpm add socket.io-client zodUpdate package.json:
{
"name": "nft-launchpad",
"version": "0.1.0",
"scripts": {
"dev": "next dev -p 3337",
"build": "next build",
"start": "next start -p 3337"
},
"dependencies": {
"next": "14.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@rainbow-me/rainbowkit": "^2.0.0",
"@tanstack/react-query": "^5.0.0",
"viem": "^2.0.0",
"wagmi": "^2.0.0",
"socket.io-client": "^4.8.1",
"zod": "^3.24.2"
}
}Afternoon: Set Up Providers
Create lib/env.ts:
import { z } from 'zod';
const envSchema = z.object({
NEXT_PUBLIC_NFT_INDEXER_URL: z.string().url(),
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: z.string().min(1),
NEXT_PUBLIC_CHAIN_ID: z.coerce.number().default(84532)
});
export const env = envSchema.parse({
NEXT_PUBLIC_NFT_INDEXER_URL: process.env.NEXT_PUBLIC_NFT_INDEXER_URL,
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID,
NEXT_PUBLIC_CHAIN_ID: process.env.NEXT_PUBLIC_CHAIN_ID
});Create lib/wagmi.ts:
import { getDefaultConfig } from '@rainbow-me/rainbowkit';
import { baseSepolia } from 'wagmi/chains';
import { env } from './env';
export const config = getDefaultConfig({
appName: 'NFT Launchpad',
projectId: env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID,
chains: [baseSepolia],
});Update app/layout.tsx:
import '@rainbow-me/rainbowkit/styles.css';
import { Providers } from './providers';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Create app/providers.tsx:
'use client';
import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { config } from '@/lib/wagmi';
import { useState } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
{children}
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}✅ Checkpoint: Next.js app with Web3 providers
Deliverables:
- [ ] Next.js app created
- [ ] Web3 dependencies installed
- [ ] Providers configured
- [ ] Environment validation set up
Day 12: Fetch Contract ABIs and Build API Client
Morning: Fetch Contract Info
Create lib/api.ts:
import { env } from './env';
const API_URL = env.NEXT_PUBLIC_NFT_INDEXER_URL;
export async function fetchContractInfo(contractName: string) {
const response = await fetch(`${API_URL}/api/contract-info/${contractName}`);
if (!response.ok) throw new Error('Failed to fetch contract info');
return response.json();
}
export async function fetchCollections() {
const response = await fetch(`${API_URL}/api/apps`);
if (!response.ok) throw new Error('Failed to fetch collections');
return response.json();
}
export async function fetchCollection(address: string) {
const response = await fetch(`${API_URL}/api/apps/${address}`);
if (!response.ok) throw new Error('Failed to fetch collection');
return response.json();
}Create types/collection.ts:
export interface Collection {
id: string;
address: string;
type: string;
ownerTokenContract: string;
ownerTokenId: string;
maxSupply: string;
mintPrice: string;
maxPerMint: string;
paused: boolean;
startDateTime: string;
totalMinted: string;
}
export interface CreateCollectionParams {
name: string;
symbol: string;
maxSupply: number;
mintPrice: string; // In ETH
maxPerMint: number;
startInMinutes: number;
}Afternoon: Build Collection Hooks
Create hooks/useCollections.ts:
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
import { io } from 'socket.io-client';
import { fetchCollections } from '@/lib/api';
import { env } from '@/lib/env';
export function useCollections() {
const queryClient = useQueryClient();
const query = useQuery({
queryKey: ['collections'],
queryFn: fetchCollections
});
// Real-time updates
useEffect(() => {
const socket = io(env.NEXT_PUBLIC_NFT_INDEXER_URL);
socket.on('emprops:app:created', () => {
queryClient.invalidateQueries({ queryKey: ['collections'] });
});
return () => socket.disconnect();
}, [queryClient]);
return query;
}✅ Checkpoint: API client working
Deliverables:
- [ ] API client functions created
- [ ] Types defined
- [ ] Collection hooks with real-time updates
Day 13: Build Collection List Page
Morning: Create Collection Card Component
Create components/CollectionCard.tsx:
import { Collection } from '@/types/collection';
import Link from 'next/link';
export function CollectionCard({ collection }: { collection: Collection }) {
const mintPrice = parseFloat(collection.mintPrice) / 1e18; // Convert from wei
return (
<Link href={`/collection/${collection.address}`}>
<div className="border rounded-lg p-6 hover:shadow-lg transition-shadow">
<h3 className="text-xl font-bold mb-2">Collection #{collection.ownerTokenId}</h3>
<div className="space-y-2 text-sm">
<p>Address: {collection.address.slice(0, 10)}...</p>
<p>Max Supply: {collection.maxSupply}</p>
<p>Mint Price: {mintPrice} ETH</p>
<p>Total Minted: {collection.totalMinted || '0'}</p>
{collection.paused && (
<p className="text-red-600 font-semibold">⏸️ Paused</p>
)}
</div>
</div>
</Link>
);
}Afternoon: Create Home Page
Update app/page.tsx:
'use client';
import { useCollections } from '@/hooks/useCollections';
import { CollectionCard } from '@/components/CollectionCard';
import { ConnectButton } from '@rainbow-me/rainbowkit';
import Link from 'next/link';
export default function Home() {
const { data: collections, isLoading, error } = useCollections();
return (
<div className="container mx-auto px-4 py-8">
<div className="flex justify-between items-center mb-8">
<h1 className="text-4xl font-bold">NFT Launchpad</h1>
<div className="flex gap-4">
<Link href="/create">
<button className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700">
Create Collection
</button>
</Link>
<ConnectButton />
</div>
</div>
{isLoading && <p>Loading collections...</p>}
{error && <p className="text-red-600">Error loading collections</p>}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{collections?.map((collection) => (
<CollectionCard key={collection.id} collection={collection} />
))}
</div>
{collections?.length === 0 && (
<p className="text-center text-gray-500 py-12">
No collections yet. Create the first one!
</p>
)}
</div>
);
}✅ Checkpoint: Collection list page working
Deliverables:
- [ ] Collection card component
- [ ] Home page with collection list
- [ ] Real-time updates working
- [ ] Wallet connect button
Day 14: Build Collection Creation Form
Morning: Create Form Component
Create components/CreateCollectionForm.tsx:
'use client';
import { useState } from 'react';
import { useAccount } from 'wagmi';
import { CreateCollectionParams } from '@/types/collection';
interface CreateCollectionFormProps {
onSubmit: (params: CreateCollectionParams) => Promise<void>;
isLoading: boolean;
}
export function CreateCollectionForm({ onSubmit, isLoading }: CreateCollectionFormProps) {
const { isConnected } = useAccount();
const [formData, setFormData] = useState<CreateCollectionParams>({
name: '',
symbol: '',
maxSupply: 100,
mintPrice: '0.001',
maxPerMint: 10,
startInMinutes: 5
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await onSubmit(formData);
};
if (!isConnected) {
return (
<div className="text-center py-12">
<p className="text-gray-600 mb-4">Please connect your wallet to create a collection</p>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="max-w-2xl mx-auto space-y-6">
<div>
<label className="block text-sm font-medium mb-2">Collection Name</label>
<input
type="text"
required
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-4 py-2 border rounded-lg"
placeholder="My NFT Collection"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Symbol</label>
<input
type="text"
required
value={formData.symbol}
onChange={(e) => setFormData({ ...formData, symbol: e.target.value })}
className="w-full px-4 py-2 border rounded-lg"
placeholder="MYNFT"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Max Supply</label>
<input
type="number"
required
min="1"
value={formData.maxSupply}
onChange={(e) => setFormData({ ...formData, maxSupply: parseInt(e.target.value) })}
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Mint Price (ETH)</label>
<input
type="text"
required
value={formData.mintPrice}
onChange={(e) => setFormData({ ...formData, mintPrice: e.target.value })}
className="w-full px-4 py-2 border rounded-lg"
placeholder="0.001"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Max Per Mint</label>
<input
type="number"
required
min="1"
value={formData.maxPerMint}
onChange={(e) => setFormData({ ...formData, maxPerMint: parseInt(e.target.value) })}
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Start In (Minutes)</label>
<input
type="number"
required
min="1"
value={formData.startInMinutes}
onChange={(e) => setFormData({ ...formData, startInMinutes: parseInt(e.target.value) })}
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<button
type="submit"
disabled={isLoading}
className="w-full bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{isLoading ? 'Creating Collection...' : 'Create Collection'}
</button>
</form>
);
}Afternoon: Create Collection Hook
Create hooks/useCreateCollection.ts:
import { useState } from 'react';
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { parseEther, encodeFunctionData } from 'viem';
import { CreateCollectionParams } from '@/types/collection';
import { fetchContractInfo } from '@/lib/api';
export function useCreateCollection() {
const [txHash, setTxHash] = useState<`0x${string}`>();
const { writeContractAsync } = useWriteContract();
const { isLoading: isConfirming } = useWaitForTransactionReceipt({ hash: txHash });
const createCollection = async (params: CreateCollectionParams) => {
// Fetch factory contract info
const factoryInfo = await fetchContractInfo('NFTContractFactoryProxyContract');
// Encode init data
const initData = encodeFunctionData({
abi: [
{
type: 'function',
name: 'initializeMintingParams',
inputs: [
{ name: 'maxSupply', type: 'uint256' },
{ name: 'mintPrice', type: 'uint256' },
{ name: 'maxPerMint', type: 'uint256' },
{ name: 'startDateTime', type: 'uint256' }
]
}
],
functionName: 'initializeMintingParams',
args: [
BigInt(params.maxSupply),
parseEther(params.mintPrice),
BigInt(params.maxPerMint),
BigInt(Math.floor(Date.now() / 1000) + params.startInMinutes * 60)
]
});
// Get app type hash
const appType = '0x...' // keccak256("SIMPLE_APP_V1")
// Create collection
const hash = await writeContractAsync({
address: factoryInfo.address as `0x${string}`,
abi: factoryInfo.abi,
functionName: 'createNFTContract',
args: [
appType,
`collection-${Date.now()}`, // appId
params.name,
params.symbol,
initData
]
});
setTxHash(hash);
return hash;
};
return {
createCollection,
isCreating: !!txHash && isConfirming,
txHash
};
}Create app/create/page.tsx:
'use client';
import { CreateCollectionForm } from '@/components/CreateCollectionForm';
import { useCreateCollection } from '@/hooks/useCreateCollection';
import { useRouter } from 'next/navigation';
export default function CreatePage() {
const router = useRouter();
const { createCollection, isCreating } = useCreateCollection();
const handleCreate = async (params: CreateCollectionParams) => {
try {
const hash = await createCollection(params);
console.log('Collection created:', hash);
router.push('/');
} catch (error) {
console.error('Failed to create collection:', error);
alert('Failed to create collection');
}
};
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-8 text-center">Create NFT Collection</h1>
<CreateCollectionForm onSubmit={handleCreate} isLoading={isCreating} />
</div>
);
}✅ Checkpoint: Can create collections
Deliverables:
- [ ] Collection creation form
- [ ] Create collection hook
- [ ] Form validation
- [ ] Transaction submission working
Day 15: Build Minting Interface
Morning: Create Mint Component
Create hooks/useMintNFT.ts:
import { useState } from 'react';
import { useWriteContract, useWaitForTransactionReceipt, useReadContract } from 'wagmi';
import { parseEther } from 'viem';
import { fetchContractInfo } from '@/lib/api';
export function useMintNFT(collectionAddress: string) {
const [txHash, setTxHash] = useState<`0x${string}`>();
// Fetch contract info
const { data: contractInfo } = useQuery({
queryKey: ['contract-info', 'SimpleAppImplContract'],
queryFn: () => fetchContractInfo('SimpleAppImplContract')
});
// Read mint price
const { data: mintPrice } = useReadContract({
address: collectionAddress as `0x${string}`,
abi: contractInfo?.abi,
functionName: 'mintPrice'
});
// Read paused state
const { data: isPaused } = useReadContract({
address: collectionAddress as `0x${string}`,
abi: contractInfo?.abi,
functionName: 'paused'
});
const { writeContractAsync } = useWriteContract();
const { isLoading: isConfirming } = useWaitForTransactionReceipt({ hash: txHash });
const mint = async (quantity: number) => {
if (isPaused) throw new Error('Minting is paused');
if (!mintPrice) throw new Error('Mint price not loaded');
const hash = await writeContractAsync({
address: collectionAddress as `0x${string}`,
abi: contractInfo.abi,
functionName: 'mint',
args: [BigInt(quantity)],
value: BigInt(mintPrice) * BigInt(quantity)
});
setTxHash(hash);
return hash;
};
return {
mint,
isMinting: !!txHash && isConfirming,
isPaused,
mintPrice,
txHash
};
}Afternoon: Create Collection Detail Page
Create app/collection/[address]/page.tsx:
'use client';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { useMintNFT } from '@/hooks/useMintNFT';
import { useAccount } from 'wagmi';
import { ConnectButton } from '@rainbow-me/rainbowkit';
export default function CollectionPage() {
const { address } = useParams();
const { isConnected } = useAccount();
const [quantity, setQuantity] = useState(1);
const { mint, isMinting, isPaused, mintPrice } = useMintNFT(address as string);
const handleMint = async () => {
try {
const hash = await mint(quantity);
alert(`NFT minted! Transaction: ${hash}`);
} catch (error) {
console.error('Mint failed:', error);
alert('Failed to mint NFT');
}
};
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-8">Collection Details</h1>
<div className="max-w-2xl mx-auto space-y-6">
<div>
<p className="text-sm text-gray-600">Collection Address</p>
<p className="font-mono">{address}</p>
</div>
{mintPrice && (
<div>
<p className="text-sm text-gray-600">Mint Price</p>
<p className="text-2xl font-bold">
{(parseFloat(mintPrice.toString()) / 1e18).toFixed(4)} ETH
</p>
</div>
)}
{isPaused && (
<div className="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded">
⏸️ Minting is currently paused
</div>
)}
{!isConnected ? (
<div className="text-center">
<p className="mb-4">Connect your wallet to mint NFTs</p>
<ConnectButton />
</div>
) : (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2">Quantity</label>
<input
type="number"
min="1"
max="10"
value={quantity}
onChange={(e) => setQuantity(parseInt(e.target.value))}
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<button
onClick={handleMint}
disabled={isMinting || isPaused}
className="w-full bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{isMinting ? 'Minting...' : `Mint ${quantity} NFT${quantity > 1 ? 's' : ''}`}
</button>
</div>
)}
</div>
</div>
);
}✅ Checkpoint: Full launchpad MVP working
Deliverables:
- [ ] Mint hook with price checking
- [ ] Collection detail page
- [ ] Minting interface
- [ ] Transaction confirmation
Phase 4 Completion Checklist
- [ ] Next.js app created
- [ ] Web3 providers configured
- [ ] Collection list page working
- [ ] Create collection form working
- [ ] Minting interface working
- [ ] Real-time updates functional
- [ ] Wallet connection working
Go/No-Go Decision: If all items checked, proceed to Phase 5
Phase 5: Studio Integration
Duration: 2 days (Days 16-17) Goal: Add "Launch as NFT" button to emprops-studio
Day 16: Add Button to Studio
Morning: Locate Integration Point
cd /Users/the_dusky/code/emerge/emerge-turbo/apps/emprops-studio
# Find the app pages
find pages -name "*app*" -type fExpected files:
pages/studio/apps/v2/[id].tsx(or similar)
Afternoon: Add Button
Edit the app detail page:
// apps/emprops-studio/pages/studio/apps/v2/[id].tsx
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function AppDetailPage() {
const router = useRouter();
const { id } = router.query;
// ... existing code
return (
<div>
{/* Existing content */}
{/* NEW: NFT Launch Button */}
<div className="mt-8 p-6 border rounded-lg bg-gradient-to-r from-purple-50 to-blue-50">
<h3 className="text-xl font-bold mb-2">🚀 Launch as NFT Collection</h3>
<p className="text-gray-600 mb-4">
Turn your app into an NFT collection. Create limited edition NFTs with custom pricing and supply.
</p>
<Link href={`http://localhost:3337/create?sourceApp=${id}`} target="_blank">
<button className="bg-gradient-to-r from-purple-600 to-blue-600 text-white px-6 py-3 rounded-lg hover:opacity-90 transition-opacity">
Launch NFT Collection →
</button>
</Link>
</div>
{/* Rest of existing content */}
</div>
);
}✅ Checkpoint: Button visible in studio
Deliverables:
- [ ] Button added to app detail page
- [ ] Links to nft-launchpad
- [ ] Passes app ID via query param
- [ ] Opens in new tab
Day 17: Test End-to-End Flow
Morning: Test Complete User Journey
- Start all services:
# Terminal 1: Indexer
pnpm --filter nft-indexer dev
# Terminal 2: Launchpad
pnpm --filter nft-launchpad dev
# Terminal 3: Studio
pnpm --filter emprops-studio dev- Execute user journey:
- [ ] Open emprops-studio (http://localhost:3336)
- [ ] Navigate to an app detail page
- [ ] See "Launch as NFT Collection" button
- [ ] Click button
- [ ] Opens nft-launchpad in new tab
- [ ] Fill out collection form
- [ ] Connect wallet
- [ ] Submit transaction
- [ ] Wait for confirmation
- [ ] See collection in list
- [ ] Click on collection
- [ ] Mint an NFT
- [ ] Transaction confirms
- [ ] NFT appears in wallet
Afternoon: Fix Any Issues
- Debug transaction errors
- Fix UI glitches
- Improve error messages
- Add loading states
✅ Checkpoint: End-to-end flow working
Deliverables:
- [ ] User can navigate from studio to launchpad
- [ ] Can create collection
- [ ] Can mint NFTs
- [ ] Real-time updates work
- [ ] No errors in console
Phase 5 Completion Checklist
- [ ] Button added to emprops-studio
- [ ] Button links to nft-launchpad
- [ ] End-to-end flow tested
- [ ] All services running together
- [ ] No regressions in studio
Go/No-Go Decision: If all items checked, proceed to Phase 6
Phase 6: Testing & Documentation
Duration: 3 days (Days 18-20) Goal: Comprehensive testing and user documentation
Day 18: Manual Testing
Morning: Test All Features
Create test checklist:
Collection Creation:
- [ ] Form validation works
- [ ] Can create with minimum values
- [ ] Can create with maximum values
- [ ] Invalid inputs show errors
- [ ] Transaction fails gracefully
- [ ] Success message appears
- [ ] Redirects to collection list
- [ ] Collection appears in list immediately (WebSocket)
Minting:
- [ ] Can mint 1 NFT
- [ ] Can mint multiple NFTs
- [ ] Cannot mint when paused
- [ ] Cannot mint before start time
- [ ] Cannot exceed max per mint
- [ ] Cannot exceed max supply
- [ ] Correct payment amount calculated
- [ ] Excess payment refunded
- [ ] NFT appears in wallet
Edge Cases:
- [ ] Disconnecting wallet during transaction
- [ ] Network switch during transaction
- [ ] Multiple users creating collections
- [ ] Rapid minting
- [ ] Indexer lag
Afternoon: Performance Testing
Test scenarios:
- [ ] 10 collections created
- [ ] 100 NFTs minted
- [ ] Page load times acceptable
- [ ] WebSocket reconnects automatically
- [ ] API responses under 1s
- [ ] No memory leaks
✅ Checkpoint: All features working correctly
Deliverables:
- [ ] Test checklist completed
- [ ] Bugs documented
- [ ] Critical issues fixed
- [ ] Performance acceptable
Day 19: User Documentation
Morning: Create User Guide
Create apps/nft-launchpad/docs/USER_GUIDE.md:
# NFT Launchpad User Guide
## Getting Started
### Prerequisites
- MetaMask or compatible Web3 wallet
- Base Sepolia testnet ETH (get from faucet)
### Connecting Your Wallet
1. Click "Connect Wallet" button
2. Select your wallet (MetaMask, WalletConnect, etc.)
3. Approve the connection
4. Ensure you're on Base Sepolia network
## Creating an NFT Collection
### Step 1: Open Launchpad
From emprops-studio, click "Launch as NFT Collection" on any app page.
### Step 2: Fill Out Form
- **Collection Name**: Give your collection a name
- **Symbol**: 3-4 letter symbol (e.g., COOL)
- **Max Supply**: Total number of NFTs (1-10,000)
- **Mint Price**: Price per NFT in ETH
- **Max Per Mint**: Max NFTs per transaction (1-100)
- **Start Time**: When minting begins (in minutes)
### Step 3: Create Collection
1. Review settings
2. Click "Create Collection"
3. Approve transaction in wallet
4. Wait for confirmation (~5-10 seconds)
5. Collection appears in list
## Minting NFTs
### Step 1: Find Collection
Browse collections on home page and click on one.
### Step 2: Mint NFTs
1. Enter quantity (1-10)
2. Check total cost
3. Click "Mint NFTs"
4. Approve transaction in wallet
5. Wait for confirmation
6. NFTs appear in your wallet!
## Troubleshooting
### Transaction Failed
- Check you have enough ETH for gas + mint price
- Ensure minting has started (check start time)
- Verify collection isn't paused
- Try refreshing the page
### Wallet Won't Connect
- Ensure wallet extension is installed
- Check you're on Base Sepolia network
- Try a different browser
- Clear browser cache
### NFTs Not Showing
- Wait 30 seconds for indexing
- Refresh the page
- Check transaction on Basescan
- View NFTs in wallet directly
## Support
For issues, please contact support or check the documentation.Afternoon: Create Developer Guide
Create apps/nft-launchpad/docs/DEVELOPER_GUIDE.md:
# NFT Launchpad Developer Guide
## Architecture Overview
[Include architecture diagrams and explanations]
## Local Development
[Include setup instructions]
## Adding Features
[Include extension points]
## Debugging
[Include debugging tips]
## Deployment
[Include deployment instructions]✅ Checkpoint: Documentation complete
Deliverables:
- [ ] User guide written
- [ ] Developer guide written
- [ ] Troubleshooting section
- [ ] Screenshots added (optional)
Day 20: Final Testing & Launch Prep
Morning: Code Review
Review checklist:
- [ ] Code follows monorepo conventions
- [ ] No console.logs in production code
- [ ] Error handling is comprehensive
- [ ] Loading states are implemented
- [ ] Accessibility considerations
- [ ] Mobile responsiveness
- [ ] Security best practices
Afternoon: Launch Preparation
Pre-launch checklist:
- [ ] All tests passing
- [ ] Documentation complete
- [ ] Environment variables documented
- [ ] Database schemas created
- [ ] Contracts deployed to testnet
- [ ] Indexer running
- [ ] Monitoring in place (basic)
- [ ] Team trained on system
Create launch announcement:
# 🚀 NFT Launchpad Now Available!
We're excited to announce the launch of our NFT Launchpad, a proof-of-concept for turning AI-generated content into NFT collections.
## Features
- Create custom NFT collections
- Set your own pricing and supply
- Mint NFTs directly to your wallet
- Real-time updates
## How to Access
1. Go to any app in emprops-studio
2. Click "Launch as NFT Collection"
3. Follow the guided process
## Important Notes
- This is currently on Base Sepolia testnet
- Use testnet ETH (no real value)
- This is a proof-of-concept
- Feedback welcome!
## Support
[Support contact info]✅ Checkpoint: Ready to launch
Deliverables:
- [ ] Code reviewed
- [ ] Launch checklist complete
- [ ] Team briefed
- [ ] Announcement prepared
Phase 6 Completion Checklist
- [ ] Manual testing complete
- [ ] Performance acceptable
- [ ] User documentation written
- [ ] Developer documentation written
- [ ] Code review complete
- [ ] Launch checklist complete
- [ ] Team trained
Go/No-Go Decision: If all items checked, LAUNCH!
Rollout Strategy
Week 1: Internal Alpha
Target: Development team only
Goals:
- Validate all features work
- Find and fix critical bugs
- Gather initial feedback
Success Criteria:
- No critical bugs
- Team comfortable with system
- Documentation accurate
Week 2-3: Closed Beta
Target: 5-10 selected users
Goals:
- Validate user experience
- Test under real usage
- Identify edge cases
Success Criteria:
- Users can complete flow without help
- No data loss
- Performance acceptable
Week 4: Open Beta
Target: All emprops-studio users
Goals:
- Scale testing
- Gather broad feedback
- Monitor system health
Success Criteria:
- System handles load
- Positive user feedback
- Few support requests
Monitoring & Maintenance
Key Metrics
System Health:
- [ ] Indexer lag (blocks behind)
- [ ] API response times
- [ ] WebSocket connections
- [ ] Database connection pool usage
User Metrics:
- [ ] Collections created per day
- [ ] NFTs minted per day
- [ ] Active wallets
- [ ] Transaction success rate
Business Metrics:
- [ ] Total value locked (mintPrice * totalMinted)
- [ ] Average collection size
- [ ] User retention
Monitoring Tools
Immediate Setup:
# Add basic logging
pnpm add winston
# Add health check endpoints
# GET /health -> { status, indexerLag, dbConnections }Future Setup:
- Datadog / Sentry for error tracking
- Grafana for metrics visualization
- AlertManager for critical alerts
Maintenance Schedule
Daily:
- Check indexer is syncing
- Review error logs
- Monitor gas prices
Weekly:
- Review user feedback
- Check database size
- Update documentation
Monthly:
- Review contracts for upgrades
- Analyze usage patterns
- Plan new features
Success Criteria Summary
Must-Have for Launch ✅
- [x] All contracts deployed to Base Sepolia
- [x] Indexer monitoring contracts
- [x] Launchpad app functional
- [x] Button in emprops-studio
- [x] Can create collections
- [x] Can mint NFTs
- [x] Real-time updates work
- [x] Documentation complete
Nice-to-Have (Post-Launch)
- [ ] Monitoring dashboard
- [ ] Advanced features (royalties, whitelist)
- [ ] Multi-chain support
- [ ] Secondary market
Conclusion
This implementation plan provides a detailed, day-by-day roadmap for integrating the NFT infrastructure into emerge-turbo. The phased approach ensures:
✅ Incremental Progress - Clear milestones every 2-3 days ✅ Risk Management - Go/No-Go decisions at each phase ✅ Quality Assurance - Testing built into every phase ✅ Team Alignment - Clear deliverables and checkpoints
Total Timeline: 20 working days (3-4 weeks) Team Size: 1-2 developers Risk Level: Low to Medium Success Probability: High (8.1/10)
Next Step: Begin Phase 1 (Validation & Setup)
Related Documents:
