Skip to content

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

  1. Overview
  2. Phase 1: Validation & Setup
  3. Phase 2: Contract Package Migration
  4. Phase 3: Indexer App Migration
  5. Phase 4: Launchpad App Creation
  6. Phase 5: Studio Integration
  7. Phase 6: Testing & Documentation
  8. Rollout Strategy
  9. 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 + Launch

Milestones

MilestoneTarget DateDeliverable
M1: Validation CompleteDay 3Contracts compile, tests pass
M2: Contracts in MonorepoDay 7Deployed to testnet
M3: Indexer RunningDay 10API responding, events indexing
M4: Launchpad MVPDay 15Can create collections
M5: Studio IntegrationDay 17Button working
M6: Testing CompleteDay 19E2E tests passing
M7: Launch ReadyDay 20Documentation 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

bash
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 compile

Expected Output:

Compiled 12 Solidity files successfully

✅ Checkpoint: All contracts compile without errors

Afternoon: Run Tests

bash
# Run full test suite
pnpm test

# If tests fail, review and fix
# Document any issues found

Expected 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

bash
cd /Users/the_dusky/code/emprops/nft_investigation/emprops-ponder

# 1. Install dependencies
pnpm install

# 2. Start dev server
pnpm dev

Expected Output:

✓ Server listening on http://localhost:42069
✓ Indexed block 1234567

Test API Endpoints:

bash
# 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

bash
cd /Users/the_dusky/code/emerge/emerge-turbo

# 1. Connect to PostgreSQL
psql $DATABASE_URL

SQL Commands:

sql
-- 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

bash
cd /Users/the_dusky/code/emerge/emerge-turbo

# Create NFT-specific environment file
touch .env.nft

Edit .env.nft:

bash
# 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

  1. Alchemy Account

  2. WalletConnect Project

  3. Test Wallet

    bash
    # Generate test wallet
    cd emprops-hardhat
    pnpm account:generate
    
    # Save private key to .env.nft
  4. Get Testnet ETH

✅ Checkpoint: All services configured

Deliverables:

  • [ ] .env.nft file 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

bash
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-contracts

Update package.json:

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:

typescript
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:

typescript
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

bash
cd /Users/the_dusky/code/emerge/emerge-turbo

# Install dependencies for all packages
pnpm install

# Compile contracts
pnpm --filter nft-contracts compile

Expected Output:

Compiled 12 Solidity files successfully

Afternoon: Run Tests

bash
# 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

bash
pnpm --filter nft-contracts db:init

Expected Output:

✓ Database schema initialized
✓ contract_deployments table created

Deploy Contracts:

bash
# Deploy all contracts
pnpm --filter nft-contracts deploy:baseSepolia

Expected 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

bash
# Store deployment info in database
pnpm --filter nft-contracts db:store-deployments

Verify in Database:

sql
-- 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

bash
# Verify contracts
pnpm --filter nft-contracts verify --network baseSepolia

Check on Basescan:

Afternoon: Test Contract Interactions

Create test script: packages/nft-contracts/scripts/test-deployment.ts

typescript
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:

bash
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

bash
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-indexer

Update package.json:

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:

typescript
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:

typescript
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:

typescript
// 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

bash
cd /Users/the_dusky/code/emerge/emerge-turbo

# Install dependencies
pnpm install

# Start indexer
pnpm --filter nft-indexer dev

Expected 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:

bash
# 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/NFTContractFactoryProxyContract

Test WebSocket:Create test file: apps/nft-indexer/test/test-socket.ts

typescript
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:

bash
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

typescript
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:

bash
pnpm --filter nft-contracts hardhat run scripts/create-test-collection.ts --network baseSepolia

Afternoon: Verify Indexing

Check Ponder logs:

✓ Indexed block 12345679
✓ Processed event: AppCreated
✓ Created app record: 0x...
✓ Minted owner token: 0

Check API:

bash
# Should now return the created collection
curl http://localhost:3338/api/apps

# Should show the minted owner token
curl http://localhost:3338/api/owner-tokens

Check 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

bash
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-launchpad

Install Web3 Dependencies:

bash
pnpm add wagmi viem@2.x @tanstack/react-query
pnpm add @rainbow-me/rainbowkit
pnpm add socket.io-client zod

Update package.json:

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:

typescript
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:

typescript
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:

typescript
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:

typescript
'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:

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
'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:

typescript
'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:

typescript
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:

typescript
'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:

typescript
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:

typescript
'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

bash
cd /Users/the_dusky/code/emerge/emerge-turbo/apps/emprops-studio

# Find the app pages
find pages -name "*app*" -type f

Expected files:

  • pages/studio/apps/v2/[id].tsx (or similar)

Afternoon: Add Button

Edit the app detail page:

typescript
// 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

  1. Start all services:
bash
# Terminal 1: Indexer
pnpm --filter nft-indexer dev

# Terminal 2: Launchpad
pnpm --filter nft-launchpad dev

# Terminal 3: Studio
pnpm --filter emprops-studio dev
  1. 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:

markdown
# 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:

markdown
# 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:

markdown
# 🚀 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:

bash
# 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:

Released under the MIT License.