Nox Shadow NFT is a privacy-first demo that combines:
- Confidential NFTs (ownership + metadata pointer + file-key stored as Nox encrypted handles)
- A factory for creating many collections
- A cUSDC-only marketplace (Arbitrum Sepolia) using a “direct pay” flow compatible with Nox proofs
- A modern Next.js UI (wallet connect +
@iexec-nox/handle)
Privacy note: this makes state confidential (via handles). EVM transaction senders and cleartext calldata are still public.
- Confidential ownership: owner stored as encrypted
uint256(uint160(address)) - Private metadata pointer: stored as encrypted
uint256(you can resolve it to an IPFS metadata CID off-chain) - Encrypted file uploads: ciphertext stored on IPFS, AES key stored confidentially on-chain as an encrypted
uint256 - Selective disclosure: grant viewers handle access; “revocation” is rotation-based
- Factory: create multiple collections from a standard implementation
- Marketplace (Pattern B):
- seller approves marketplace for NFT transfer execution
- buyer pays seller directly in cUSDC via
confidentialTransfer - buyer calls marketplace
buyWithCtoken(...)to receive the NFT - optional “operator EOA” can preview by being granted viewer rights (off-chain decryption)
- Not ERC721: standard ERC721 logs leak ownership (
Transfer(from,to,tokenId)), so this project avoids address-leaking transfer logs. - Most private possible within Nox on a public EVM:
- encrypted on-chain state for ownership + metadata pointer + file key
- public chain still reveals tx sender, timing, and any required clear arguments (e.g. recipient address for ACL)
Nox currently supports encrypted runtime types including uint256. To represent an address:
- Encode:
ownerAsUint256 = uint256(uint160(ownerAddress)) - Encrypt off-chain with
@iexec-nox/handle:encryptInput(ownerAsUint256, "uint256", registryAddress)- Send
(handle, handleProof)to the contract
- Decrypt (if you have ACL) and decode back to address:
address(uint160(decryptedUint256))
- Ownership privacy: on-chain observers see only a
bytes32handle, not an address. - Selective disclosure: you can grant a third-party decryption rights for the current owner handle.
- Revocation: NoxCompute does not provide
removeViewer()for persistent ACL. So revocation is:- rotate the owner handle to a new handle
- re-grant only the viewers you still want
- any viewer who saved the old handle can still decrypt the old state (history can’t be erased)
contracts/ConfidentialNftRegistry.sol: confidential NFT registry (ownership + metadata pointer + file key)contracts/ConfidentialNftFactory.sol: creates new collections (EIP-1167 clones)contracts/ConfidentialNftMarketplace.sol: cUSDC-only marketplace (direct pay + buy)
- NoxCompute:
0xd464B198f06756a1d00be223634b85E0a731c229 - cUSDC (accepted payment token):
0x1CCeC6bC60dB15E4055D43Dc2531BB7D4E5B808e
/dashboard: cToken portfolio + Owned NFTs (detected by decrypting owner handles)/confidential-nft: mint/transfer/metadata/file-key/ACL management/marketplace: list + buy with cUSDC (direct pay flow)/activity,/delegated-view: helper tools for cToken activity + viewer grants
- Node.js 18+ (Hardhat v3 works best on newer Node)
- A WalletConnect project ID (cloud.reown.com)
npm installCreate .env.local (minimum):
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=<your_project_id>
NEXT_PUBLIC_CONFIDENTIAL_NFT_ADDRESS=0x...
NEXT_PUBLIC_CONFIDENTIAL_NFT_MARKETPLACE_ADDRESS=0x...
# Optional (UI preview)
NEXT_PUBLIC_IPFS_GATEWAY=https://gateway.pinata.cloud/ipfsnpm run devSet env vars:
export DEPLOYER_PRIVATE_KEY=0x...
export ARBITRUM_SEPOLIA_RPC_URL="https://arbitrum-sepolia.gateway.tenderly.co"
# For IPFS pinning (Next.js API routes):
export PINATA_JWT="..."
# Marketplace payment token (defaults in scripts can be overridden)
export MARKETPLACE_ACCEPTED_CTOKEN=0x1CCeC6bC60dB15E4055D43Dc2531BB7D4E5B808eCompile + deploy (implementation + factory + marketplace + a first collection):
npm run compile:contracts
npm run deploy:confidential-nftThen copy the printed addresses into .env.local as:
NEXT_PUBLIC_CONFIDENTIAL_NFT_ADDRESSNEXT_PUBLIC_CONFIDENTIAL_NFT_MARKETPLACE_ADDRESS
Run the end-to-end flow (mint → approve → list → pay cUSDC → buy → transfer):
npm run test:e2e:arbsepoliaRequires:
SELLER_PRIVATE_KEY=0x...
BUYER_PRIVATE_KEY=0x...
NEXT_PUBLIC_CONFIDENTIAL_NFT_ADDRESS=0x...
NEXT_PUBLIC_CONFIDENTIAL_NFT_MARKETPLACE_ADDRESS=0x...contracts/ # ConfidentialNftRegistry.sol
scripts/ # Hardhat deploy script
app/(app)/confidential-nft/ # Frontend test page
app/(app)/marketplace/ # Marketplace page
hooks/use-confidential-nft.ts # Mint/transfer/rotate/grant/revoke
hooks/use-confidential-nft-disclosure.ts # Grant list (Active/Outdated)
lib/confidential-nft-abi.ts # ABI for viem/wagmi reads + events
- You cannot hide the tx sender on Arbitrum using Nox alone. For that, you need a relayer / ERC-4337 flow (separate layer).
- You cannot delete what was already disclosed: a viewer can keep decrypted results for old handles.
- Listings and marketplace actions still reveal some public metadata (seller, tokenId, listing price). The cUSDC transfer amount is confidential, but the listing price is public in this demo.
- This is a demo-quality reference implementation; audit before production use.
MIT