Skip to content

Mainnet Smoke Test Guide

How to validate the full Diamond NFT system on Arbitrum One before launching real collections. The process: deploy test infrastructure, exercise every feature, abandon the test contracts, then redeploy clean for production.


Prerequisites

RequirementDetails
Environment builtpnpm env:build staging — generates .env.staging in each package
Deployer wallet fundedThe wallet behind NFT_DEPLOYER_PRIVATE_KEY needs ETH on Arbitrum One for gas
Sepolia tests passingFull lifecycle should already work on Arbitrum Sepolia
Contract tests passingpnpm nft:test — all tests green
emprops-api runningStaging or local instance, pointed at mainnet chain config

Warning

This process uses real ETH for gas on Arbitrum One. Costs are low (typical Arbitrum L2 gas — see Cost Estimate), but make sure the deployer wallet is funded.


Phase 1: Deploy Infrastructure

bash
pnpm nft:deploy:mainnet

This deploys the full Diamond infrastructure to Arbitrum One:

  • All shared facets (ERC721, Mint, ContractURI, MetadataUpdate)
  • CollectionInit initializer
  • DiamondFactory

It writes all contract addresses to packages/nft-contracts/deployments/arbitrum-one.json. Open that file and confirm all addresses are present.

Optionally verify contracts on Arbiscan so the source code is publicly readable — this also lets you interact with contracts through Arbiscan's Read/Write UI.


Phase 2: Deploy a Test Collection

Use the Monitor NFT Admin UI (Collections tab) to deploy a throwaway collection. Enter a test collection's UUID and deploy it to chain 42161 (Arbitrum One).

The monitor handles the full flow: calls the DiamondFactory on-chain, writes the contract address back to the DB, and sets mint_status = "deployed".

What to confirm:

  • Transaction succeeded on Arbiscan
  • Collection record in DB has contract_address and chain_id = 42161
  • On-chain state is correct: name(), symbol(), maxSupply(), mintActive() = true, minter() = deployer address

You can check on-chain state through the Monitor UI or directly on Arbiscan.


Phase 3: Test Minting

Mint via API (metadata pipeline)

The standard mint path goes through emprops-api, which builds metadata and mints on-chain:

POST /nft/collections/:testCollectionId/mint
{
  "recipient": "<your-wallet-address>",
  "chainId": 42161
}

This builds ERC-721 metadata from the collection's generation config, calls MintFacet.mintTo() on-chain, creates a deck record, and stores the token metadata.

What to confirm: totalSupply() incremented, token appears in recipient wallet on Arbiscan, tokenURI() returns the correct metadata URL.

Mint via Monitor (direct on-chain)

Use Monitor → Mint tab to mint directly without the metadata pipeline. This calls the contract's mintTo() directly.

What to confirm: Second token minted, totalSupply() incremented again.

Verify Metadata

The metadata endpoints are public (no auth):

GET /nft/metadata/:testCollectionId/collection.json
GET /nft/metadata/:testCollectionId/0

Confirm contract-level metadata (ERC-7572) and token metadata (name, image, attributes) serve correctly and the image URL resolves.


Phase 4: Test Admin Operations

All admin operations go through Monitor → Admin tab, or directly via the monitor's admin API. Each call sends a transaction through the deployer wallet.

Toggle Minting

Disable minting and confirm mintActive() = false and mint attempts fail. Re-enable and confirm minting works again.

Update Base URI

Change the base URI and confirm tokenURI() resolves to the new path.

Update Max Supply

Lower the max supply and confirm minting beyond the limit fails.

Transfer Minter Role

Change the minter address and confirm the old minter can no longer mint.


Phase 5: Verify External Visibility

Check that minted NFTs appear correctly on third-party platforms:

  • Collection appears on Arbiscan with correct name/symbol
  • Token transfers visible in transaction history
  • NFT appears on OpenSea (Arbitrum) — may take a few minutes to index
  • Metadata renders correctly on OpenSea (name, image, attributes)
  • contractURI() serves valid ERC-7572 collection metadata

Tip

OpenSea indexes Arbitrum One automatically. If metadata doesn't appear, use OpenSea's "Refresh metadata" button on the token page, or wait 10-15 minutes for their indexer.


Phase 6: Abandon Test Contracts

Once all checks pass, decommission the test infrastructure. This is irreversible — by design.

Decommission the Test Collection

POST /nft/admin/decommission
{
  "chainId": 42161,
  "collectionAddress": "<test-collection-address>"
}

This endpoint handles the full teardown:

  1. Calls setMintActive(false) for immediate protection
  2. Reads all facets via DiamondLoupe's facets()
  3. Executes diamondCut with action=REMOVE for every non-infrastructure facet (ERC721, Mint, ContractURI, etc.)
  4. Resets DB state: mint_statusnot_deployed, nulls contract_address and related fields

The contract still exists on-chain as a dead Diamond shell — only infrastructure facets remain (loupe, ownership, ERC165). All business functions revert.

Decommission the Factory

POST /nft/factory/decommission
{
  "chainId": 42161
}

This endpoint handles the full factory teardown:

  1. Bricks ALL deployed collections on chain 42161 (same decommission as above, for each one)
  2. Transfers factory ownership to 0x000000000000000000000000000000000000dEaD (irreversible burn)
  3. Deletes the nft_factory_deployment DB record

After this, the factory contract exists on-chain but can never create collections again — owner() is the burn address.

Danger

Factory decommission is irreversible. The factory can never create collections again. This is by design — you'll deploy a fresh factory for production.


Phase 7: Production Deploy

With the smoke test complete, deploy fresh production infrastructure:

bash
pnpm nft:deploy:mainnet

This deploys a brand new factory and facets. The old test contracts are abandoned on-chain but have no connection to the new deployment.

What to confirm:

  • New arbitrum-one.json written with fresh contract addresses (different from the test deploy)
  • Factory owner() = deployer address (not burn address)
  • Ready to create real collections

Post-Deploy

  • Commit packages/nft-contracts/deployments/arbitrum-one.json to the repo
  • Verify the Monitor UI shows the correct factory for chain 42161
  • Consider transferring factory ownership to a multi-sig for production security

Quick Reference

#StepCommand / Action
1Deploy test factory + facetspnpm nft:deploy:mainnet
2Deploy test collectionMonitor UI → Collections tab
3Mint via API (metadata pipeline)POST /nft/collections/:id/mint
4Mint via Monitor (direct)Monitor UI → Mint tab
5Verify metadata endpointsGET /nft/metadata/:id/collection.json
6Test admin operationsMonitor UI → Admin tab
7Verify on Arbiscan + OpenSeaManual check
8Decommission test collectionPOST /nft/admin/decommission
9Decommission test factoryPOST /nft/factory/decommission
10Fresh production deploypnpm nft:deploy:mainnet
11Commit deployment JSONgit add packages/nft-contracts/deployments/arbitrum-one.json

Cost Estimate

Arbitrum One gas is cheap. Rough estimates for the full smoke test:

OperationEstimated Cost
Deploy factory + facets + init~$1-3
Deploy test collection~$0.50-1
Mint 2-3 tokens~$0.10-0.30
Admin operations (4-5 txns)~$0.20-0.50
Decommission collection + factory~$0.20-0.60
Total smoke test + teardown + production redeploy~$4-10

Tip

Check Arbiscan Gas Tracker before starting. Gas varies but the full cycle should stay well under $10.

Released under the MIT License.