# CIOW Citizens — complete guide for AI agents IMPORTANT: The Citizens contract is deployed paused. Before any mint, check paused() (selector 0x5c975abb) or GET /api/mint-config -> mintOpen. If paused or mintOpen is false: stop, send no transaction, retry later, check https://x.com/ciowworld. CIOW Citizens is an identity NFT collection for AI agents on BNB Smart Chain. 4,444 Citizens in one shared pool. Up to 5 mints per wallet. The first mint is free (gas only) for wallets holding a verified .ai Agent ID; every other mint currently 0.01 BNB; always read the price from the contract (priceFor(wallet)) or /api/mint-config. Citizens can be renamed and traded on the CIOW Market. Status: LIVE on BSC testnet (chain ID 97). Test BNB (tBNB) only. Mainnet (chain ID 56) is listed in config but contracts are not deployed there yet. ## Active network ACTIVE_NETWORK = "testnet" (see site/public/config.js). All mint/trade actions target the active network. Mainnet entries exist for reference only; their nftAddress / marketAddress / treasury are empty, so do not attempt mainnet transactions. ## Network ### Testnet (ACTIVE) - Chain ID: 97 (0x61) - Chain name: BNB Smart Chain Testnet - RPC: https://bsc-testnet-rpc.publicnode.com - Explorer: https://testnet.bscscan.com - Native currency: tBNB, 18 decimals - Faucet: Get test BNB: https://www.bnbchain.org/en/testnet-faucet (this URL is config.js faucetUrl) ### Mainnet (listed, not active) - Chain ID: 56 (0x38) - Chain name: BNB Smart Chain - RPC: https://bsc-rpc.publicnode.com - Explorer: https://bscscan.com - Native currency: BNB, 18 decimals ## Contracts ### Testnet (ACTIVE) - NFT (CIOWCitizens): 0xceB3ffB2D54A36849fE9b4e3ef6139A726D676B6 - Market (CIOWMarket): 0x9e92D0E8aAE67a46D2B4a0469fDD95F7D3b58C83 - Treasury: 0x3CF0A5A34B6f6D1E82b0AE6d7B2F4E3c0A7bA708 - .ai Agent ID registry (testnet mock): 0xa56Aeb981174e7559D682F6F9A667cd94cF6a94c - Registry RPC: https://bsc-testnet-rpc.publicnode.com - NFT deploy block: 139499904 - Market deploy block: 139499909 - MAX_SUPPLY: 4444 - MAX_PER_WALLET_CAP: 50 (default maxPerWallet = 5) ### Mainnet (not deployed) - .ai Agent ID registry (SPACE ID): 0x60B9262456dE011F7d15a54CdD8c48bAaa8B022F - NFT / Market / Treasury: not deployed (empty in config) - Registry RPC: https://bsc-rpc.publicnode.com ### Citizens NFT contract — ERC-2981 royalty - The Citizens contract implements ERC-2981 royaltyInfo(uint256,uint256) (selector 0x2a55205a). - royaltyBps is 0 by default; owner may set it via setRoyaltyBps up to 10% (MAX_ROYALTY_BPS). - Receiver = treasury. - This is only a signal for external marketplaces. ## Testnet only: get verified Applies ONLY to BNB Chain testnet (chain 97). The mock registry will NOT exist on mainnet. Do not attempt this on mainnet. - Testnet registry (MockAgentIdRegistry): agentRegistry 0xa56Aeb981174e7559D682F6F9A667cd94cF6a94c - Function: set(string handle) - Selector: 0x4ed3885e (verified with viem toFunctionSelector) - RPC: https://bsc-testnet-rpc.publicnode.com Cast example (developer, local testing only) moved to "Developer (local testing only)". Main flow uses wallet signing (raw tx JSON below). ### Handle encoding (testnet registry) The handle is the .ai name lowercased WITHOUT the ".ai" suffix (e.g. Alice.ai -> alice). Generate calldata with: cast calldata 'set(string)' alice -> 0x4ed3885e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005616c696365000000000000000000000000000000000000000000000000000000 (ABI layout: selector 0x4ed3885e, offset 0x20, length 5, UTF-8 bytes right-padded to 32 bytes.) ### Wallets without a private key (Binance Agentic Wallet, WalletConnect, any EIP-1193 wallet) The agent builds a raw transaction object that any wallet can sign and send (eth_sendTransaction), never asking for a private key. The from field is the connected wallet. Value is wei in hex. Paid mint (0.01 BNB default; always read priceFor(wallet) first and use that exact value): {"from":"0xYourWallet","to":"0xceB3ffB2D54A36849fE9b4e3ef6139A726D676B6","data":"0x1249c58b","value":"0x2386f26fc10000","chainId":"0x61"} Free-tier mint (priceFor(wallet) == 0): same with "value":"0x0". Testnet registry set("yourhandle") (testnet only): {"from":"0xYourWallet","to":"0xa56Aeb981174e7559D682F6F9A667cd94cF6a94c","data":"0x4ed3885e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a796f757268616e646c6500000000000000000000000000000000000000000000","value":"0x0","chainId":"0x61"} Note: set() and mint() must be sent from the SAME wallet. Note: value is wei in hex; 0x2386f26fc10000 = 10000000000000000 wei = 0.01 BNB. After sending, check verification with mintStatus(wallet). Never print or share private keys; use $PRIVATE_KEY placeholders only. ## Constants (from contract) - MAX_SUPPLY = 4444 - MAX_PUBLIC_PRICE = 1 ether - MAX_RENAME_FEE = 0.1 ether - MAX_PER_WALLET_CAP = 50 - BPS = 10000 - MAX_FEE_BPS = 1000 (market fee hard cap 10%) - MAX_ROYALTY_BPS = 1000 (royalty hard cap 10%, Citizens only) - Default publicPrice = 0.01 BNB = 10000000000000000 wei - Default renameFee = 0.0025 BNB = 2500000000000000 wei - Default market feeBps = 500 (5%) ## Mint rules - Single entry point: mint() (payable, no arguments). - Free mint: available once per verified .ai Agent ID and once per wallet. verifiedAgentId(wallet) returns (bool ok, uint256 agentId). ok is true only when the registry reports a non-empty handle for the wallet and ownerOf(agentId) == wallet. priceFor(wallet) returns 0 when a free mint is available now, else publicPrice. - Everyone else pays publicPrice (currently 0.01 BNB by default; always read priceFor(wallet) or /api/mint-config) in native BNB. - Exact payment only: msg.value must equal expected; otherwise WrongPayment(expected, sent). There is no refund path. - Per-wallet cap: mintedBy[wallet] increments on every mint (not balanceOf, so transfers do not reset quota). Default maxPerWallet = 5, read maxPerWallet() at runtime; admin may change it within 1..50. - Supply cap: totalMinted <= 4444 (MAX_SUPPLY). SoldOut when reached. - Mint uses _safeMint. Contract wallets (smart accounts, multisigs, contract-based agent wallets) MUST implement IERC721Receiver.onERC721Received or mint reverts with ERC721InvalidReceiver. EOAs need nothing extra. - Minting may be paused by the owner (paused()). When paused, mint() reverts (Pausable). ## mintStatus codes (from contract) mintStatus(address wallet) returns (uint8 code, bool freeTier, uint256 price, uint256 minted, uint256 maxPerWallet_, uint256 remaining_) - 0 OK: eligible to mint now (respect price). - 1 PAUSED: contract paused; no mint. - 2 SOLD_OUT: totalMinted >= MAX_SUPPLY. - 3 QUOTA_REACHED: wallet mintedBy >= maxPerWallet. - There is no code 4 in the current contract. (Older drafts mentioned 4; it is never returned now.) Additional views: - maxPerWallet() returns the current per-wallet cap. - mintedBy(address) returns mints performed by a wallet. - priceFor(address) returns exact wei to send. - publicPrice() returns the paid mint price. - totalMinted() / remaining() return supply counts. - verifiedAgentId(address) returns (ok, agentId). - paused() returns bool. ## Function ABIs and selectors (CIOWCitizens) Human-readable signatures, 4-byte selectors, and example calldata (example wallet 0x1111111111111111111111111111111111111111, tokenId 1, name "Alpha"). - mint() signature: mint() selector: 0x1249c58b calldata: 0x1249c58b payable: yes (value = price from mintStatus/priceFor) - mintStatus(address) signature: mintStatus(address) selector: 0x0deab810 calldata: 0x0deab8100000000000000000000000001111111111111111111111111111111111111111 - priceFor(address) signature: priceFor(address) selector: 0xf12a7add calldata: 0xf12a7add0000000000000000000000001111111111111111111111111111111111111111 - maxPerWallet() signature: maxPerWallet() selector: 0x453c2310 calldata: 0x453c2310 - mintedBy(address) signature: mintedBy(address) selector: 0x3cef28d2 calldata: 0x3cef28d20000000000000000000000001111111111111111111111111111111111111111 - verifiedAgentId(address) signature: verifiedAgentId(address) selector: 0x2764d5e1 calldata: 0x2764d5e10000000000000000000000001111111111111111111111111111111111111111 - publicPrice() signature: publicPrice() selector: 0xa945bf80 calldata: 0xa945bf80 - totalMinted() signature: totalMinted() selector: 0xa2309ff8 calldata: 0xa2309ff8 - remaining() signature: remaining() selector: 0x55234ec0 calldata: 0x55234ec0 - paused() signature: paused() selector: 0x5c975abb calldata: 0x5c975abb - treasury() signature: treasury() selector: 0x61d027b3 calldata: 0x61d027b3 - rename(uint256,string) signature: rename(uint256,string) selector: 0x3ec2d836 calldata (tokenId 1, "Alpha"): 0x3ec2d836000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000005416c706861000000000000000000000000000000000000000000000000000000 payable: yes (0 for first rename, renameFee for later) - isNameAvailable(string) signature: isNameAvailable(string) selector: 0x8cf44d21 calldata ("Alpha"): 0x8cf44d2100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005416c706861000000000000000000000000000000000000000000000000000000 - displayName(uint256) signature: displayName(uint256) selector: 0x5814595f calldata (1): 0x5814595f0000000000000000000000000000000000000000000000000000000000000001 - hasRenamed(uint256) signature: hasRenamed(uint256) selector: 0x195eec59 calldata (1): 0x195eec590000000000000000000000000000000000000000000000000000000000000001 - names(uint256) signature: names(uint256) selector: 0x4622ab03 calldata (1): 0x4622ab030000000000000000000000000000000000000000000000000000000000000001 - transferCount(uint256) signature: transferCount(uint256) selector: 0xec02ef57 calldata (1): 0xec02ef570000000000000000000000000000000000000000000000000000000000000001 - withdrawPending() signature: withdrawPending() selector: 0x7edbceb1 calldata: 0x7edbceb1 ERC-721 (standard) used: - ownerOf(uint256) selector 0x6352211e - balanceOf(address) selector 0x70a08231 - approve(address,uint256) selector 0x095ea7b3 - setApprovalForAll(address,bool) selector 0xa22cb465 - getApproved(uint256) selector 0x081812fc - isApprovedForAll(address,address) selector 0xe985e9c5 - tokenURI(uint256) selector 0xc87b56dd ERC-2981 (royalty): - royaltyInfo(uint256,uint256) selector 0x2a55205a - setRoyaltyBps(uint96) (owner) caps at MAX_ROYALTY_BPS (1000 = 10%) ## Name / rename rules (from contract) - Length: 3 to 24 bytes (UTF-8 byte length). Otherwise InvalidNameLength. - Charset: digits 0-9, letters A-Z / a-z, and space (0x20) only. Otherwise InvalidNameChars. - Spaces: no leading space, no trailing space, no consecutive spaces. Otherwise InvalidNameSpaces. - Uniqueness: case-insensitive. The contract lowercases the name and hashes it (keccak256 of lowercased bytes). Two names that differ only by case collide. nameHashToToken[hash] stores tokenId + 1 (0 means free). - Reserved: reservedNames[hash] names cannot be used by anyone (NameIsReserved). - Ownership: only the current owner of tokenId may rename (NotOwner otherwise). - Fees: - First rename is free: hasRenamed[tokenId] == false => msg.value must be 0. (WrongPayment(0, sent) if sent != 0.) - Later renames cost renameFee (default 0.0025 BNB): msg.value must equal renameFee. (WrongPayment(renameFee, sent) if mismatch.) - renameFee is admin-configurable up to MAX_RENAME_FEE (0.1 ether). - Rename is allowed only when not paused (rename has whenNotPaused). - On success emits NameChanged(tokenId, oldName, newName) and MetadataUpdate(tokenId). - Rename does not change tokenId or owner. ## Function ABIs and selectors (CIOWMarket) - list(uint256,uint256) signature: list(uint256,uint256) selector: 0x50fd7367 calldata (tokenId 1, price 0.1 BNB = 100000000000000000 wei): 0x50fd73670000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000016345785d8a0000 Requires: price > 0 and <= type(uint96).max; caller owns tokenId; market is approved for tokenId (single approve or setApprovalForAll). Reverts: ZeroPrice, PriceTooHigh, NotOwner, NotApproved. Paused blocks listing. - updatePrice(uint256,uint256) signature: updatePrice(uint256,uint256) selector: 0x82367b2d calldata (tokenId 1, 0.2 BNB): 0x82367b2d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000002c68af0bb140000 Requires: listing exists; caller is seller; listing still valid (StaleListing otherwise). Reverts: NotListed, NotSeller, StaleListing, ZeroPrice, PriceTooHigh. - cancel(uint256) signature: cancel(uint256) selector: 0x40e58ee5 calldata (tokenId 1): 0x40e58ee50000000000000000000000000000000000000000000000000000000000000001 Seller may cancel any time. Anyone may cancel a stale listing. Reverts: NotListed, NotSeller (if caller is not seller and listing is still valid). - buy(uint256,uint256) signature: buy(uint256,uint256) selector: 0xd6febde8 calldata (tokenId 1, expectedPrice 0.1 BNB): 0xd6febde80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000016345785d8a0000 payable: yes (value = listing price). expectedPrice must equal current listing price (PriceChanged(current) otherwise). msg.value must equal price (WrongPayment). Reverts: NotListed, PriceChanged, WrongPayment, SelfBuy, StaleListing. On success: NFT transferred seller->buyer (safeTransferFrom), seller receives price - fee, fee forwarded to treasury, listing deleted, Sold event. - isValid(uint256) signature: isValid(uint256) selector: 0xf577a500 calldata (1): 0xf577a5000000000000000000000000000000000000000000000000000000000000000001 Returns true only while seller still owns tokenId, transferCount is unchanged since listing, and market is approved. A token that leaves the seller and returns does NOT revive the old listing. - quote(uint256) signature: quote(uint256) selector: 0xed1bd76c calldata (0.1 BNB): 0xed1bd76c000000000000000000000000000000000000000000000000016345785d8a0000 Returns (fee, sellerReceives) at current feeBps. - listings(uint256) signature: listings(uint256) selector: 0xde74e57b calldata (1): 0xde74e57b0000000000000000000000000000000000000000000000000000000000000001 Returns (seller, price, transferCount, feeBps) struct fields. - feeBps() signature: feeBps() selector: 0x24a9d853 calldata: 0x24a9d853 - nft() signature: nft() selector: 0x47ccca02 calldata: 0x47ccca02 - treasury() signature: treasury() selector: 0x61d027b3 calldata: 0x61d027b3 - withdrawPending() signature: withdrawPending() selector: 0x7edbceb1 calldata: 0x7edbceb1 ## Market rules - 5% fee to treasury (default feeBps = 500). Verify on-chain via feeBps(); fee is locked per listing at list time (listing.feeBps). Hard cap 1000 bps (10%). Seller receives price - fee (fee rounds down in seller's favor). - The CIOW Market charges a 5% fee to the treasury and does NOT pay ERC-2981 royalties (it never calls royaltyInfo). - Fixed price in native BNB. No auctions. - Non-custodial: NFT stays with seller until sale. Listing requires approval. - A listing dies permanently once the token moves (transferCount changes), even if it returns to the seller. Seller must list again. Check isValid(tokenId) before buying. - Self-buy is not allowed (SelfBuy). - Buyer passes expectedPrice as slippage protection. - Price cap: price <= type(uint96).max (~7.92e28 wei). - Failed pushes (e.g. contract sellers rejecting BNB) credit pending[recipient]; claim via withdrawPending(). ## Step-by-step for agents Mint: 1. Read config: active network = testnet; use NFT address and RPC from config. 2. Call mintStatus(wallet) -> (code, freeTier, price, minted, maxPerWallet, remaining). Also acceptable: priceFor(wallet) + maxPerWallet() + mintedBy(wallet). 3. If code != 0: stop and report (1 paused, 2 sold out, 3 quota reached). 4. Testnet only (chain 97): if mintStatus shows the wallet is not verified for the free tier and you want it, call the mock registry set(string handle) (selector 0x4ed3885e) on agentRegistry 0xa56Aeb981174e7559D682F6F9A667cd94cF6a94c FROM THE SAME WALLET that will mint, wait for the receipt, then re-read mintStatus(wallet). This step does not exist on mainnet (chain 56): there the real .ai Agent ID registry is used and cannot be set by the agent. 5. Send transaction: to = NFT address, data = 0x1249c58b (mint selector), value = price (wei). Exact value required. 6. Wait for receipt. Confirm success via Transfer event (topic 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) and/or /api/citizens (new citizen appears). 7. If wallet is a contract, ensure IERC721Receiver support. Rename: 1. Verify ownership (ownerOf(tokenId) == wallet). 2. Validate name locally (length 3-24, charset, spaces). 3. Check isNameAvailable(name). 4. Determine fee: 0 if hasRenamed(tokenId) == false else renameFee (read renameFee). 5. Send: to = NFT address, data = rename selector + tokenId + name, value = fee. 6. Confirm via NameChanged event (topic 0xd03378e710a4f526d1030d6dd70e5c0999dcaf843ca8a83aadcb0946a251de8e) and /api/meta/:id (name updates). Market list: 1. Approve market: setApprovalForAll(market, true) (or approve(market, tokenId)). 2. Call isValid? listing none yet. Ensure owner. 3. Send list(tokenId, price) with value 0. price in wei, >0, <= uint96.max. 4. Confirm via Listed event and /api/market. Market buy: 1. GET /api/market (or read listings(tokenId) + isValid(tokenId)). 2. Verify isValid(tokenId) == true. 3. Send buy(tokenId, expectedPrice) with value = price (exact). 4. Confirm via Sold event and ownership change (/api/citizens or ownerOf). Market cancel: 1. Send cancel(tokenId) (value 0). Anyone may cancel stale listings. 2. Confirm via Cancelled event and /api/market. ## Examples cast (Foundry): all `cast send ... --private-key` examples moved to "Developer (local testing only)" (below). Main flow uses wallet signing (raw tx). Preflight (no private key) remains usable directly: ``` NFT=0xceB3ffB2D54A36849fE9b4e3ef6139A726D676B6 RPC=https://bsc-testnet-rpc.publicnode.com cast call $NFT "mintStatus(address)(uint8,bool,uint256,uint256,uint256,uint256)" $WALLET --rpc-url $RPC ``` viem (TypeScript): ```ts import { createWalletClient, createPublicClient, http, parseEther } from "viem"; import { bscTestnet } from "viem/chains"; import { privateKeyToAccount } from "viem/accounts"; const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const publicClient = createPublicClient({ chain: bscTestnet, transport: http("https://bsc-testnet-rpc.publicnode.com") }); const walletClient = createWalletClient({ account, chain: bscTestnet, transport: http("https://bsc-testnet-rpc.publicnode.com") }); const NFT = "0xceB3ffB2D54A36849fE9b4e3ef6139A726D676B6" as const; const abi = [ /* mintStatus, mint, rename, ... */ ]; const [code, freeTier, price, minted, maxPerWallet, remaining] = await publicClient.readContract({ address: NFT, abi, functionName: "mintStatus", args: [account.address], }); if (code !== 0) throw new Error(`mintStatus code ${code}`); const hash = await walletClient.writeContract({ address: NFT, abi, functionName: "mint", value: price, }); const receipt = await publicClient.waitForTransactionReceipt({ hash }); // confirm Transfer event topic 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef ``` ethers v6: ```ts import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider("https://bsc-testnet-rpc.publicnode.com"); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider); const NFT = "0xceB3ffB2D54A36849fE9b4e3ef6139A726D676B6"; const nft = new ethers.Contract(NFT, abi, wallet); const [code, freeTier, price, minted, maxPerWallet, remaining] = await nft.mintStatus(wallet.address); if (code !== 0) throw new Error(`mintStatus code ${code}`); const tx = await nft.mint({ value: price }); const receipt = await tx.wait(); ``` ## API endpoints All return JSON. Base URL is the site origin. - GET /api/citizens Response: { "mode": "live", "updatedAt": , "citizens": [ { "id": , "owner": "0x..." (lowercase), "name": "", "displayName": "", "mintedAt": 0 } ] } Citizens sorted by id ascending. owner may be empty string if unknown. - GET /api/market Response: { "mode": "live", "feeBps": , "treasury": "0x...", "listings": [ { "tokenId": , "seller": "0x...", "price": "", "feeBps": , "name": "", "valid": true } ], "sales": [ { "tokenId": , "seller": "0x...", "buyer": "0x...", "price": "", "fee": "", "txHash": "0x...", "blockNumber": } ], "latestBlock": } Listings sorted by tokenId; sales newest first. - GET /api/meta/:id (id must be a canonical integer 1..4444, no leading zeros) Unminted or out-of-range ids return 404 JSON {"error":"Not found"}. Unrevealed (default) response: { "name": "Citizen #1", "description": "A CIOW Citizen identity NFT.", "image": "https://ciow.world/og.png", "external_url": "https://ciow.world/?citizen=1", "background_color": "87CEEB", "attributes": [ { "trait_type": "Background", "value": "Unrevealed" }, { "trait_type": "Skin", "value": "Unrevealed" }, { "trait_type": "Outfit", "value": "Unrevealed" }, { "trait_type": "Eyes", "value": "Unrevealed" }, { "trait_type": "Hair Hat", "value": "Unrevealed" }, { "trait_type": "Accessory", "value": "Unrevealed" }, { "trait_type": "Rarity", "value": "Unrevealed" }, { "trait_type": "Status", "value": "Placeholder" } ] } Revealed (REVEALED=true with METADATA_BASE_URI) returns metadata from that base: image is IMAGE_BASE_URI + id + ".png" (or SITE_URL/og.png fallback), plus edition/dna if present. 404 for out-of-range or unminted id. - GET /api/mint-config Mint configuration: network, chain, contracts, mint price/maxPerWallet, selectors, ABI. Link: /api/mint-config ## Common errors and fixes CIOWCitizens custom errors: - SoldOut -> all 4444 minted. No new mints; use market. - MaxPerWalletReached -> wallet reached maxPerWallet. Read mintedBy/maxPerWallet; cannot mint more on this wallet. - InvalidMaxPerWallet -> admin only; newMax must be 1..50. - WrongPayment(expected, sent) -> send exact expected wei (priceFor/mintStatus price). No refund path. - ZeroAddress -> admin setter got zero address. - NothingPending -> withdrawPending called with zero pending balance. - TransferFailed -> native transfer failed; funds credited to pending[recipient]. - NotOwner -> only token owner may rename (and only market listing owner for market). - InvalidNameLength -> name must be 3..24 bytes. - InvalidNameChars -> allowed: 0-9, A-Z, a-z, space. - InvalidNameSpaces -> no leading/trailing/consecutive spaces. - NameTaken -> name already in use (case-insensitive). Pick another. - NameIsReserved -> name is reserved by admin. Pick another. - RenameFeeTooHigh -> admin only; fee <= 0.1 ether. - PublicPriceTooHigh -> admin only; price <= 1 ether. CIOWMarket custom errors: - NotOwner -> caller is not token owner (list) or not listing seller. - NotApproved -> market not approved for tokenId. Approve first. - ZeroPrice / PriceTooHigh -> price must be 1..type(uint96).max. - NotListed -> no listing for tokenId. - NotSeller -> caller is not the listing seller (and listing still valid). - StaleListing -> token moved since listing (transferCount changed) or not owned/approved. Seller must list again; buyer should refresh. - SelfBuy -> cannot buy your own listing. - WrongPayment(expected, sent) -> send exact listing price. - PriceChanged(current) -> expectedPrice mismatch; retry with current price. - FeeAboveCap -> admin only; bps <= 1000. - ZeroAddress -> admin setter got zero address. - NothingPending / TransferFailed -> see above. Other: - ERC721InvalidReceiver -> contract wallet must implement IERC721Receiver.onERC721Received. - Pausable: mint/list/buy/rename revert while paused. - verifiedAgentId never reverts; on registry failure returns (false, 0), so treat as no free tier (paid mint only). - ReentrancyGuard protects mint/buy/rename/withdrawPending. ## Paused note The Citizens contract is deployed paused (constructor calls _pause; the owner opens minting explicitly). Before any mint, first call paused() (selector 0x5c975abb, OpenZeppelin Pausable, returns bool) or GET /api/mint-config -> mintOpen. If paused is true or mintOpen is false, minting is not open yet: stop, send no transaction, retry later, and check X @ciowworld (https://x.com/ciowworld) for updates. mintStatus code 1 means the same (PAUSED). The same applies to market and rename actions while paused. ## Provenance provenance = sha256( concat( hex_lowercase(sha256(image_bytes_of_token_1)), ..., hex_lowercase(sha256(image_bytes_of_token_4444)) ) ) The concatenation is of the 64-char lowercase hex strings (ASCII, no separators) for token IDs 1..4444 in ascending order; the result is written as a 0x-less 64-char lowercase hex string. It is not stored on-chain before reveal. Example shell: for i in $(seq 1 4444); do sha256sum images/$i.png | cut -c1-64; done | tr -d '\n' | sha256sum A provenance hash for the collection will be published here and on X @ciowworld before reveal. Provenance hash: will be published before reveal. ## Safety - Never include or request private keys. Use $PRIVATE_KEY placeholders in examples. - Always preflight with mintStatus/priceFor before sending value. - Send exact value; contracts do not refund. - Verify on-chain feeBps and prices; do not hardcode beyond defaults. - Check isValid(tokenId) before buying a market listing. - Do not promise rewards, yield, profit, or fee sharing to holders. ## Developer (local testing only) WARNING: never paste private keys into chats or agents. Use local environment variables (e.g. $PRIVATE_KEY) only on your own machine. Main flow uses wallet signing (raw tx). Testnet registry set (developer): ``` cast send 0xa56Aeb981174e7559D682F6F9A667cd94cF6a94c "set(string)" "yourhandle" --rpc-url https://bsc-testnet-rpc.publicnode.com --private-key $PRIVATE_KEY ``` Foundry (developer): ``` NFT=0xceB3ffB2D54A36849fE9b4e3ef6139A726D676B6 MKT=0x9e92D0E8aAE67a46D2B4a0469fDD95F7D3b58C83 RPC=https://bsc-testnet-rpc.publicnode.com # mint (free) cast send $NFT "mint()" --value 0 --rpc-url $RPC --private-key $PRIVATE_KEY # mint (paid) cast send $NFT "mint()" --value 10000000000000000wei --rpc-url $RPC --private-key $PRIVATE_KEY # rename (free first) cast send $NFT "rename(uint256,string)" 1 "Alpha" --value 0 --rpc-url $RPC --private-key $PRIVATE_KEY # approve + list at 0.1 BNB cast send $NFT "setApprovalForAll(address,bool)" $MKT true --rpc-url $RPC --private-key $PRIVATE_KEY cast send $MKT "list(uint256,uint256)" 1 100000000000000000 --rpc-url $RPC --private-key $PRIVATE_KEY # buy at 0.1 BNB cast send $MKT "buy(uint256,uint256)" 1 100000000000000000 --value 100000000000000000wei --rpc-url $RPC --private-key $PRIVATE_KEY # cancel cast send $MKT "cancel(uint256)" 1 --rpc-url $RPC --private-key $PRIVATE_KEY # claim pending funds cast send $NFT "withdrawPending()" --rpc-url $RPC --private-key $PRIVATE_KEY cast send $MKT "withdrawPending()" --rpc-url $RPC --private-key $PRIVATE_KEY ``` ## Links - Docs: /docs - Agent guide: /llms.txt - Agent skill: /skill.md - Citizens API: /api/citizens - Market API: /api/market - Metadata API: /api/meta/:id - Mint config API: /api/mint-config - X: https://x.com/ciowworld