NFT Diamond Standard (ERC-2535) on Arbitrum: Implementation Guide
Related ADR: NFT Diamond Standard on ArbitrumDeadline: End of next week (~8 working days) for Phases 1-4 Scope: Smart contracts + API endpoints + Studio NFT restoration
Phase Summary
| Phase | Scope | Timeline | Status |
|---|---|---|---|
| 1 | Smart Contracts (Diamond + Factory) | Days 1-2 | Pending |
| 2 | API Endpoints (setup, deploy, generate, mint, metadata) | Days 3-5 | Pending |
| 3 | Database Schema (NFT fields on collection model) | Day 4 | Pending |
| 4 | Integration & E2E Testing | Days 6-8 | Pending |
| 5 | Studio NFT Restoration (Arbitrum UI integration) | Post-launch | Planned |
| 6 | Advanced Features (auction, marketplace facet, Ponder) | Future | Planned |
Overview
Build NFT collection support for Emerge using ERC-2535 Diamond Standard on Arbitrum. Creators deploy NFT collections backed by AI-generated content, with Gemini LLM-powered collection setup.
Key Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Contract pattern | ERC-2535 Diamond | Per-collection upgradeability, shared facets |
| Tooling | Hardhat 3 (beta) | Faster tests, declarative config, multichain native |
| Chain | Arbitrum (Sepolia → One) | Grant-funded, low gas, sub-second finality |
| Minting model | Custodial mintTo | Platform signs txs, users never need wallets |
| Collection setup | Gemini LLM direct call | Returns EmProps v2 instruction set |
| Metadata | GCS + CDN | Existing infra, fast, updateable |
| Indexing | None (v1) | We control all mints, add Ponder in v2 |
Phase 1: Smart Contracts Package (Days 1-2)
1.1 Package Setup
Create packages/nft-contracts/ as a Hardhat 3 project.
package.json dependencies:
hardhatv3 (beta)@openzeppelin/contractsv5.xerc721av4.xviem(for deploy scripts)
hardhat.config.ts: Declarative config targeting Arbitrum Sepolia (chain ID 421614).
1.2 Diamond Core (Libraries + Interfaces)
Based on diamond-3-hardhat reference implementation.
| File | Purpose |
|---|---|
contracts/Diamond.sol | Base Diamond proxy (fallback + receive) |
contracts/libraries/LibDiamond.sol | Diamond storage, cut logic, owner enforcement |
contracts/interfaces/IDiamondCut.sol | Standard: add/replace/remove facets |
contracts/interfaces/IDiamondLoupe.sol | Standard: introspection |
contracts/interfaces/IERC165.sol | Standard: interface detection |
Diamond Storage Pattern:
bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
struct DiamondStorage {
mapping(bytes4 => FacetAddressAndSelectorPosition) facetAddressAndSelectorPosition;
bytes4[] selectors;
mapping(bytes4 => bool) supportedInterfaces;
address contractOwner;
}1.3 Core Facets
| Facet | Key Functions |
|---|---|
DiamondCutFacet.sol | diamondCut(FacetCut[], address, bytes) |
DiamondLoupeFacet.sol | facets(), facetAddress(bytes4), facetFunctionSelectors(address) |
OwnershipFacet.sol | owner(), transferOwnership(address) |
1.4 NFT Facets
| Facet | Key Functions | Storage |
|---|---|---|
ERC721AFacet.sol | transferFrom, approve, balanceOf, ownerOf, safeTransferFrom | LibERC721A |
ERC721MetadataFacet.sol | name(), symbol(), tokenURI(tokenId) → baseURI + tokenId | LibMeta |
MintFacet.sol | mintTo(address, uint256), totalSupply(), maxSupply() | LibMint |
RoyaltyFacet.sol | royaltyInfo(tokenId, salePrice) → ERC-2981 | LibRoyalty |
Storage Libraries (each at unique keccak256 slot):
LibERC721A.sol- Token ownership, balances, approvals (ERC721A-style packed data)LibMint.sol- maxSupply, mintPaused, currentIndexLibMeta.sol- name, symbol, baseURILibRoyalty.sol- royaltyReceiver, royaltyBps
1.5 Diamond Factory
contract DiamondFactory {
// Pre-deployed facet addresses (set in constructor)
address[] public facetAddresses;
IDiamondCut.FacetCut[] public defaultCuts;
function createCollection(
string memory name,
string memory symbol,
string memory baseURI,
uint256 maxSupply,
address royaltyReceiver,
uint96 royaltyBps,
address owner
) external returns (address) {
// 1. Deploy new Diamond proxy with CREATE2
// 2. Execute diamondCut to attach all default facets
// 3. Initialize: name, symbol, baseURI, maxSupply, royalty, owner
// 4. Emit CollectionCreated event
// 5. Return diamond address
}
event CollectionCreated(
address indexed diamond,
string name,
string symbol,
address indexed owner
);
}CREATE2 salt: keccak256(abi.encodePacked(msg.sender, name, symbol, block.timestamp))
1.6 Tests
| Test File | Coverage |
|---|---|
test/Diamond.test.ts | Proxy routing, facet selector mapping, DiamondCut operations |
test/DiamondFactory.test.ts | Collection creation, CREATE2 determinism, event emission |
test/ERC721A.test.ts | Transfer, approval, balance, safeTransfer |
test/MintFacet.test.ts | mintTo, maxSupply enforcement, admin-only |
test/Metadata.test.ts | tokenURI resolution (baseURI + tokenId) |
test/Royalty.test.ts | ERC-2981 royaltyInfo returns |
1.7 Deploy Script
scripts/deploy.ts:
1. Deploy DiamondCutFacet
2. Deploy DiamondLoupeFacet
3. Deploy OwnershipFacet
4. Deploy ERC721AFacet
5. Deploy ERC721MetadataFacet
6. Deploy MintFacet
7. Deploy RoyaltyFacet
8. Deploy DiamondFactory (with all facet addresses)
9. Verify all contracts on Arbiscan
10. Output JSON with all addressesPhase 1 Checklist
- [ ] Package created and compiles
- [ ] All facets implemented
- [ ] Factory deploys Diamonds correctly
- [ ] Tests pass
- [ ] Deployed to Arbitrum Sepolia
- [ ] Verified on Arbiscan
Phase 2: API Endpoints (Days 3-5)
2.1 Dependencies
Add to apps/emprops-api/package.json:
ethersv6.x - Server-side contract interaction + signing
New environment variables:
NFT_DEPLOYER_PRIVATE_KEY=0x... # Platform wallet private key
NFT_FACTORY_ADDRESS=0x... # Deployed DiamondFactory address
NFT_RPC_URL=https://... # Arbitrum RPC endpoint
# Gemini: reuse existing GCP_GEMINI_CREDENTIALS_JSON2.2 Route Structure
All under apps/emprops-api/src/routes/nft/:
routes/nft/
├── index.ts # Router module
├── setup.ts # Gemini collection setup
├── deploy.ts # Diamond deployment
├── generate.ts # Token artwork generation
├── mint.ts # Custodial minting
└── metadata.ts # Metadata servingRegister in apps/emprops-api/src/index.ts following existing pattern (~line 1240).
2.3 Endpoint: Collection Setup (Gemini LLM)
POST /nft/collections/setup
Auth: jwtOrApiKeyMiddlewareRequest:
{
"prompt": "cosmic dreamscapes with nebula effects",
"style_preferences": "vibrant, surreal"
}Response:
{
"data": {
"version": "v2",
"steps": [
{ "id": 1, "nodeName": "prompt", "nodePayload": { "prompt": "cosmic dreamscape..." } },
{ "id": 2, "nodeName": "comfyui_workflow", "nodePayload": { "workflow": "sdxl-base-v1" } }
],
"generations": { "generations": 1, "hashes": [], "use_custom_hashes": false },
"variables": [
{
"name": "style", "type": "pick", "value_type": "strings",
"value": { "display_names": ["Nebula", "Galaxy"], "values": ["nebula", "galaxy"], "weights": [1, 1] },
"lock_value": false, "test_value": null
}
]
},
"error": null
}Implementation:
- Direct HTTP call to Gemini API (reuse auth pattern from
apps/worker/src/utils/gcp-gemini-auth.ts) - System prompt includes v2 instruction set schema + example
- Returns
GenerationInputtype fromapps/emprops-api/src/modules/art-gen/nodes-v2/index.ts
2.4 Endpoint: Deploy Collection
POST /nft/collections/:id/deploy
Auth: jwtOrApiKeyMiddlewareRequest:
{
"max_supply": 1000,
"royalty_bps": 500,
"royalty_receiver": "0x..."
}Response:
{
"data": {
"contract_address": "0x...",
"tx_hash": "0x...",
"base_token_uri": "https://cdn.emerge.pizza/nft-metadata/{collection_id}/"
},
"error": null
}Implementation:
- Validate collection exists and user owns it
- Create ethers.js Wallet from
NFT_DEPLOYER_PRIVATE_KEY - Call
DiamondFactory.createCollection(name, symbol, baseURI, maxSupply, royaltyReceiver, royaltyBps, owner) - Wait for tx receipt
- Parse
CollectionCreatedevent for diamond address - Update collection in DB:
contract_address,deploy_tx_hash,base_token_uri,max_supply,blockchain: "ARBITRUM"
2.5 Endpoint: Generate Token
POST /nft/collections/:id/generate
Auth: jwtOrApiKeyMiddlewareRequest:
{
"token_id": 1,
"generation_overrides": {}
}Response:
{
"data": {
"token_id": 1,
"metadata_uri": "https://cdn.emerge.pizza/nft-metadata/{collection_id}/1",
"asset_uri": "https://cdn.emerge.pizza/generations/{collection_id}/1.png",
"job_id": "uuid"
},
"error": null
}Implementation:
- Submit artwork generation job to existing queue (ComfyUI pipeline)
- On completion: save asset to GCS at
generations/{collection_id}/{tokenId}.{ext} - Build metadata JSON (ERC721 standard):json
{ "name": "{collection_title} #{tokenId}", "description": "...", "image": "https://cdn.emerge.pizza/generations/{collection_id}/{tokenId}.png", "external_url": "https://emerge.pizza/nft/{collection_id}/{tokenId}", "attributes": [...] } - Write metadata to GCS at
nft-metadata/{collection_id}/{tokenId}usingStorageClient.storeFileReturnPath() - Create output record in DB
2.6 Endpoint: Mint Token
POST /nft/collections/:id/mint
Auth: jwtOrApiKeyMiddlewareRequest:
{
"recipient_address": "0x...",
"quantity": 1
}Response:
{
"data": {
"tx_hash": "0x...",
"token_ids": [1]
},
"error": null
}Implementation:
- Validate collection is deployed (
contract_addressexists) - Validate
tokens_minted + quantity <= max_supply - Call
MintFacet.mintTo(recipient, quantity)via ethers.js - Wait for tx receipt
- Update
collection.tokens_minted += quantity - Return tx hash and minted token IDs
2.7 Endpoint: Metadata (Public)
GET /nft/collections/:id/metadata/:tokenId
Auth: NONE (public - this is what tokenURI resolves to)Response: Standard ERC721 metadata JSON.
Implementation:
- Read metadata from GCS/DB
- Return JSON with correct Content-Type
- If metadata doesn't exist, return 404
GET /nft/collections/:id/tokens
Auth: jwtOrApiKeyMiddlewareResponse: Paginated list of tokens with metadata URIs and mint status.
Phase 2 Checklist
- [ ] ethers.js added to emprops-api
- [ ] NFT routes registered
- [ ] Setup endpoint returns valid instruction sets via Gemini
- [ ] Deploy endpoint creates Diamonds on Arbitrum
- [ ] Generate endpoint creates artwork + metadata
- [ ] Mint endpoint mints tokens
- [ ] Metadata endpoint serves ERC721 JSON
Phase 3: Database Schema (Day 4, parallel with API)
3.1 Schema Changes
File: packages/database/prisma/schema.prisma
Add to existing collection model:
// NFT on-chain fields
contract_address String? // Diamond proxy address
base_token_uri String? // CDN base URI for metadata
max_supply Int? // On-chain max supply
tokens_minted Int @default(0)
royalty_bps Int? // Basis points (500 = 5%)
royalty_receiver String? // Royalty recipient address
mint_status String? @default("draft") // draft|deploying|active|paused|completed
deploy_tx_hash String? // Deployment transaction hash
chain_id Int? // 421614 (Arb Sepolia) or 42161 (Arb One)Add "ARBITRUM" to blockchain values (currently "ETHEREUM" | "BASE" | "TEZOS").
3.2 Migration
cd packages/database
pnpm prisma migrate dev --name add-nft-diamond-fieldsPhase 3 Checklist
- [ ] Schema updated
- [ ] Migration created and applied
- [ ] Existing data unaffected (all new fields nullable)
Phase 4: Integration & Testing (Days 6-8)
4.1 End-to-End Flow
1. POST /nft/collections/setup { prompt: "cosmic art" }
→ Returns v2 instruction set
2. POST /projects/:pid/collections (existing)
→ Creates collection in DB with instruction set data
3. POST /nft/collections/:id/deploy { max_supply: 100, ... }
→ Diamond deployed on Arbitrum Sepolia
→ collection.contract_address set
4. POST /nft/collections/:id/generate { token_id: 1 }
→ Artwork generated via ComfyUI
→ Metadata JSON written to GCS
→ CDN serves metadata at baseURI/1
5. POST /nft/collections/:id/mint { recipient_address: "0x...", quantity: 1 }
→ Token minted on Arbitrum
→ tokenURI(1) → CDN → metadata → image
6. Verify on OpenSea (Arbitrum Sepolia)
→ Collection visible, metadata + image load correctly4.2 Test Matrix
| Test | Type | Tool |
|---|---|---|
| Diamond proxy routing | Unit | Hardhat 3 |
| Factory deployment | Unit | Hardhat 3 |
| mintTo permissions | Unit | Hardhat 3 |
| tokenURI resolution | Unit | Hardhat 3 |
| Gemini setup endpoint | Integration | vitest |
| Deploy endpoint | Integration | vitest + local Hardhat |
| Metadata serving | Integration | vitest |
| Mint endpoint | Integration | vitest + local Hardhat |
4.3 Verification Checklist
- [ ] Diamond Factory deploys collections on Arbitrum Sepolia
- [ ]
mintTomints tokens to arbitrary addresses - [ ]
tokenURIreturns valid metadata from CDN - [ ] Gemini returns valid EmProps v2 instruction sets
- [ ] Generate → metadata → mint pipeline works end-to-end
- [ ] Tokens visible on OpenSea (Arbitrum Sepolia testnet)
- [ ] Front-end dev has documented API endpoints to integrate against
File Inventory
New Files to Create
| File | Purpose |
|---|---|
packages/nft-contracts/package.json | Hardhat 3 project config |
packages/nft-contracts/hardhat.config.ts | Hardhat 3 declarative config |
packages/nft-contracts/contracts/Diamond.sol | Base Diamond proxy |
packages/nft-contracts/contracts/DiamondFactory.sol | Factory contract |
packages/nft-contracts/contracts/facets/*.sol | All 7 facets |
packages/nft-contracts/contracts/libraries/*.sol | Storage libraries |
packages/nft-contracts/contracts/interfaces/*.sol | Standard interfaces |
packages/nft-contracts/test/*.test.ts | Contract tests |
packages/nft-contracts/scripts/deploy.ts | Deployment script |
apps/emprops-api/src/routes/nft/index.ts | NFT route router |
apps/emprops-api/src/routes/nft/setup.ts | Gemini setup endpoint |
apps/emprops-api/src/routes/nft/deploy.ts | Diamond deploy endpoint |
apps/emprops-api/src/routes/nft/generate.ts | Token generation endpoint |
apps/emprops-api/src/routes/nft/mint.ts | Custodial mint endpoint |
apps/emprops-api/src/routes/nft/metadata.ts | Metadata serving endpoint |
Existing Files to Modify
| File | Change |
|---|---|
packages/database/prisma/schema.prisma | Add NFT fields to collection model |
apps/emprops-api/package.json | Add ethers.js dependency |
apps/emprops-api/src/index.ts | Register NFT routes (~line 1240) |
Existing Code to Reuse
| What | Location |
|---|---|
GenerationInput type | apps/emprops-api/src/modules/art-gen/nodes-v2/index.ts |
v2InstructionSet template | apps/emprops-api/src/routes/projects/untitled/index.ts |
StorageClient | apps/emprops-api/src/clients/storage-client.ts |
| Gemini auth pattern | apps/worker/src/utils/gcp-gemini-auth.ts |
| Route registration | apps/emprops-api/src/index.ts (line ~1240) |
| Collection Zod schema | apps/emprops-api/src/lib/collections.ts |
Phase 5: Studio NFT Restoration — Arbitrum Integration (Post-Launch)
Context: Existing NFT Infrastructure Audit
The Emerge platform was originally built as an NFT launch tool. A full audit of 4 codebases reveals extensive existing NFT infrastructure that can be reconnected using the new Diamond/Arbitrum backend.
Codebases Audited:
| Codebase | Language | Status | Key NFT Capabilities |
|---|---|---|---|
emprops-api (stakeordie) | Java/Spring Boot | Legacy (not in monorepo) | Full marketplace: mint, list, buy, sell, royalties, event audit |
emprops-open-api | Node.js | Predecessor to turbo | Metadata + IPFS + rewards, no active minting |
apps/emprops-api (turbo) | Node.js | Current | Collection CRUD, no direct blockchain interaction |
apps/emprops-studio (turbo) | Next.js | Current | Full NFT UI: marketplace, minting, publishing, wallet |
emprops-hardhat | Solidity | External (80% complete) | ERC721A + Factory + Owner Token contracts |
emprops-ponder | TypeScript | External (75% complete) | Blockchain indexer with API + WebSocket |
emprops-react-web3 | TypeScript | External (80% complete) | React provider, Ponder adapter, hooks |
5.1 Studio UI — What Already Exists
The studio has extensive NFT UI that is partially functional but disconnected from backend:
| Component | File(s) | Lines | Current State |
|---|---|---|---|
| Marketplace Browse | pages/marketplace/index.tsx | — | Functional, lists public collections |
| Collection View | pages/marketplace/apps/[id]/index.tsx | — | Shows collection detail + tokens |
| Token Detail | pages/tokens/emprops/[id]/[tokenId].tsx | — | Token view with metadata |
| MintButton (Tezos) | components/MintButton/TezosMint.tsx | — | Tezos beacon wallet minting |
| MintButton (Ethereum) | components/MintButton/EthMint.tsx | — | EVM minting via wagmi |
| MintButton (Base) | components/MintButton/BaseMint.tsx | — | EVM minting via wagmi |
| PublishCollectionModal | components/PublishCollectionModal/ | — | Blockchain publishing flow |
| CollectionSettings | components/CollectionSettings/ | — | Manage published collections |
| MarketActions | components/MarketActions/ | — | Buy/sell/list (Tezos only) |
| contract-client.ts | clients/contract-client.ts | 1,275 | Core NFT engine: all chains |
| Contract hooks | hooks/contract-client.ts | 402 | Modern EVM hooks via emprops-clients |
| Collection hooks | hooks/collections.ts | 517 | Collection CRUD + receivers |
| Wallet types | types/wallet.ts | — | Blockchain = "TEZOS" | "ETHEREUM" | "BASE" |
5.2 What Needs to Change for Arbitrum
5.2.1 Add ARBITRUM to Blockchain Types
File: apps/emprops-studio/types/wallet.ts
- Add
"ARBITRUM"toBlockchaintype - Add Arbitrum chain IDs (421614 for Sepolia, 42161 for mainnet)
- Add
isArbitrumCompliant()helper (EVM-compatible, same as Ethereum)
File: apps/emprops-api/src/lib/collections.ts
- Add
"ARBITRUM"to Zod blockchain enum (line ~255)
File: packages/database/prisma/schema.prisma
- Add
"ARBITRUM"to blockchain values
5.2.2 Create ArbitrumMint Component
New: apps/emprops-studio/components/MintButton/ArbitrumMint.tsx
- For Phase 1, custodial minting — MintButton calls API
/nft/collections/:id/mintendpoint - No user wallet needed initially (platform signs via server wallet)
- Later: Add user-signed minting via wagmi + Diamond MintFacet
5.2.3 Update PublishCollectionModal for Diamond Deployment
Modify: apps/emprops-studio/components/PublishCollectionModal/
- Add Arbitrum as deployment target
- Call
/nft/collections/:id/deployinstead of Platform API - Show deployment status (tx hash, contract address, Arbiscan link)
- Configure: max supply, royalty bps, royalty receiver
5.2.4 Update contract-client.ts for Diamond Pattern
Modify: apps/emprops-studio/clients/contract-client.ts
- Add Arbitrum Diamond contract interaction functions
getArbitrumCollection(contractAddress)— read collection data from DiamondgetArbitrumTokenMetadata(contractAddress, tokenId)— read tokenURI- Use viem/wagmi for read calls (consistent with existing EVM patterns)
- Write operations go through API (custodial) not direct contract calls
5.2.5 Update Contract Hooks
Modify: apps/emprops-studio/hooks/contract-client.ts
- Add Arbitrum chain support to existing
useCollectionContract - Add
useDiamondCollection(address)hook for Diamond-specific reads - Add
useDiamondLoupe(address)hook for facet introspection
5.2.6 Extend MarketActions for Arbitrum
Modify: apps/emprops-studio/components/MarketActions/
- Currently Tezos-only (line 60 blockchain check)
- For Arbitrum v1: Replace with "View on OpenSea" link
- For Arbitrum v2: Add native listing/buying (requires marketplace facet)
5.2.7 Wire Token Pages to New Metadata
Modify: apps/emprops-studio/pages/tokens/emprops/[id]/[tokenId].tsx
- Detect Arbitrum tokens by blockchain field
- Fetch metadata from
/nft/collections/:id/metadata/:tokenIdAPI - Display image, attributes, and collection info
- Link to Arbiscan for on-chain verification
5.3 Features to Restore from Legacy Platform
These features existed in the original emprops-api (Java) and can be rebuilt:
| Feature | Original Implementation | Restoration Approach |
|---|---|---|
| Marketplace listings | SalesEntity + endpoints | Add marketplace facet to Diamond, or use OpenSea APIs |
| Secondary sales tracking | PrivateSalesController | Ponder indexer in v2, or OpenSea API polling |
| Floor/ceiling prices | ProjectController.stats | Aggregate from sales data or OpenSea |
| Token trait filtering | token_features table | Already exists in studio, wire to on-chain attributes |
| Event audit trail | EventEntity (57 lines) | Emit events from API + index from chain |
| Questionnaire-driven art | questionnaire_answered | Already exists as EmProps v2 variables system |
| Royalty configuration | RoyaltiesEntity | RoyaltyFacet (ERC-2981) in Diamond |
| Profile pages | ProfileController + studio pages | Already exist in studio, add Arbitrum wallet support |
5.4 Migration Path: Legacy Features → Diamond Architecture
Legacy (Java emprops-api) → New (Diamond on Arbitrum)
─────────────────────────────────────────────────────────────────
Project/Collection CRUD → Already in turbo monorepo ✅
Token minting (off-chain tracking) → MintFacet.mintTo + API tracking
Marketplace listings → Phase 2: OpenSea links / Phase 3: Marketplace facet
Sales tracking → Phase 2: DB tracking / Phase 3: Ponder indexer
Royalties (off-chain rates) → RoyaltyFacet (ERC-2981, on-chain) ✅
Metadata (IPFS) → GCS + CDN (updateable, faster) ✅
Questionnaire → Art → EmProps v2 instruction set + Gemini ✅
Contract registry (ABI storage) → Diamond facet introspection (DiamondLoupe) ✅
Event audit trail → API events + chain events (Ponder in v2)5.5 Phase 5 Checklist
- [ ]
ARBITRUMadded to blockchain types (studio, API, database) - [ ] ArbitrumMint component created (custodial via API)
- [ ] PublishCollectionModal supports Diamond deployment
- [ ] contract-client.ts updated with Diamond read functions
- [ ] Contract hooks support Arbitrum chain
- [ ] MarketActions shows "View on OpenSea" for Arbitrum tokens
- [ ] Token detail pages render Arbitrum token metadata
- [ ] Marketplace browse includes Arbitrum collections
- [ ] Profile pages show Arbitrum tokens
5.6 Files to Modify (Phase 5)
| File | Change |
|---|---|
apps/emprops-studio/types/wallet.ts | Add ARBITRUM to Blockchain type + chain IDs |
apps/emprops-studio/clients/contract-client.ts | Add Diamond contract read functions |
apps/emprops-studio/hooks/contract-client.ts | Add Arbitrum chain + Diamond hooks |
apps/emprops-studio/hooks/collections.ts | Support Arbitrum in collection publish flow |
apps/emprops-studio/components/MintButton/ | Add ArbitrumMint component |
apps/emprops-studio/components/PublishCollectionModal/ | Add Diamond deployment flow |
apps/emprops-studio/components/MarketActions/ | Add Arbitrum support (OpenSea links) |
apps/emprops-studio/components/CollectionSettings/ | Add Arbitrum collection management |
apps/emprops-studio/pages/tokens/emprops/[id]/[tokenId].tsx | Render Arbitrum token metadata |
5.7 Files to Create (Phase 5)
| File | Purpose |
|---|---|
apps/emprops-studio/components/MintButton/ArbitrumMint.tsx | Custodial mint button for Arbitrum |
apps/emprops-studio/hooks/diamond.ts | Diamond-specific hooks (loupe, facets) |
apps/emprops-studio/lib/arbitrum.ts | Arbitrum chain config + utilities |
Phase 6: Advanced Features (Future)
Features that go beyond restoration, leveraging Diamond upgradeability:
| Feature | Diamond Facet | Description |
|---|---|---|
| Reveal Mechanics | RevealFacet | Hidden metadata → reveal after mint (update GCS JSON) |
| Auction System | AuctionFacet | Time-based auctions with bid tracking |
| On-chain Marketplace | MarketplaceFacet | Native buy/sell without OpenSea |
| Governance | GovernanceFacet | Collection holders vote on collection direction |
| Token-gated Content | AccessFacet | Verify ownership for exclusive content |
| Batch Operations | BatchFacet | Batch mint, batch transfer for efficiency |
| Ponder Indexer | N/A (infra) | Full on-chain event indexing, replace DB tracking |
Existing Code Reference: External Repos
For reference, these external repos contain prior EVM NFT work that informed this design:
| Repo | Location | Contents | Reuse Status |
|---|---|---|---|
emprops-hardhat | /Users/the_dusky/code/emprops/nft_investigation/emprops-hardhat | ERC721A + Factory + Owner Token (~80%) | Reference only — Diamond replaces this pattern |
emprops-ponder | /Users/the_dusky/code/emprops/nft_investigation/emprops-ponder | Blockchain indexer (~75%) | Phase 6 — Add when secondary market features needed |
emprops-react-web3 | /Users/the_dusky/code/emprops/nft_investigation/emprops-react-web3 | React provider + hooks (~80%) | Reference — Patterns inform studio hook updates |
emprops-api (Java) | /Users/the_dusky/code/emprops/core-services/emprops-api | Original marketplace backend (~5,500 LOC) | Reference — Feature spec for restoration |
emprops-open-api | /Users/the_dusky/code/emprops/emprops-open-api | Node.js predecessor to turbo | Reference — Metadata patterns carried forward |
Risk Register
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Hardhat 3 beta blocker | Low | High | Fall back to Hardhat 2 (1 day rework) |
| Diamond storage collision | Low | High | Use unique keccak256 slots per library |
| Gemini returns invalid instruction set | Medium | Low | Validate output against schema, retry |
| Arbitrum Sepolia RPC reliability | Low | Medium | Use multiple RPC providers |
| Gas estimation errors | Medium | Low | Add gas buffer, monitor tx failures |
| Studio component breakage during Arbitrum addition | Medium | Medium | Feature-flag Arbitrum, test alongside existing chains |
| contract-client.ts complexity growth | Medium | Low | Create separate arbitrum-client.ts if needed |
| OpenSea metadata compatibility | Low | Medium | Test metadata format against OpenSea standards early |
