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_status → not_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.