← Docs · Collection

Proof-of-work verification

Every Proof of Architect token is produced by grinding a nonce until its keccak-256 hash has enough leading zero bits. The winning hash becomes the token seed, so the work is fully verifiable after the fact. This page specifies the exact math, the difficulty formula and a real worked example. Reference contract (Arc): 0x2F7cE1e4A175b1A16e4f151fA5B862ea6b9F3C8b.

1. Preimage and hash

The value being hashed is a packed 104-byte string — three integers and two addresses, in this exact order, with no padding between fields:

work = keccak256(
    abi.encodePacked(
        uint256 chainId,   // 32 bytes, big-endian
        address contract,  // 20 bytes
        address miner,     // 20 bytes
        uint256 nonce      // 32 bytes, big-endian
    )
)

// 32 + 20 + 20 + 32 = 104 bytes
// 104 < 136 = the Keccak-256 rate block, so one hash pass

2. Validity rule

A nonce is valid when the number of leading zero bits of work is at least the required difficulty for that wallet:

valid  <=>  leadingZeroBits(work) >= requiredBits(miner)

// leadingZeroBits counts zero bits from the most-significant bit
// of the 32-byte hash; leadingZeroBits(0) = 256.
// Expected number of attempts ~= 2^requiredBits.

Leading zero bits (not bytes, not a numeric target) means a hash starting with 0x00 has at least 8 valid bits, 0x0000 at least 16, and so on. A difficulty of 30 bits requires roughly one billion hashes on average.

3. Difficulty formula

requiredBits(miner) =
    max( baseBits,
         baseBits + 2 * epochIndex + loadAdjust + streakBits
                  - discountBits(miner) )     // capped at 250
TermMeaningUnits
baseBitsWave-1 base difficulty of the deployment (30 bits on the current deployment).bits
2 * epochIndex+2 bits per wave; each wave is about 4× harder. The 1-based wave reported on-chain is epochIndex + 1 (epochs of 1,000 paid mints).bits
loadAdjustLoad regulator, 0..64 bits, nudged every 25 mints toward a target pace of 30 s/mint (a ±20% dead zone between tightening and loosening).bits
streakBitsPer-wallet streak: +2 bits for each extra mint from the same wallet while inside its cooldown (cooldown = 60 s × wave). Resets once the cooldown has elapsed.bits
discountBits(miner)PoW discount granted by the staking vault (up to 6 bits, floored at baseBits) — see below.bits

Staking discount (current core)

A holder can lock an Architector in the staking vault to lower personal difficulty. The discount is the maximum over the wallet active stakes, capped at 6 bits, and the result is floored at baseBits:

TierLockDiscount
0 / 1flexible / 7 days2 bits
2 / 330 / 90 days4 bits
4 / 5180 / 365 days6 bits

The current deployment has the staking vault registered as a module, so the discount applies live: lock an Architector in the staking vault and your required bits are reduced by up to 6, floored at baseBits.

4. Worked example (real nonce)

The first token ever minted in the collection was mined on the initial v2 instance. Its nonce and winning hash are on-chain and have been cross-checked byte-for-byte by the browser miner, the GPU miner and the MCP server. The preimage layout and the validity rule are unchanged in the current v3 core; only the bound contract address differs.

chainId5042002
contract (v2 instance)0xc7D2C2cC9291485ec8B727333B6a1478Dd66c3D5
miner0x1c35c02eac5545205BE510F10024ec9cD973E5Dd
nonce403415
work (seedOf(1))0x00000dcc59e937a8228cb92d216aeb3705bd3c4d1dff61fa648d1d603777f5bf
leadingZeroBits(work)20
requiredBits(miner)20
validtrue (20 >= 20)

Why 20 bits: the hash begins 0x00 00 0d — two zero bytes (16 bits) plus 0x0d = 00001101 (4 more leading zeros) = 20. That miner had minted zero tokens at the time, so requiredBits was exactly the v2 base difficulty (20 bits), and the nonce was accepted. The winning hash is stored on-chain as seedOf(1) and nonceOf(1) = 403415.

5. How to verify

With the MCP tool verify_nonce

The remote MCP server exposes verify_nonce(miner, nonce). It recomputes work locally (it does not trust the RPC for the PoW verdict) and compares the leading-zero-bit count against requiredBits(miner) read live from the contract. miner is a 0x-prefixed address; nonce is a decimal uint256 string.

tool      : verify_nonce
arguments : { "miner": "0x…", "nonce": "403415" }
returns   : {
  "miner": "0x…",
  "nonce": "403415",
  "work": "0x…",
  "leadingZeroBits": 20,
  "requiredBits": 24,
  "valid": false,
  "note": "valid is checked against the CURRENT difficulty…"
}

Companion tools: required_bits(miner) for the current difficulty, and get_token(tokenId) for a token owner, seed and nonce. See /docs/agent-access for the endpoint and client configuration.

With the site API and on-chain reads

The metadata endpoint, GET /api/meta/{id}, serves the derived traits and rarity of a token, but not the raw seed. To verify independently, read the three values below from the contract (or via the MCP get_token tool) and recompute locally:

read  seedOf(id)    -> bytes32   // the winning work, stored on mint
read  nonceOf(id)   -> uint256
read  ownerOf(id)   -> address   // the miner at mint time

recompute  work = keccak256(chainId, contract, owner, nonce)
assert     work == seedOf(id)     // byte-for-byte

Example metadata read:

curl -sS https://proofofarchitect.builders/api/meta/1

A quick free difficulty probe: call eth_estimateGas against mint() with a fresh nonce. It reverts with BelowFloor(uint8 got, uint8 need) (selector 0xfcf93064) and reveals the current difficulty without a transaction.

6. Why rarity is deterministic

There is no randomness and no oracle. The token seed is the winning work hash itself (seedOf(tokenId) = work, or the claim hash for a free claim). Arc PREVRANDAO is always zero, so nothing else feeds the art.

The Architector traits are a pure function of that seed: each slot is drawn by deterministic weighted rejection sampling, consuming keccak256(seed ‖ uint8 slotIndex ‖ uint16 counter) read as sixteen big-endian uint16 words. Rarity is then the information content of the resulting trait set: -Σ log2(count(value) / totalTokens), mapped to a tier (Standard, Notable, Rare, Epic, Mythic).

Consequences: the same seed always yields the same card and the same rarity, and anyone can recompute both from the on-chain seed. Changing a token seed would require a different proof of work — the art is a commitment to the hash. The score and tier are served at GET /api/meta/{id} under rarity, and by the MCP tool verify_rarity.

Related: Agent access · Stats dataset · Docs index · GitBook