Skip to content

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

PhaseScopeTimelineStatus
1Smart Contracts (Diamond + Factory)Days 1-2Pending
2API Endpoints (setup, deploy, generate, mint, metadata)Days 3-5Pending
3Database Schema (NFT fields on collection model)Day 4Pending
4Integration & E2E TestingDays 6-8Pending
5Studio NFT Restoration (Arbitrum UI integration)Post-launchPlanned
6Advanced Features (auction, marketplace facet, Ponder)FuturePlanned

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

DecisionChoiceRationale
Contract patternERC-2535 DiamondPer-collection upgradeability, shared facets
ToolingHardhat 3 (beta)Faster tests, declarative config, multichain native
ChainArbitrum (Sepolia → One)Grant-funded, low gas, sub-second finality
Minting modelCustodial mintToPlatform signs txs, users never need wallets
Collection setupGemini LLM direct callReturns EmProps v2 instruction set
MetadataGCS + CDNExisting infra, fast, updateable
IndexingNone (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:

  • hardhat v3 (beta)
  • @openzeppelin/contracts v5.x
  • erc721a v4.x
  • viem (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.

FilePurpose
contracts/Diamond.solBase Diamond proxy (fallback + receive)
contracts/libraries/LibDiamond.solDiamond storage, cut logic, owner enforcement
contracts/interfaces/IDiamondCut.solStandard: add/replace/remove facets
contracts/interfaces/IDiamondLoupe.solStandard: introspection
contracts/interfaces/IERC165.solStandard: interface detection

Diamond Storage Pattern:

solidity
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

FacetKey Functions
DiamondCutFacet.soldiamondCut(FacetCut[], address, bytes)
DiamondLoupeFacet.solfacets(), facetAddress(bytes4), facetFunctionSelectors(address)
OwnershipFacet.solowner(), transferOwnership(address)

1.4 NFT Facets

FacetKey FunctionsStorage
ERC721AFacet.soltransferFrom, approve, balanceOf, ownerOf, safeTransferFromLibERC721A
ERC721MetadataFacet.solname(), symbol(), tokenURI(tokenId)baseURI + tokenIdLibMeta
MintFacet.solmintTo(address, uint256), totalSupply(), maxSupply()LibMint
RoyaltyFacet.solroyaltyInfo(tokenId, salePrice) → ERC-2981LibRoyalty

Storage Libraries (each at unique keccak256 slot):

  • LibERC721A.sol - Token ownership, balances, approvals (ERC721A-style packed data)
  • LibMint.sol - maxSupply, mintPaused, currentIndex
  • LibMeta.sol - name, symbol, baseURI
  • LibRoyalty.sol - royaltyReceiver, royaltyBps

1.5 Diamond Factory

solidity
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 FileCoverage
test/Diamond.test.tsProxy routing, facet selector mapping, DiamondCut operations
test/DiamondFactory.test.tsCollection creation, CREATE2 determinism, event emission
test/ERC721A.test.tsTransfer, approval, balance, safeTransfer
test/MintFacet.test.tsmintTo, maxSupply enforcement, admin-only
test/Metadata.test.tstokenURI resolution (baseURI + tokenId)
test/Royalty.test.tsERC-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 addresses

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

  • ethers v6.x - Server-side contract interaction + signing

New environment variables:

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

2.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 serving

Register in apps/emprops-api/src/index.ts following existing pattern (~line 1240).

2.3 Endpoint: Collection Setup (Gemini LLM)

POST /nft/collections/setup
Auth: jwtOrApiKeyMiddleware

Request:

json
{
  "prompt": "cosmic dreamscapes with nebula effects",
  "style_preferences": "vibrant, surreal"
}

Response:

json
{
  "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 GenerationInput type from apps/emprops-api/src/modules/art-gen/nodes-v2/index.ts

2.4 Endpoint: Deploy Collection

POST /nft/collections/:id/deploy
Auth: jwtOrApiKeyMiddleware

Request:

json
{
  "max_supply": 1000,
  "royalty_bps": 500,
  "royalty_receiver": "0x..."
}

Response:

json
{
  "data": {
    "contract_address": "0x...",
    "tx_hash": "0x...",
    "base_token_uri": "https://cdn.emerge.pizza/nft-metadata/{collection_id}/"
  },
  "error": null
}

Implementation:

  1. Validate collection exists and user owns it
  2. Create ethers.js Wallet from NFT_DEPLOYER_PRIVATE_KEY
  3. Call DiamondFactory.createCollection(name, symbol, baseURI, maxSupply, royaltyReceiver, royaltyBps, owner)
  4. Wait for tx receipt
  5. Parse CollectionCreated event for diamond address
  6. 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: jwtOrApiKeyMiddleware

Request:

json
{
  "token_id": 1,
  "generation_overrides": {}
}

Response:

json
{
  "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:

  1. Submit artwork generation job to existing queue (ComfyUI pipeline)
  2. On completion: save asset to GCS at generations/{collection_id}/{tokenId}.{ext}
  3. 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": [...]
    }
  4. Write metadata to GCS at nft-metadata/{collection_id}/{tokenId} using StorageClient.storeFileReturnPath()
  5. Create output record in DB

2.6 Endpoint: Mint Token

POST /nft/collections/:id/mint
Auth: jwtOrApiKeyMiddleware

Request:

json
{
  "recipient_address": "0x...",
  "quantity": 1
}

Response:

json
{
  "data": {
    "tx_hash": "0x...",
    "token_ids": [1]
  },
  "error": null
}

Implementation:

  1. Validate collection is deployed (contract_address exists)
  2. Validate tokens_minted + quantity <= max_supply
  3. Call MintFacet.mintTo(recipient, quantity) via ethers.js
  4. Wait for tx receipt
  5. Update collection.tokens_minted += quantity
  6. 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: jwtOrApiKeyMiddleware

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

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

bash
cd packages/database
pnpm prisma migrate dev --name add-nft-diamond-fields

Phase 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 correctly

4.2 Test Matrix

TestTypeTool
Diamond proxy routingUnitHardhat 3
Factory deploymentUnitHardhat 3
mintTo permissionsUnitHardhat 3
tokenURI resolutionUnitHardhat 3
Gemini setup endpointIntegrationvitest
Deploy endpointIntegrationvitest + local Hardhat
Metadata servingIntegrationvitest
Mint endpointIntegrationvitest + local Hardhat

4.3 Verification Checklist

  • [ ] Diamond Factory deploys collections on Arbitrum Sepolia
  • [ ] mintTo mints tokens to arbitrary addresses
  • [ ] tokenURI returns 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

FilePurpose
packages/nft-contracts/package.jsonHardhat 3 project config
packages/nft-contracts/hardhat.config.tsHardhat 3 declarative config
packages/nft-contracts/contracts/Diamond.solBase Diamond proxy
packages/nft-contracts/contracts/DiamondFactory.solFactory contract
packages/nft-contracts/contracts/facets/*.solAll 7 facets
packages/nft-contracts/contracts/libraries/*.solStorage libraries
packages/nft-contracts/contracts/interfaces/*.solStandard interfaces
packages/nft-contracts/test/*.test.tsContract tests
packages/nft-contracts/scripts/deploy.tsDeployment script
apps/emprops-api/src/routes/nft/index.tsNFT route router
apps/emprops-api/src/routes/nft/setup.tsGemini setup endpoint
apps/emprops-api/src/routes/nft/deploy.tsDiamond deploy endpoint
apps/emprops-api/src/routes/nft/generate.tsToken generation endpoint
apps/emprops-api/src/routes/nft/mint.tsCustodial mint endpoint
apps/emprops-api/src/routes/nft/metadata.tsMetadata serving endpoint

Existing Files to Modify

FileChange
packages/database/prisma/schema.prismaAdd NFT fields to collection model
apps/emprops-api/package.jsonAdd ethers.js dependency
apps/emprops-api/src/index.tsRegister NFT routes (~line 1240)

Existing Code to Reuse

WhatLocation
GenerationInput typeapps/emprops-api/src/modules/art-gen/nodes-v2/index.ts
v2InstructionSet templateapps/emprops-api/src/routes/projects/untitled/index.ts
StorageClientapps/emprops-api/src/clients/storage-client.ts
Gemini auth patternapps/worker/src/utils/gcp-gemini-auth.ts
Route registrationapps/emprops-api/src/index.ts (line ~1240)
Collection Zod schemaapps/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:

CodebaseLanguageStatusKey NFT Capabilities
emprops-api (stakeordie)Java/Spring BootLegacy (not in monorepo)Full marketplace: mint, list, buy, sell, royalties, event audit
emprops-open-apiNode.jsPredecessor to turboMetadata + IPFS + rewards, no active minting
apps/emprops-api (turbo)Node.jsCurrentCollection CRUD, no direct blockchain interaction
apps/emprops-studio (turbo)Next.jsCurrentFull NFT UI: marketplace, minting, publishing, wallet
emprops-hardhatSolidityExternal (80% complete)ERC721A + Factory + Owner Token contracts
emprops-ponderTypeScriptExternal (75% complete)Blockchain indexer with API + WebSocket
emprops-react-web3TypeScriptExternal (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:

ComponentFile(s)LinesCurrent State
Marketplace Browsepages/marketplace/index.tsxFunctional, lists public collections
Collection Viewpages/marketplace/apps/[id]/index.tsxShows collection detail + tokens
Token Detailpages/tokens/emprops/[id]/[tokenId].tsxToken view with metadata
MintButton (Tezos)components/MintButton/TezosMint.tsxTezos beacon wallet minting
MintButton (Ethereum)components/MintButton/EthMint.tsxEVM minting via wagmi
MintButton (Base)components/MintButton/BaseMint.tsxEVM minting via wagmi
PublishCollectionModalcomponents/PublishCollectionModal/Blockchain publishing flow
CollectionSettingscomponents/CollectionSettings/Manage published collections
MarketActionscomponents/MarketActions/Buy/sell/list (Tezos only)
contract-client.tsclients/contract-client.ts1,275Core NFT engine: all chains
Contract hookshooks/contract-client.ts402Modern EVM hooks via emprops-clients
Collection hookshooks/collections.ts517Collection CRUD + receivers
Wallet typestypes/wallet.tsBlockchain = "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" to Blockchain type
  • 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/mint endpoint
  • 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/deploy instead 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 Diamond
  • getArbitrumTokenMetadata(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/:tokenId API
  • 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:

FeatureOriginal ImplementationRestoration Approach
Marketplace listingsSalesEntity + endpointsAdd marketplace facet to Diamond, or use OpenSea APIs
Secondary sales trackingPrivateSalesControllerPonder indexer in v2, or OpenSea API polling
Floor/ceiling pricesProjectController.statsAggregate from sales data or OpenSea
Token trait filteringtoken_features tableAlready exists in studio, wire to on-chain attributes
Event audit trailEventEntity (57 lines)Emit events from API + index from chain
Questionnaire-driven artquestionnaire_answeredAlready exists as EmProps v2 variables system
Royalty configurationRoyaltiesEntityRoyaltyFacet (ERC-2981) in Diamond
Profile pagesProfileController + studio pagesAlready 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

  • [ ] ARBITRUM added 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)

FileChange
apps/emprops-studio/types/wallet.tsAdd ARBITRUM to Blockchain type + chain IDs
apps/emprops-studio/clients/contract-client.tsAdd Diamond contract read functions
apps/emprops-studio/hooks/contract-client.tsAdd Arbitrum chain + Diamond hooks
apps/emprops-studio/hooks/collections.tsSupport 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].tsxRender Arbitrum token metadata

5.7 Files to Create (Phase 5)

FilePurpose
apps/emprops-studio/components/MintButton/ArbitrumMint.tsxCustodial mint button for Arbitrum
apps/emprops-studio/hooks/diamond.tsDiamond-specific hooks (loupe, facets)
apps/emprops-studio/lib/arbitrum.tsArbitrum chain config + utilities

Phase 6: Advanced Features (Future)

Features that go beyond restoration, leveraging Diamond upgradeability:

FeatureDiamond FacetDescription
Reveal MechanicsRevealFacetHidden metadata → reveal after mint (update GCS JSON)
Auction SystemAuctionFacetTime-based auctions with bid tracking
On-chain MarketplaceMarketplaceFacetNative buy/sell without OpenSea
GovernanceGovernanceFacetCollection holders vote on collection direction
Token-gated ContentAccessFacetVerify ownership for exclusive content
Batch OperationsBatchFacetBatch mint, batch transfer for efficiency
Ponder IndexerN/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:

RepoLocationContentsReuse Status
emprops-hardhat/Users/the_dusky/code/emprops/nft_investigation/emprops-hardhatERC721A + Factory + Owner Token (~80%)Reference only — Diamond replaces this pattern
emprops-ponder/Users/the_dusky/code/emprops/nft_investigation/emprops-ponderBlockchain indexer (~75%)Phase 6 — Add when secondary market features needed
emprops-react-web3/Users/the_dusky/code/emprops/nft_investigation/emprops-react-web3React provider + hooks (~80%)Reference — Patterns inform studio hook updates
emprops-api (Java)/Users/the_dusky/code/emprops/core-services/emprops-apiOriginal marketplace backend (~5,500 LOC)Reference — Feature spec for restoration
emprops-open-api/Users/the_dusky/code/emprops/emprops-open-apiNode.js predecessor to turboReference — Metadata patterns carried forward

Risk Register

RiskLikelihoodImpactMitigation
Hardhat 3 beta blockerLowHighFall back to Hardhat 2 (1 day rework)
Diamond storage collisionLowHighUse unique keccak256 slots per library
Gemini returns invalid instruction setMediumLowValidate output against schema, retry
Arbitrum Sepolia RPC reliabilityLowMediumUse multiple RPC providers
Gas estimation errorsMediumLowAdd gas buffer, monitor tx failures
Studio component breakage during Arbitrum additionMediumMediumFeature-flag Arbitrum, test alongside existing chains
contract-client.ts complexity growthMediumLowCreate separate arbitrum-client.ts if needed
OpenSea metadata compatibilityLowMediumTest metadata format against OpenSea standards early

Released under the MIT License.