Skip to content

07 — Annex: On-Chain Estate

The contract layer: every live contract, its upgradeability, the chains it is deployed on, the roles that govern it, and the external protocol contracts the platform calls but does not own. This is the other half of 02.

Deployment reality differs sharply from code completeness

The single most useful thing to know before reading the tables: the estate is not uniform across chains, and the code compiling is not evidence of deployment.

Chain What is actually there
137 Polygon The whole estate
63 Mordor Nearly all of it, minus oracle adapters and callsigns
80002 Amoy The wager/membership core only
61 ETC Custody + account stack only — no FeeRouter
1 / 10 / 8453 / 42161 Routers + FeeRouter + account stack only
Sepolia, Hoodi Present in NETWORKS with zero contracts

Complete-but-undeployed: FundingPoolFactory/FundingPool exist nowhere on chain, and StakingRouter is undeployed even though its two fee services are already registered on the FeeRouter. CallsignRegistry is Polygon-only. StandardDAOFactory can never ship on Mordor — pre-Cancun EVM, and OZ Governor emits MCOPY.

Two traps for anyone reading addresses

Nonce-derived address reuse across chains. Ethereum's liquidityRouter proxy is byte-identical to the BridgeRouter proxy on chains 10, 8453 and 42161. A label is only valid together with its chain id; carrying one across a chain boundary produces a confident wrong answer. Base's Uniswap and Chainalysis addresses also genuinely differ from the other four mainnets.

The wagerRegistryIntents facet is recorded on 137 and 63 but not on Amoy 80002 — so the testnet membership chain has no gasless twin facet. Self-submit is unaffected, which is exactly why this could go unnoticed.

Authority: what is settled and what is not

The historical deployer 0x52502d04…F6e1 is the compromised key. Ten recorded Mordor contracts were migrated to the current 2-of-3 admin Safe 0xcf76db7a…0447; roughly 60 sole-held roles and 62 Ownable owners across 23 unrecorded contracts were deliberately written off rather than migrated. Two live gaps remain open:

  • DEFAULT_ADMIN_ROLE on the Bridge and Liquidity routers is still a single hot key (issue #966) — treat those routers accordingly.
  • The recorded treasury on chains 1, 10, 8453 and 42161 is still the superseded Safe 0x8cc564E3…c3fa.

Evidence-based inventory of the smart-contract layer and its chain topology. Repo root: /home/user/prediction-dao-research. Read-only pass; nothing modified.

Rules observed while writing this: no address appears here that is not quoted from a repo file, and every row cites path:line. deployments/** is the source of truth for addresses (per CLAUDE.md repository map); frontend/src/config/contracts.js is the client copy, synced by npm run sync:frontend-contracts, and the two diverge in places that are flagged below.


0. Executive shape

Layer What it is Where
Escrow / wagers WagerRegistry UUPS proxy + WagerRegistryIntents delegatecall facet over one WagerRegistryCore layout contracts/wagers/
Access / identity MembershipManager (UUPS), MembershipVoucher (immutable ERC-721), VoucherBatchMinter (immutable), SanctionsGuard (immutable singleton), CallsignRegistry (UUPS) contracts/access/, contracts/naming/
Group escrow WagerPoolFactory (UUPS) → WagerPool ERC-1167 clones; FundingPoolFactory (UUPS) → FundingPool clones contracts/pools/
Fees FeeRouter (UUPS) — single on-chain fee source of truth contracts/fees/
DeFi routers BridgeRouter, LiquidityRouter, StakingRouter (all UUPS, transient custody only) contracts/bridge/, contracts/liquidity/, contracts/staking/
Apps catalog MiniAppRegistry (UUPS) contracts/apps/
Accounts (4337) CoinbaseSmartWallet + CoinbaseSmartWalletFactory (vendored) + FairWinsVerifyingPaymaster contracts/account/
Custody (Safe) SafePolicyGuard (v1), SafePolicyGuardV2, PolicyGuardSetup, SafeProposalHub — all non-upgradeable singletons contracts/custody/
Oracles Polymarket / Chainlink data-feed / Chainlink Functions / UMA OOv3 adapters (all Ownable, immutable) contracts/oracles/
Tokens / DAO TokenFactory (UUPS) + clone templates, ExternalDAORegistry (UUPS), StandardDAOFactory (UUPS) contracts/tokens/, contracts/clearpath/
Privacy / misc KeyRegistry, BackupPointerRegistry (tiny immutable registries) contracts/privacy/
Reference-only contracts-archive/ — v1 estate incl. FriendGroupMarketFactory; never import or deploy contracts-archive/markets/FriendGroupMarketFactory.sol

contracts/mocks/** and contracts/test/** are test-only (fuzz harnesses, mock USDC, mock Safe, mock Uniswap, MockERC4626Vault, etc.) and are excluded from the live inventory.


1. Contract inventory

Columns: Upgradeability | deployments key(s) | Chains | Roles | Holds/escrows | Facet/pair | Evidence

1.1 Wagers (specs 025 / 035 / 036)

Contract Purpose Upgradeability deployments key(s) Chains deployed Key roles Holds / escrows Facet / pair Evidence
WagerRegistry P2P wager escrow: named creator/opponent, ERC-20 stake, resolution Either/Creator/Opponent/ThirdParty/Polymarket/+oracle UUPS proxy at a stable address (UUPSManaged) wagerRegistry / wagerRegistryImpl 137, 63, 80002 DEFAULT_ADMIN_ROLE (config: membership mgr, adapter, token allowlist), GUARDIAN_ROLE (pause), ACCOUNT_MODERATOR_ROLE (freeze accounts), UPGRADER_ROLE (impl and the intents facet) Yes — escrows both parties' ERC-20 stakes; safeTransferFrom(from, address(this), amount) Main facet; delegatecalls unknown selectors to WagerRegistryIntents contracts/wagers/WagerRegistry.sol:18-35, roles at :20-24; escrow pull contracts/wagers/WagerRegistryCore.sol:246; payouts :548-601; fallback contracts/wagers/WagerRegistry.sol:93-110
WagerRegistryIntents The gasless/signer-attributed twin facet (…WithSig / …WithAuthorization) plus relocated cold paths (batchExpireOpen, autoResolveFrom*) — exists because the main impl sits against the 24 KB limit Separate implementation, never initialized; reached by delegatecall from the proxy wagerRegistryIntents 137, 63 onlyabsent on Amoy 80002 inherits the same roles (shared storage) executes in the proxy's storage, so the proxy holds the escrow Pairs with WagerRegistry; both inherit WagerRegistryCore contracts/wagers/WagerRegistryIntents.sol:10-32; intents typehashes :34-68; presence per chain: deployments/polygon-chain137-v2.json / mordor-chain63-v2.json have wagerRegistryIntents, deployments/amoy-chain80002-v2.json does not
WagerRegistryCore The single storage-layout + internal action bodies for the pair. Abstract — never deployed on its own abstract base (UUPSManaged, ReentrancyGuardUpgradeable, PausableUpgradeable, EIP712Upgradeable) — (no key) declares WAGER_PARTICIPANT_ROLE, GUARDIAN_ROLE, ACCOUNT_MODERATOR_ROLE the shared parent of both facets contracts/wagers/WagerRegistryCore.sol:20-45, storage block :53-80; bounds MAX_ACCEPT_WINDOW = 30 days / MAX_RESOLVE_WINDOW = 180 days :46-47

EIP-712 domain: "FairWins WagerRegistry" / "1"contracts/wagers/WagerRegistry.sol:63. Open-challenge accept typehash OpenAccept(uint256 wagerId,address taker)contracts/wagers/WagerRegistryCore.sol:52.

1.2 Membership / access (specs 007 / 026 / 027)

Contract Purpose Upgradeability deployments key(s) Chains Key roles Holds Pair Evidence
MembershipManager Tiered, time-bound memberships per bytes32 role; USDC-priced; only paid role is WAGER_PARTICIPANT_ROLE UUPS proxy (UUPSManaged + SignerIntentBase) membershipManager / membershipManagerImpl 137, 63, 80002 DEFAULT_ADMIN_ROLE (treasury, tier config, role admin), ROLE_MANAGER_ROLE (grant/revoke out-of-band), plus an authorizedCallers map for the WagerRegistry hook Holds accruedFees (undrawn USDC) + takes payment to treasury Burns MembershipVoucher on redeem contracts/access/MembershipManager.sol:13-20, ROLE_MANAGER_ROLE :26, state :43-62
MembershipVoucher Transferable ERC-721 bearer claim on a (role,tier) membership; confers nothing while held Deliberately immutable ("a tradable bearer asset's rules must not change after purchase") membershipVoucher 137, 63, 80002 AccessControl; membershipManager is immutable and the sole redemption-burner; ERC-2981 royalty ≤ MAX_ROYALTY_BPS = 500 Pays USDC straight to treasury at mint — holds no funds at rest Pairs with MembershipManager contracts/access/MembershipVoucher.sol:23-37
VoucherBatchMinter "Buy N / gift" helper — batches voucher mints + gift transfer in one tx Immutable, no admin, no withdrawal path voucherBatchMinter 137, 63, 80002 none (stateless, custody-free) Holds nothing at rest; MAX_QUANTITY = 50 Pairs with MembershipVoucher contracts/access/VoucherBatchMinter.sol:23-45
SanctionsGuard Non-bypassable on-chain screening: Chainalysis oracle + operator deny-list; fail-closed (configured-but-unreachable oracle ⇒ everyone not-allowed) Immutable singleton (AccessControl, constructor-configured) sanctionsGuard 137, 63, 80002 SANCTIONS_ADMIN_ROLE (deny-list), DEFAULT_ADMIN_ROLE (set oracle) — both granted to the air-gapped floppy-keystore admin at deploy Nothing consulted read-only by WagerRegistry, MembershipManager, pool factories, routers, MiniAppRegistry, CallsignRegistry contracts/access/SanctionsGuard.sol:8-36; isAllowed fail-closed :41-48

1.3 Group pools

Contract Purpose Upgradeability deployments key(s) Chains Key roles Holds Pair Evidence
WagerPoolFactory (spec 034) Authority + registry for group wager pools; screens creator, assigns a 4-word BIP-39 tuple, clones pools, network-scoped registry, serves clones' compliance callbacks (screen/requireMembership) UUPS proxy wagerPoolFactory / wagerPoolFactoryImpl / poolImpl 137, 63 POOL_PARTICIPANT_ROLE (membership role it requires, not a role it grants); DEFAULT_ADMIN_ROLE + UPGRADER_ROLE from UUPSManaged No escrow itself — the clones hold it Clones WagerPool; EIP-712 domain "FairWins WagerPoolFactory"/"1" contracts/pools/WagerPoolFactory.sol:22-52; POOL_PARTICIPANT_ROLE :39; caps MAX_MEMBERS_CAP = 1000, 30 d/180 d :41-48
WagerPool Immutable per-pool escrow: members join/approve/claim by public wallet address (no Semaphore/ZK); resolves by creator-proposed payout matrix keyed by winner address, approved to a fraction-of-joined threshold ERC-1167 clone of an immutable template poolImpl (template) 137, 63 (clones created on-chain) none — authority is the roster + factory hooks Yes — escrows USDC buy-ins; only exits are claim (Resolved) and refund/cancel Cloned by WagerPoolFactory; every actor action has a …WithSig twin + joinWithAuthorization (EIP-3009) baked into the template contracts/pools/WagerPool.sol:34-56
FundingPoolFactory (spec 103) Sibling of the wager factory with the wager removed; own 4-word namespace, own registry UUPS proxy fundingPoolFactory / fundingPoolFactoryImpl / fundingPoolImpl NONE — undeployed on every chain POOL_PARTICIPANT_ROLE (same role as wager pools, so one tier config covers both) Clones FundingPool; EIP-712 domain "FairWins FundingPoolFactory"/"1" contracts/pools/FundingPoolFactory.sol:21-52; key registered in the storage gate scripts/deploy/check-storage-layout.js:54; deploy script scripts/deploy/deploy-funding-pool-factory.js; empty in frontend/src/config/contracts.js (Mordor fundingPoolFactory: '' ~L53, Polygon likewise) and absent from every deployments/*-v2.json
FundingPool Variable contributions toward a public purpose + goal; organizer closes at any time and the pot pays the organizer's own address (no recipient, no admin sweep); organizer / strict majority of contributors / settle deadline flip to Refunding with pull refunds ERC-1167 clone, immutable fundingPoolImpl undeployed none Would escrow contributions; only exits are close → organizer and claimRefund → exactly contributed[claimant] Cloned by FundingPoolFactory contracts/pools/FundingPool.sol:34-52

1.4 Naming, fees, apps

Contract Purpose Upgradeability deployments key(s) Chains Key roles Holds Evidence
CallsignRegistry (spec 054) Optional %callsign naming registry, gated on getActiveTier(user, WAGER_PARTICIPANT_ROLE) >= Gold; ENS-style commit→reveal; delayed repoint UUPS proxy (UUPSManaged + SignerIntentBase) callsignRegistry / callsignRegistryImpl 137 only (empty on Mordor + Amoy) REGISTRY_CURATOR_ROLE, MODERATOR_ROLE, VERIFIER_ROLE — least privilege, none can reassign a callsign No funds ("holds NO funds") contracts/naming/CallsignRegistry.sol:8-31; domain "FairWins CallsignRegistry"/"1" per CLAUDE.md; Mordor/Amoy callsignRegistry: '' in frontend/src/config/contracts.js
FeeRouter (spec 060) Single on-chain source of truth for configurable platform fees (bytes32 serviceId → rate + per-service cap) and the atomic wrapper (depositToVaultWithFee: fee → treasury + net → ERC-4626 vault in one tx) UUPS proxy feeRouter / feeRouterImpl 1, 10, 137, 8453, 42161, 63 (not 61 ETC, not 80002) FEE_ADMIN_ROLE (change rates within caps), DEFAULT_ADMIN_ROLE, UPGRADER_ROLE — all three granted to admin in initialize Holds no balance outside a transaction; treasury == address(0) ⇒ fees skipped, never lost contracts/fees/FeeRouter.sol:12-58; MAX_WRAPPED_FEE_BPS = 250 :37; __gap :46
MiniAppRegistry (spec 073) Curated catalog of third-party mini-app packages; vendor submits (Pending) → curator approves/suspends/deprecates. isLaunchable(id) — never status — is the serving decision; approveApp(id, expectedManifestHash) is content-committed UUPS proxy miniAppRegistry / miniAppRegistryImpl 137 + 63 only (deliberately not Amoy) APP_CURATOR_ROLEself-administering (_setRoleAdmin(APP_CURATOR_ROLE, APP_CURATOR_ROLE)), the platform's entire supply-chain trust boundary, required non-zero at initialize, held by the compliance multisig; DEFAULT_ADMIN_ROLE only for gate config; optional vendor tier gate (membershipManager + minTier, address(0) ⇒ gate off) Fund-free — "never holds, moves, or approves value" contracts/apps/MiniAppRegistry.sol:9-55, role :67, gate state :104-112, initialize :129-153

1.5 Routers (spec 066 / 067)

Contract Purpose Upgradeability deployments key(s) Chains Key roles Holds Evidence
BridgeRouter Curated Across V3 route registry + per-tx limits + pause; charges bridge.transfer by reading FeeRouter, skims to treasury, forwards net to the SpokePool atomically UUPS proxy bridgeRouter / bridgeRouterImpl 1, 10, 137, 8453, 42161 LIQUIDITY_ADMIN_ROLE (routes/limits/protocol addresses), GUARDIAN_ROLE (pause new bridges), DEFAULT_ADMIN_ROLE (fund-path addresses), UPGRADER_ROLE Transient custody onlynonReentrant, resets approvals to 0, asserts ResidualFunds contracts/bridge/BridgeRouter.sol:16-59; THE MEMBER IS THE DEPOSITOR (depositV3 gets msg.sender, never address(this)) :28-38; service id keccak256("bridge.transfer") :59
LiquidityRouter Curates supply pools (Uniswap V3 trading pools + Across bridge pools) and is the fee path for Uniswap supplies only UUPS proxy liquidityRouter / liquidityRouterImpl 1, 10, 137, 8453, 42161 LIQUIDITY_ADMIN_ROLE, GUARDIAN_ROLE, DEFAULT_ADMIN_ROLE, UPGRADER_ROLE Never in an exit path; position NFTs mint to the member; Across addLiquidity is a direct member call it never touches contracts/liquidity/LiquidityRouter.sol:16-58; no removePool :31-33; MAX_FEE_BPS = 250 enforced in the charging contract :55-60; service id keccak256("liquidity.deposit") :52
StakingRouter (spec 066) Per-network control surface for staking + liquid-staking fee path UUPS proxy stakingRouter NONE deployed (empty string in every frontend map that declares it) STAKING_ADMIN_ROLE, GUARDIAN_ROLE contracts/staking/StakingRouter.sol:14-44; registered in scripts/deploy/check-storage-layout.js:57; stakingRouter: '' for Mordor/Amoy/Polygon in frontend/src/config/contracts.js

1.6 ERC-4337 accounts + paymaster (specs 041 / 050)

Contract Purpose Upgradeability deployments key(s) Chains Key roles Holds Evidence
CoinbaseSmartWalletFactory (vendored) Deterministic factory for passkey smart accounts; emits AccountCreated(address,bytes[],uint256) Immutable factory; implementation is immutable accountFactory 1, 10, 61, 63, 137, 8453, 42161, 80002 — the same address 0xd519C25e9dEd0DAC586B764574100479CB318734 on all of them none nothing contracts/account/CoinbaseSmartWalletFactory.sol:13-36; addresses per chain in deployments/*-v2.json contracts.accountFactory
CoinbaseSmartWallet (vendored) ERC-4337 account, MultiOwnable, ERC-1271, self-UUPS-upgradeable, WebAuthn/P-256 owners Per-account ERC-1967 proxy, member-upgradeable accountImpl = 0xfC5086A397e4FbAAF8f73892807415Da8d255E61 on all eight chains same eight account owner set (MultiOwnable) Yes — it is the member's wallet contracts/account/CoinbaseSmartWallet.sol:15-22
FairWinsVerifyingPaymaster (spec 050) ERC-4337 v0.6 verifying paymaster; sponsors a UserOp iff paymasterAndData carries a valid verifyingSigner (KMS, relay-gateway) signature over the op + validity window. Validation is signature-only, zero-storage Immutable, Ownable; entryPoint is immutable verifyingPaymaster (+ verifyingPaymasterSigner recorded) 137 + 80002 only owner (floppy keystore) — the only withdrawer; verifyingSigner rotatable via setVerifyingSigner. A compromised signer can grief the deposit on gas but cannot withdraw Holds the FairWins EntryPoint deposit — the bounded loss cap contracts/account/FairWinsVerifyingPaymaster.sol:12-52; addresses deployments/polygon-chain137-v2.json (0xe14554D14eB5DeC47f7824ebeeDa6C9f3A50d105), deployments/amoy-chain80002-v2.json (0xA00A06ae44FA2bd40Ec10D9613c96afD779b6898); shared signer 0x9Ec0d8fF320c3590b47Da5B06ae0253Ab1Ca22CD
p256Verifier RIP-7212 fallback verifier slot p256Verifier null on every chain — recorded but never deployed deployments/*-v2.json contracts.p256Verifier: null (all 8)

1.7 Custody / Protect (specs 043 / 049 / 068)

Contract Purpose Upgradeability deployments key(s) Chains Key roles Holds Evidence
SafePolicyGuard (v1, spec 049) Safe v1.4.1 transaction guard: per-tx + 24 h-window spend limits, recipient allowlist, cooldown. Still enforcing for vaults that have not adopted V2 Deliberately NOT upgradeable — "an upgrade key would be a backdoor over every vault's enforcement" safePolicyGuard 137 (0xa0F188776a65794cc06777412432e47dcB0d0c4B) + 1337 local No owner, no admin role. Authority is the vault itself: every mutator requires msg.sender == safe, i.e. a threshold-approved Safe self-transaction. Lockout-proof (txs to the Safe or the guard bypass fund rules) Nothing, non-payable everywhere contracts/custody/SafePolicyGuard.sol:6-32; WINDOW = 24 hours, MAX_COOLDOWN = 365 days, MAX_ASSETS = 16 :35-42
SafePolicyGuardV2 (spec 068) Ordered rule array replaced atomically by setRules, first-match-governs, one fall-through for an unmet approver requirement of strictly identical scope; no matching rule ⇒ denial. Approver sets verified against the vault's own approvedHashes at nonce()-1, and an approver counts only while still an owner NOT upgradeable; new rule types ship as a new guard version. Migration is vault-consented via a threshold-approved setGuard safePolicyGuardV2 1337, 10, 61, 63, 137, 8453, 42161 — identical address 0xf18B813Ad8C01249FE904A732543A1b8E6CAfd0c on all six real chains (1337 differs) same model as v1 — vault-is-authority, no admin Nothing contracts/custody/SafePolicyGuardV2.sol:26-47; the ISafeMinimal view-only slice :5-24
PolicyGuardSetup Stateless helper delegatecalled from Safe.setup(to,data,…) so a vault is policy-governed from its first transaction; writes the guard slot, emits Safe's ChangedGuard, then calls the guard config (so msg.sender seen by the guard IS the new Safe) Stateless, immutable policyGuardSetup 10, 61, 63, 137, 8453, 42161, 13370xD0CB9D0ca2E56e9552cb833eC6D16F86ce818C2b everywhere none Nothing; no payable/fallback, no selfdestruct contracts/custody/PolicyGuardSetup.sol:6-45; guard slot keccak256("guard_manager.guard.address") :23
SafeProposalHub Events-only broadcaster of Safe tx preimages for serverless co-owner discovery — no hosted Safe Transaction Service, no backend. Clients MUST recompute Safe.getTransactionHash(...) and reject mismatches Stateless, immutable; uses no OpenZeppelin so it compiles pre-Cancun (ETC/Mordor) safeProposalHub 10, 61, 63, 137, 8453, 42161, 13370x94b5b38C247CE51F7C42C83B63115998b7e970E7 everywhere none — "value-free and authority-free" Nothing contracts/custody/SafeProposalHub.sol:4-45; events Proposed/Cancelled :27-42; MAX_DATA_LENGTH = 8192 :18

1.8 Oracle adapters

Contract Purpose Upgradeability deployments key(s) Chains Roles Holds Evidence
PolymarketOracleAdapter Reads Polymarket Gnosis CTF conditions (conditionId = keccak256(oracle, questionId, outcomeSlotCount); [1,0]/[0,1]), caches resolutions Immutable, Ownable + ReentrancyGuard polymarketAdapter 137 (0x83688e9b8D4f085E3eF4619D91e0e6303cFcf0A4), 80002 (0x98fe63209f5BffcCe905bF8779a1F06576A2C313), 1337 owner Nothing contracts/oracles/PolymarketOracleAdapter.sol:5-31; stores polymarketCTF
ChainlinkDataFeedOracleAdapter Reads a Chainlink price feed after a deadline and compares to a threshold (GT/GTE/LT/LTE/EQ); per-feed allowlist Immutable, Ownable chainlinkDataFeedAdapter 137 (0x7ae8220Dc02D0504EDCBa2C1B1AbA579AA3F0f23), 80002 (same address) owner, allowedFeeds Nothing contracts/oracles/ChainlinkDataFeedOracleAdapter.sol:5-30
ChainlinkFunctionsOracleAdapter Sends a Chainlink Functions request to a DON and caches the boolean in the fulfillment callback Immutable; FunctionsClient + ConfirmedOwner (two-step ownership — does not complete on transfer) chainlinkFunctionsAdapter 137 (0x148C2E347a601AC1a680b17321529b0Ffc31AeFc), 80002 (0x074fC18C1E322a7537b53B8B2Bf0762629E3b532) ConfirmedOwner Nothing (Functions subscription pays) contracts/oracles/ChainlinkFunctionsOracleAdapter.sol:5-32; two-step-ownership caveat docs/runbooks/key-compromise-recovery.md:88
UMAOptimisticOracleV3Adapter Resolves via UMA OOv3 assertions (asserter posts a bond; undisputed liveness ⇒ true; disputes escalate to the DVM via the same callback) Immutable, Ownable umaAdapter 137 (0x8224433d099Af6cd30540A78421aBFd6e044E949), 80002 (0xcEa9b4A01CcD3aA6545ea834a268C69e7eEfee88) owner May move bond tokens (SafeERC20) contracts/oracles/UMAOptimisticOracleV3Adapter.sol:5-29; MIN_LIVENESS = 30 :19; CEI sentinel :21-26
IOracleAdapter The standard interface every adapter implements (oracleType, isAvailable, getConfiguredChainId, isConditionSupported) interface contracts/oracles/IOracleAdapter.sol:9-35

Registry wiring: WagerRegistry keeps a dedicated polymarketAdapter slot for ABI compatibility plus a generic mapping(ResolutionType => IOracleAdapter) oracleAdapters (contracts/wagers/WagerRegistryCore.sol:60-69). ETC 61 / Mordor 63 have no oracle adapters at all — "ETC has no Polymarket/Chainlink/UMA", so those keys are intentionally absent (frontend/src/config/contracts.js:22-27).

1.9 Tokens, DAO, privacy

Contract Purpose Upgradeability deployments key(s) Chains Roles Evidence
TokenFactory (spec 028) Platform token-issuance authority + registry; clones ERC-20/721 templates UUPS proxy tokenFactory / tokenFactoryImpl + openERC20Impl, openERC721Impl, restrictedERC20Impl, and …V2Impl variants 137 (v1 templates), 63 (v1 + V2 templates) TOKEN_ISSUER_ROLE contracts/tokens/TokenFactory.sol:17-30
Token templates OpenERC20(V2), OpenERC721(V2), RestrictedERC20(V2) (ERC-1404) ERC-1167 clones of immutable templates the …Impl keys above 137 / 63 MINTER_ROLE, PAUSER_ROLE, BURNER_ROLE, COMPLIANCE_ROLE contracts/tokens/templates/OpenERC20V2.sol:32-34, OpenERC721V2.sol:30-32, RestrictedERC20V2.sol:13
ExternalDAORegistry (spec 030 / ClearPath) Network-scoped registry of DAOs deployed by other platforms UUPS proxy externalDAORegistry / externalDAORegistryImpl 63 only DAO_MEMBER_ROLE contracts/clearpath/ExternalDAORegistry.sol:10-22
StandardDAOFactory One call deploys a member's standard DAO (OZ TimelockController + Governor + token) UUPS proxy standardDaoFactory NONE — empty on Polygon and Amoy, and never coming on Mordor: Mordor's EVM is pre-Cancun and OZ 5.4.0's Governor uses MCOPY (issue #1268) DAO_MEMBER_ROLE contracts/clearpath/StandardDAOFactory.sol:20-54; the absence-is-the-answer note frontend/src/config/contracts.js:43-47
KeyRegistry address → encryption public key Immutable, no roles keyRegistry 137, 63, 80002 (0xcEFdeBba8E040c035c690ca9057cF22E73247c24 on 63 + 80002), 1337 none contracts/privacy/KeyRegistry.sol:4-11
BackupPointerRegistry Per-wallet pointer to an off-chain encrypted backup (e.g. an IPFS CID) Immutable, no roles backupPointerRegistry 137, 630x664ACAd4d604c626A6160948Df9C10FE38010E11 on both, plus 1337 none contracts/privacy/BackupPointerRegistry.sol:4-13

1.10 Bases and legacy

Item Status Evidence
UUPSManaged Abstract base every upgradeable contract inherits. UPGRADER_ROLE separated from DEFAULT_ADMIN_ROLE; constructor _disableInitializers() locks bare-impl hijack; _authorizeUpgrade is the only, never-removed gate (non-brickable); uint256[50] __gap contracts/upgradeable/UUPSManaged.sol:8-46
SignerIntentBase Shared EIP-712 intent layer: per-signer 2-D replay-nonce map (signer → nonce → used), _verifyIntent, invalidateNonce(WithSig). Storage is ERC-7201 namespaced (slot 0x4640730a…2400), so bolting it onto a live proxy shifts no sequential slots and costs no __gap contracts/upgradeable/SignerIntentBase.sol:12-50
FriendGroupMarketFactory (v1) LEGACY. Source lives only in contracts-archive/markets/FriendGroupMarketFactory.sol — reference-only, never imported or deployed. No live network configures its address; frontend/src/config/contracts.js keeps only a friendGroupMarketFactory deploy block (63 → 15658191, 0 elsewhere) for legacy reads. Events: MarketCreatedPending/ParticipantAccepted/WinningsClaimed/StakeRefunded contracts/oracles/PolymarketOracleAdapter.sol:12 (only in-tree mention); blocks frontend/src/config/contracts.js:396,411,425,458,474; legacy fallback readers frontend/src/data/wagers/EventsSource.js:41,165, frontend/src/utils/blockchainService.js:152,252,535, frontend/src/data/reports/reportDataSource.js:139-140
zkWagerPool* (Semaphore) Abandoned. zkWagerPoolSemaphore, semaphoreVerifier, poseidonT3, zkWagerPoolFactory(+Impl) are recorded on Mordor 63 but "the prior Semaphore-based factory (0x33cD…) is abandoned and intentionally NOT wired here" deployments/mordor-chain63-v2.json contracts; frontend/src/config/contracts.js:48-51

2. Chain topology

2.1 EVM networks

NETWORKS in frontend/src/config/networks.js — ten numeric entries. Cohort split is total (isTestnet on every entry); cohortChainIds() is the only roster an estate read may use, because listSupportedChainIds() spans both cohorts and would breach constitution III (frontend/src/config/networks.js:1160-1172).

chainId Name Cohort Reference-chain roles Our contracts present Evidence
137 Polygon mainnet Membership chain (MAINNET_CHAIN_ID), mini-app registry chain, PRIMARY_CHAIN_ID Everything except fundingPoolFactory, stakingRouter, standardDaoFactory, tokenFactory V2 impls, externalDAORegistry networks.js:59,557-559; deployments/polygon-chain137-v2.json
80002 Polygon Amoy testnet Membership chain for testnet builds (TESTNET_CHAIN_ID); deliberately not the mini-app registry chain wagerRegistry (no intents facet), membershipManager, voucher, batchMinter, keyRegistry, sanctionsGuard, 3 oracle adapters, entryPoint/accountFactory/accountImpl, verifyingPaymaster networks.js:60,254; deployments/amoy-chain80002-v2.json
63 Ethereum Classic Mordor testnet Mini-app registry chain for testnet builds (MINIAPP_TESTNET_CHAIN_ID = 63) — the one reference chain that does not derive from TESTNET_CHAIN_ID, because Amoy has no registry Most of the estate + tokenFactory V2 + externalDAORegistry + abandoned zk pools; no oracle adapters, no callsignRegistry, no bridge/liquidity router networks.js:1141-1159,375-377; deployments/mordor-chain63-v2.json
61 Ethereum Classic mainnet Custody only: safeProposalHub, safePolicyGuardV2, policyGuardSetup, entryPoint, accountFactory, accountImpl. No feeRouter networks.js:469-471; deployments/etc-chain61-v2.json; frontend/src/config/contracts.js ETC_CONTRACTS ~L298
1 Ethereum mainnet feeRouter, bridgeRouter, liquidityRouter, account stack. Only chain with an Across HubPool networks.js:815-817; deployments/mainnet-chain1-v2.json
10 Optimism mainnet custody trio + feeRouter + bridgeRouter + liquidityRouter + account stack deployments/optimism-chain10-v2.json
8453 Base mainnet same as Optimism deployments/base-chain8453-v2.json
42161 Arbitrum One mainnet same as Optimism deployments/arbitrum-chain42161-v2.json
11155111 Sepolia testnet none (no entry in NETWORK_CONTRACTS) networks.js:897-899
560048 Hoodi testnet none networks.js:974-976
1337 Hardhat testnet, local-only isLocalOnlyChain(1337); selectable: false; subgraphUrl: null full local sandbox set networks.js:1026-1035; frontend/src/config/contracts.js:549-562

isLocalOnlyChain is exported because local-only is a fact about reachability: a shipped build can never reach http://127.0.0.1:8545, so screeningChainIds() = cohortChainIds() .filter(id => !isLocalOnlyChain(id)) (frontend/src/lib/screening/sources.js:185-187).

NETWORK_CONTRACTS mapping (frontend/src/config/contracts.js:370-381) — note the E2E Amoy impersonation: 80002: E2E_AMOY_LOCAL ? HARDHAT_CONTRACTS : AMOY_CONTRACTS, DEV-guarded so the branch is dead code in production bundles.

2.2 Non-EVM networks (parallel registries — never merged into NETWORKS)

id Kind Cohort Notes Evidence
'bitcoin' Bitcoin (spec 061) mainnet String id, no chainId, no contracts, no subgraph. coinType: 0, hrp bc, gateway segment mainnet. Capabilities: portfolio/send/receive only; wagers/pools/membership/gasless/swap/earn/predict all false; collect: 'stamps-only' frontend/src/config/bitcoinNetworks.js:28-60
'bitcoin-testnet' Bitcoin Testnet4 testnet coinType: 1, hrp tb, gateway segment testnet bitcoinNetworks.js:61-70
'solana' / 'solana-devnet' Solana (spec 063) mainnet / testnet Same parallel pattern; coinType: 501; ids must never reach getContractAddressForChain, wagmi, or subgraph code frontend/src/config/solanaNetworks.js:1-30
hyperliquid (venue, not a network) Perps venue (spec 082) evm: false, chains: [] — "its own L1 — never a numeric chain id (FR-012)". isEvmPerpVenue is the boundary guard. Read-only market data; the only platform-priced perps fee frontend/src/config/perps.js:42-56

Boundary guards: isBitcoinNetworkId (bitcoinNetworks.js:74-76), isSolanaNetworkId, isEvmPerpVenue (perps.js:54-56).

2.3 Proxy → implementation, per chain (from deployments/**)

Only rows where deployments/ records both halves. Addresses quoted verbatim.

Polygon 137 — deployments/polygon-chain137-v2.json

Contract Proxy Implementation
WagerRegistry 0xE878b62887fC8A5F739B8Ce61bC19546A280Ef89 0x9c52C1ef4Bbe65CF19a5C26bebD4A22100964898
WagerRegistryIntents (facet) — (reached via the proxy) 0x8878C8d6822De61cA8886Ae1D8BDA60b61ca1d81
MembershipManager 0xEfd1a880c6BfBf38A661A3F5fF6d5ECB296D557a 0x7177470fE3c5D89CEf965A596540E57cE290C939
TokenFactory 0x5806e76cA3c838524E7cF43db7625bdFBA0783a0 0xE819f7b672D81A8b78d40b1C99Fe5d646513D12C
WagerPoolFactory 0x420aEC3c76859eB74ab21c769c16AcdAB221f723 0x754d8aa4785Ec4bEE7c921f8d032D5E3a78d9308 (clone template poolImpl 0xB153e4456FaD1A7E96e35e14094Cf6964348BC40)
CallsignRegistry 0x22BD6Dd351Db375b64C2886Bda6f3E3F4fd31dA2 0xD220D34ed2148B9F4DC65C1bc75169D7DECFBB49
FeeRouter 0xf8161fC26172621E9fbcc6c39500Bb14b0902B35 0x40ee755246E60f66E7bA425F99C6d704859d38db
BridgeRouter 0x8064F3Cd9F8f113691B981d2B15EF85D95Abd551 0x8F7A7e7437733326BD2F8045BFceD9B821aF1De1
LiquidityRouter 0x13762c059c2A22E3bCd8A44F36EA44e8e3B22B31 0x33818052Ca8B5b8Bb9777Bf6eBbaFCD8Faae6e65
MiniAppRegistry 0x5a168Cc9FeFaf40e7BC536C8C61669e6d547A0A2 0x41858006aD6dd0788b84F9fb17A28d8167C7b331
Immutable singletons sanctionsGuard 0x2Dc53d91A189be71DfE96Ea9BCFCF6aDDA77BC76, membershipVoucher 0xCB28DC438564672067a6f84131B5130e6Cf7ECC6, voucherBatchMinter 0x4b50d24ca28CbDC029714e5830f7D16a0ebEDb0e, keyRegistry 0xcEFdeBba8E040c035c690ca9057cF22E73247c24, backupPointerRegistry 0x664ACAd4d604c626A6160948Df9C10FE38010E11, safePolicyGuard 0xa0F188776a65794cc06777412432e47dcB0d0c4B, safePolicyGuardV2 0xf18B813Ad8C01249FE904A732543A1b8E6CAfd0c, policyGuardSetup 0xD0CB9D0ca2E56e9552cb833eC6D16F86ce818C2b, safeProposalHub 0x94b5b38C247CE51F7C42C83B63115998b7e970E7, verifyingPaymaster 0xe14554D14eB5DeC47f7824ebeeDa6C9f3A50d105

Mordor 63 — deployments/mordor-chain63-v2.json

Contract Proxy Implementation
WagerRegistry 0x3ccB144d8aa838e8d4D695867cC72e548117830C 0x9FfE701be18Ff033706f2df19cd8730F5CB884B2
WagerRegistryIntents 0x81a9c5904A109F0b7bC6329ba6F2be2de5B6abB1
MembershipManager 0x68bCBA1055DAbe11b98Bb8425A16e648Ad65d541 0x7D38F7Ef26f7E2409d5C04a62c1d9A3Ec002A49e
TokenFactory 0x5bdf74Ce98D41bf35192c20B25ACd561C75CFe62 0x135108EB6f81e361b6cF131d2Cb9A01E92Cd8ED9
ExternalDAORegistry 0xcEE0fb2e1407f0A0d19Bcf4Fee2726A3005FA3C0 0x28270cB71E87D2D6C662e61CFE6eD02d05d43B7A
WagerPoolFactory 0xac78B4EdeF96e74a2653028dF93A26acFCfC613F 0xfB6F9F7EfD86a220eE1aD7906278247051B25430 (template poolImpl 0xd0b94a77DA7Aaa488343CF89978f1Bbf9E72E277)
FeeRouter 0x5249e3008Cb1Eb81B5BF39148B7760B1c36e516e 0x744b8E56d84bb8D7657b2Bb13426cB882c93B7E6
MiniAppRegistry 0xFEd626025225A3B1aB3BA72D429B8c9C74cb5058 0xc8Dd8601b35aDa3AF367C9E41f24Fd0503Ced674
Abandoned zkWagerPoolFactory 0x33cDfa339AbE993FEfEB9fE1A8341105ba55D586 0xd3e851FDDa9D5796D503daFd34b2403D7336d9fD — deliberately not wired

Amoy 80002 — deployments/amoy-chain80002-v2.json

Contract Proxy Implementation
WagerRegistry 0xA429CdaD3E1497e33BEA7D6FE7d6913fE880241b 0xa2176F5Fea39888cD1697Be4651415490C78905dno wagerRegistryIntents recorded, so the gasless facet is unavailable here
MembershipManager 0x89158f2E044C73c687dA12B7FA42b94F9A6D8465 0xb6499596703cEE6eA4BE5b5F01DEc4d7ccfe10bD

Ethereum 1 / Optimism 10 / Base 8453 / Arbitrum 42161

Contract Ethereum 1 Optimism 10 / Base 8453 / Arbitrum 42161
FeeRouter proxy → impl 0xB9F80D6D4CfD3ecC60b63810aDF9d88931D0e3d30x5cCd55D62Ce7Df730c39543B332dD8d6054B5d00 0x98218248CA53Dd88159979af20172C86b94e8B290x9B68fDbBaEaeafbe2349549A4994A4697462AFea (same on all three)
BridgeRouter proxy → impl 0x258181DF2aa45EA3a3eAC748d6491D5e1f2675eE0xcA277Cc3485Da12771d6171a9D0A894B8DD159f8 0x1afcAC1949BD306F7D4818999f509941F2E855820x41ba6bca216bd6A4c5a0bf8F9B2d682EC0a879d5
LiquidityRouter proxy → impl 0x1afcAC1949BD306F7D4818999f509941F2E855820x41ba6bca216bd6A4c5a0bf8F9B2d682EC0a879d5 0xA273aF8ebB76d1D0Dcd55692C1f5a7db956F7EED0x7Af46728e7C969b75723398e3F93b565E968A3ba

Address-collision caution for the workbook: Ethereum's liquidityRouter proxy 0x1afcAC…5582 is byte-identical to the BridgeRouter proxy address on 10/8453/42161, and its impl 0x41ba6bca… is the BridgeRouter impl there. This is a nonce-derived artefact of deploying from one EOA in a fixed order, and scripts/deploy/check-storage-layout.js:118 documents exactly this overlap. Never read one chain's label onto another.

ETC 61 — deployments/etc-chain61-v2.json

Custody + account stack only: safePolicyGuardV2 0xf18B813A…fd0c, policyGuardSetup 0xD0CB9D0c…8C2b, safeProposalHub 0x94b5b38C…70E7, entryPoint 0x5FF137D4…2789, accountFactory 0xd519C25e…8734, accountImpl 0xfC5086A3…5E61. No FeeRouter, no routers, no wager/membership estate.

2.4 Recorded deploy blocks (event-scan floors)

deployments/*-v2.json deployBlocks and the client copy in frontend/src/config/contracts.js:409-470:

Chain Recorded blocks
137 backupPointerRegistry 90120712, safePolicyGuard 90120723, policyGuardSetup 90120725, safeProposalHub 90120743, safePolicyGuardV2 90932886, feeRouter 90939919, miniAppRegistry 91265680, accountFactory 89725718; client-side also wagerRegistry 89717915, membershipVoucher 89717905 (measured), membershipManager 89717895 (measured), tokenFactory 89717942, wagerPoolFactory 89720740, oracle adapters 87937162/87937176/87937184
63 wagerRegistry 16404317, membershipManager 16404308, membershipVoucher 16404315, voucherBatchMinter 16407208, backupPointerRegistry 16430733, wagerPoolFactory 16495564, poolImpl 16497337, safePolicyGuardV2 16645528, policyGuardSetup 16645530, safeProposalHub 16645531, feeRouter 16646334, miniAppRegistry 16685064, accountFactory 16650970
80002 wagerRegistry 40521027, membershipManager 40521018, membershipVoucher 40521024, voucherBatchMinter 40545690
61 / 10 / 8453 / 42161 safeProposalHub only: 25026893 / 154753770 / 49158472 / 488059169 (plus safePolicyGuardV2, policyGuardSetup, feeRouter, accountFactory in deployments/)
1 accountFactory 25625767

safeProposalHub MUST carry a block on every custody chainuseVaultProposals refuses to scan without one, so a missing entry silently kills proposal discovery even where the hub is deployed (frontend/src/config/contracts.js:400-403). miniAppRegistry's block is a placeholder 0 on several chains and disables nothing, because catalog/launch reads are view calls, never event scans (contracts.js:404-408). Separately, deployBlocks.accountFactory is recorded but the spec-104 passkey discovery path is blocked on it per CLAUDE.md — and deployBlocks?.X || 0 makes a missing entry a silent scan from block 0.


3. Roles & authority

3.1 Role constants found in contracts/** (excluding mocks/, test/)

Role constant Contract(s) What it can do Evidence
DEFAULT_ADMIN_ROLE (OZ) every UUPSManaged adopter + SanctionsGuard Config: on WagerRegistry the membership manager, oracle adapter, token allowlist; on MembershipManager the treasury + tier config + role admin; on the routers the fund-path addresses (spokePool/positionManager/feeRouter/sanctionsGuard); on MiniAppRegistry only the vendor gate contracts/upgradeable/UUPSManaged.sol:33-36; contracts/wagers/WagerRegistry.sol:21
UPGRADER_ROLE UUPSManaged (all adopters) Replace the implementation — and on WagerRegistry also setIntentExtension, because swapping the facet is equivalent to an upgrade contracts/upgradeable/UUPSManaged.sol:18-20,40; contracts/wagers/WagerRegistry.sol:91,109-110
GUARDIAN_ROLE WagerRegistryCore, BridgeRouter, LiquidityRouter, StakingRouter Emergency pause / unpause of new activity. Per-router — it does not inherit the WagerRegistry guardian set; admin tabs read authority from the router in scope (readRouterAuthority) WagerRegistryCore.sol:43, BridgeRouter.sol:56, LiquidityRouter.sol:48, StakingRouter.sol:44
ACCOUNT_MODERATOR_ROLE WagerRegistryCore Freeze / unfreeze individual accounts (a frozen account cannot create/accept/cancel/declare/claim/refund) WagerRegistryCore.sol:44; WagerRegistry.sol:23,26-27
WAGER_PARTICIPANT_ROLE WagerRegistryCore (declared); MembershipManager (the paid membership key) The gate a member must hold to wager; the only paid role WagerRegistryCore.sol:42; MembershipManager.sol:14-16
ROLE_MANAGER_ROLE MembershipManager Grant / revoke memberships out-of-band MembershipManager.sol:26
SANCTIONS_ADMIN_ROLE SanctionsGuard Add/remove discretionary deny-list entries (DEFAULT_ADMIN_ROLE sets the oracle) SanctionsGuard.sol:18-19,24
POOL_PARTICIPANT_ROLE WagerPoolFactory, FundingPoolFactory Membership role gating pool participation — distinct from WAGER_PARTICIPANT_ROLE; the same role for both pool kinds, so one tier config covers both WagerPoolFactory.sol:37-39; FundingPoolFactory.sol:35-37
FEE_ADMIN_ROLE FeeRouter Change service rates within their registered caps; granted to admin in initialize alongside DEFAULT_ADMIN_ROLE + UPGRADER_ROLE. History = FeeBpsChanged events FeeRouter.sol:33-34,49-58
LIQUIDITY_ADMIN_ROLE BridgeRouter, LiquidityRouter Curate routes / pools / limits (reversible) — as distinct from DEFAULT_ADMIN_ROLE, which owns the fund-path addresses (not reversible) BridgeRouter.sol:52-54; LiquidityRouter.sol:47
STAKING_ADMIN_ROLE StakingRouter Staking config (contract undeployed) StakingRouter.sol:42
APP_CURATOR_ROLE MiniAppRegistry Approve / suspend / deprecate listings — the platform's entire supply-chain trust boundary. Self-administering (_setRoleAdmin(APP_CURATOR_ROLE, APP_CURATOR_ROLE)), required non-zero at initialize, "held by the compliance multisig and granted post-deploy, never in initialize" MiniAppRegistry.sol:19-21,67,129-151
REGISTRY_CURATOR_ROLE, MODERATOR_ROLE, VERIFIER_ROLE CallsignRegistry Policy params / moderation / verification marker. None can reassign a callsign to a different owner CallsignRegistry.sol:28-31
TOKEN_ISSUER_ROLE TokenFactory Gate token creation TokenFactory.sol:30
MINTER_ROLE, PAUSER_ROLE, BURNER_ROLE OpenERC20V2, OpenERC721V2 Per-token-clone operations OpenERC20V2.sol:32-34; OpenERC721V2.sol:30-32
COMPLIANCE_ROLE RestrictedERC20V2 ERC-1404 transfer restrictions RestrictedERC20V2.sol:13
DAO_MEMBER_ROLE ExternalDAORegistry, StandardDAOFactory ClearPath registry/factory membership gate ExternalDAORegistry.sol:22; StandardDAOFactory.sol:54
No role at all (by design) SafePolicyGuard, SafePolicyGuardV2, PolicyGuardSetup, SafeProposalHub, KeyRegistry, BackupPointerRegistry, VoucherBatchMinter Authority for the guards is the vault itself (msg.sender == safe); the hub and registries are authority-free SafePolicyGuard.sol:14-18; SafePolicyGuardV2.sol:34-39; SafeProposalHub.sol:11-15
owner (Ownable / ConfirmedOwner) 4 oracle adapters, FairWinsVerifyingPaymaster Adapter config; on the paymaster the only withdrawer of the EntryPoint deposit FairWinsVerifyingPaymaster.sol:28-31,32
verifyingSigner (not a role) FairWinsVerifyingPaymaster Authorize sponsorship. A compromised signer can grief the deposit on gas but cannot withdraw; rotate with setVerifyingSigner FairWinsVerifyingPaymaster.sol:28-31,37-38

Not an on-chain role, but the same class of authority: authorizedCallers on MembershipManager (MembershipManager.sol:45) — kept for the WagerRegistry hook surface.

3.2 Who holds what — recorded facts and open items

Fact Detail Evidence
Admin Safe (current) 0xcf76db7aa9Fb1BFe08E010468F3344bB45830447, 2-of-3, owners 0x26235546…1C9A, 0x12151853…8575, 0x48cBca63…2275, saltNonce 68 — the same address on chains 1, 10, 61, 63, 137, 8453, 42161 deployments/admin-safe.json
Admin Safe (superseded) 0x8cc564E3dF4003c2F0a33C679c8DfE6237c5c3fa — retired 2026-08-22 because "owner set included a compromised hot EOA and a passkey CONTRACT that only had code on Polygon, leaving an effective 2-of-2 off Polygon". Replacement is KMS HSM + Ledger + Trezor, all EOAs, so all three sign on every chain. Note this same address is still the recorded treasury on chains 1, 10, 8453, 42161 deployments/admin-safe.json superseded; treasury in deployments/{mainnet,optimism,base,arbitrum}-*.json
Key-compromise write-off (Mordor) 2026-08-22: a chain-wide RoleGranted/OwnershipTransferred scan of Mordor found 23 unrecorded contracts holding live authority — superseded WagerRegistry/MembershipManager iterations, the contracts-archive v1 RBAC estate (RoleManager, MembershipPaymentManager, FriendGroupMarketFactory) and token clones — carrying ~60 sole-held roles and 62 Ownable owners still held by the compromised EOA 0x52502d04…F6e1. Decision: NOT MIGRATED — deliberately abandoned. The 10 recorded Mordor contracts were migrated in full to the admin Safe. "Do NOT deploy anything new that depends on these, and do not read their state as trustworthy." deployments/mordor-chain63-v2.json legacyContractsWriteOff; runbook docs/runbooks/key-compromise-recovery.md
Deployer EOA 0x52502d049571C7893447b86c4d8B38e6184bF6e1 on every non-local chain (0xf39Fd6e5…2266 on Hardhat) — this is the compromised key named in the write-off above deployer in every deployments/*-v2.json
Outstanding: router admin handoff (#966) Per CLAUDE.md: BridgeRouter + LiquidityRouter "are now deployed and recorded on all five EVM mainnets (1/10/137/8453/42161); the admin handoff off the deployer EOA is still outstanding (issue #966), so treat DEFAULT_ADMIN_ROLE on these routers as held by a single hot key until that lands." CLAUDE.md spec-067 guardrail
⚠️ Stale doc docs/developer-guide/bridge-and-liquidity.md:25 still says "neither router is deployed on any network yet (issue #966)" — contradicted by the addresses now in deployments/{mainnet,optimism,polygon,base,arbitrum}-*.json. The deployment half of #966 is done; the handoff half is not. Flagging so the workbook does not inherit the stale claim docs/developer-guide/bridge-and-liquidity.md:25-28 vs deployments/*-v2.json
Two-step ownership trap ChainlinkFunctionsOracleAdapter uses ConfirmedOwner: ownership does not complete on transfer — the recipient must accept docs/runbooks/key-compromise-recovery.md:88
Sanctions/oracle admin Both SANCTIONS_ADMIN_ROLE and DEFAULT_ADMIN_ROLE on SanctionsGuard are "granted to the air-gapped floppy-keystore admin at deploy" contracts/access/SanctionsGuard.sol:18-19
Paymaster owner "only owner (floppy keystore) withdraws"; verifyingSigner is the relay-gateway KMS key (verifyingPaymasterSigner 0x9Ec0d8fF320c3590b47Da5B06ae0253Ab1Ca22CD on both 137 and 80002) contracts/account/FairWinsVerifyingPaymaster.sol:28-29; deployments/polygon-chain137-v2.json, amoy-chain80002-v2.json
Treasuries 137 → 0x1215185387E70a48b07D73AcB67002A073F18575; 1/10/8453/42161 → 0x8cc564E3dF4003c2F0a33C679c8DfE6237c5c3fa (the superseded Safe); 63/80002 → the deployer EOA treasury in each deployments/*-v2.json
No Projects-v2 / role mirror Off-chain: there is deliberately no status:* label mirror; on-chain authority is read from the contract that will enforce it, and an unconfirmed authority read leaves a control offered rather than hiding a killswitch on an RPC timeout CLAUDE.md multi-agent + spec-071 guardrails

3.3 Fee services registered on FeeRouter (config, all ship at 0 bps)

serviceId Kind Cap Charged by Evidence
earn.lend Wrapped 250 bps FeeRouter.depositToVaultWithFee (the only on-chain charging path) scripts/deploy/lib/feeServices.js:18
polymarket.taker ConfigOnly 100 bps relay-gateway reads (spec-057 cap) feeServices.js:19
polymarket.maker ConfigOnly 50 bps relay-gateway reads feeServices.js:20
stake.lido ConfigOnly 250 bps StakingRouter skims + forwards feeServices.js:24
stake.polygon ConfigOnly 250 bps StakingRouter feeServices.js:25
perps.hyperliquid.builder ConfigOnly 10 bps (Hyperliquid's own limit, not our 250) Hyperliquid, per order feeServices.js:31
bridge.transfer ConfigOnly 250 bps BridgeRouter (value-out) scripts/deploy/deploy-bridge-liquidity.js:110
liquidity.deposit ConfigOnly 250 bps LiquidityRouter (Uniswap supply, value-in) scripts/deploy/deploy-bridge-liquidity.js:111

registerService is one-shot per id (AlreadyRegistered); the deploy script asserts the live table matches feeServices.js (deploy-fee-router.js:180,221,252). ConfigOnly is load-bearing: it makes depositToVaultWithFee("bridge.transfer", …) revert ServiceNotWrapped (deploy-bridge-liquidity.js:100-111).


4. On-chain ↔ off-chain seams

4.1 Client resolution seam

Everything the browser resolves goes through frontend/src/config/contracts.js#getContractAddressForChain(name, chainId) (:535-546), backed by per-chain literal maps. Never hand-build an address; never hand-build a provider either — reads go through frontend/src/lib/network/rpcEndpoints.js#resolveRpcEndpointsutils/rpcProvider.js#makeReadProvider / getReadProvider(chainId) (member override → build default). Per-chain key sets (frontend/src/config/contracts.js, line numbers from the const *_CONTRACTS declarations):

Map (line) Chain Keys declared ('' = declared-but-undeployed)
MORDOR_CONTRACTS (:29) 63 deployer, wagerRegistry, membershipManager, keyRegistry, sanctionsGuard, paymentToken, wmatic, membershipVoucher, voucherBatchMinter, tokenFactory, externalDAORegistry, backupPointerRegistry, wagerPoolFactory, miniAppRegistry, safeProposalHub, safePolicyGuardV2, policyGuardSetup, feeRouter, entryPoint, accountFactory — '': treasury, fundingPoolFactory, callsignRegistry, stakingRouter, bridgeRouter, liquidityRouter
HARDHAT_CONTRACTS (:82) 1337 (and 80002 under E2E_AMOY_LOCAL) full local set incl. standardDaoFactory, safePolicyGuard, fundingPoolFactory, callsignRegistry, feeRouter
AMOY_CONTRACTS (:207) 80002 wagerRegistry, membershipManager, keyRegistry, sanctionsGuard, polymarketAdapter, paymentToken, wmatic, 2 chainlink adapters, umaAdapter, membershipVoucher, voucherBatchMinter, entryPoint, verifyingPaymaster, accountFactory — '': callsignRegistry, miniAppRegistry, stakingRouter, bridgeRouter, liquidityRouter, standardDaoFactory
POLYGON_CONTRACTS (:246) 137 the full estate — '': fundingPoolFactory, stakingRouter, standardDaoFactory
ETC_CONTRACTS (:298) 61 deployer, safeProposalHub, safePolicyGuardV2, policyGuardSetup, entryPoint, accountFactory
ETHEREUM_CONTRACTS (:310) 1 bridgeRouter, liquidityRouter, feeRouter, deployer, entryPoint, accountFactory
OPTIMISM_/BASE_/ARBITRUM_CONTRACTS (:319/:331/:343) 10 / 8453 / 42161 bridgeRouter, liquidityRouter, safeProposalHub, safePolicyGuardV2, policyGuardSetup, feeRouter, deployer, entryPoint, accountFactory

Note: wagerRegistryIntents has no client key — a grep of frontend/src returns nothing. That is correct: the facet is only ever reached through the proxy's fallback, so the client resolves wagerRegistry and gets a merged ABI. Tests deploy the pair via test/helpers/proxy.js#deployWagerRegistry.

Tenant seam (spec 072): a dedicated tenant resolves only its own generated set from tenantContractsForChain(chainId) — "absence stays absence, no fallback to the shared estate" (frontend/src/config/contracts.js:493-503).

4.2 Subgraph (subgraph/**)

Only Polygon 137 (network: matic) is in the committed manifest. Eight data sources + two templates:

Data source Address (137) Start block Events handled Entities
WagerRegistry 0xE878b628…Ef89 89717915 WagerCreated, OpenWagerCreated, WagerAccepted, PayoutClaimed, WagerRefunded, WagerDrawn, WagerCancelled, WagerResolved, WagerDeclined, DrawProposed, DrawRevoked Wager, WagerTransfer
MembershipVoucher 0xCB28DC43…ECC6 89717905 VoucherMinted, Transfer Voucher
MembershipManager 0xEfd1a880…557a 89717895 MembershipRedeemed Voucher
ChainlinkDataFeedOracleAdapter 0x7ae8220D…0f23 87937162 ConditionRegistered, MarketLinked, ConditionResolved OracleCondition, OracleMarketLink
ChainlinkFunctionsOracleAdapter 0x148C2E34…AeFc 87937176 same three same
UMAOptimisticOracleV3Adapter 0x8224433d…E949 87937184 same three same
TokenFactory 0x5806e76c…83a0 89717942 TokenCreated Token
WagerPoolFactory 0x420aEC3c…f723 89720740 PoolCreated, TokenAllowed Pool, PoolAllowedToken
template TokenInstance dynamic Transfer, Paused, Unpaused, Frozen, RoleGranted, RoleRevoked Holder, TokenActivity
template WagerPool dynamic (clones) Joined, JoiningClosedEvent, OutcomeProposed, Approved, OutcomeLocked, Claimed, Refunded, PoolCancelled PoolMember, PoolProposal, PoolPayoutEntry, PoolApproval, PoolClaim, PoolRefund

Schema entities (subgraph/schema.graphql): Wager:41, WagerTransfer:73, Voucher:102, OracleCondition:137, OracleMarketLink:164, Token:184, Holder:207, TokenActivity:224, Pool:251, PoolMember:294, PoolProposal:311, PoolPayoutEntry:337, PoolApproval:351, PoolClaim:364, PoolRefund:378, PoolAllowedToken:389.

subgraph/networks.json additionally declares deployable address sets for mordor (WagerRegistry, MembershipVoucher, MembershipManager, TokenFactory, WagerPoolFactory) and polygon-amoy (WagerRegistry, MembershipVoucher, MembershipManager + 3 oracle adapters).

Not indexed by any subgraph — read on-chain only: FeeRouter, BridgeRouter, LiquidityRouter, CallsignRegistry, MiniAppRegistry, SafeProposalHub/policy guards, SanctionsGuard, FundingPoolFactory/FundingPool (spec 103 ships "no subgraph entity yet" — the feed is the clone's own log bounded at createdBlock), KeyRegistry, BackupPointerRegistry, the account stack and the paymaster. Chains with subgraphUrl: null (e.g. 1337) read straight over RPC (networks.js:1035-1036).

4.3 Event-scan floors and the reads that depend on them

Reader Needs Consequence if missing
useVaultProposals (custody queue) getDeploymentBlockForChain('safeProposalHub', chainId) Refuses to scan → proposal discovery silently dead on that chain (frontend/src/config/contracts.js:400-403)
Mini-app catalog / launch nothing (view calls only) a 0 block cannot make Apps look available; availability comes from a non-empty address + an Approved record (contracts.js:404-408)
useVouchers#listMyVouchers membershipVoucher block a block later than real creation renders an honest-looking empty list for a holding that exists — the Amoy E2E case (contracts.js:437-447)
passkey account discovery (spec 104 R2) deployBlocks.accountFactory deployBlocks?.X \|\| 0 ⇒ a silent scan from block 0 rather than a loud failure (CLAUDE.md spec-104 guardrail)

4.4 Browser-direct vs relay-gateway

Path Rail Evidence
Every contract read (balances, wagers, membership, pools, fee rates, vault queues, screening) Browser-direct over getReadProvider(chainId) / makeReadProvider, member RPC override first CLAUDE.md spec-069 guardrail; frontend/src/lib/screening/sources.js uses readProviderFor
Relayed intents (contract actions + EIP-3009) services/relay-gateway policy gateway → services/oz-relayer engine. Every gasless flow keeps a self-submit fallback CLAUDE.md spec-035/036; gateway targets are validated per chain from the deployment record — wagerRegistry, membershipManager, sanctionsGuard all required (services/relay-gateway/src/config/index.js:313,329-332)
Sponsored UserOps alto bundler (EntryPoint v0.6) + KMS-signed ERC-7677 endpoint POST /v1/paymaster on the same gateway deployments/polygon-chain137-v2.json infra (alto ghcr.io/pimlicolabs/alto:v1.2.7, executor 0x7C6da19ae005D4F13BB8660Ac989c48aCcFE84F6, key in Secret Manager alto-executor-key-137)
FeeRouter rate reads by the gateway gateway reads the router read-only via services/relay-gateway/src/fees/onchain.js; env bps are fallback. FEE_ROUTER_CHAIN_ID defaults 137, address defaults to the deployment record's feeRouter and a contradicting env value fails boot services/relay-gateway/src/config/index.js:200-205,1203-1218
Gateway enabled-chain gate A chain may be enabled only if the record has all three of wagerRegistry/membershipManager/sanctionsGuard; a chain with custody contracts only (no wagerRegistry) fails to enable. Membership reference chain must be enabled and have a membershipManager, else boot refuses ("no member could be authenticated") services/relay-gateway/src/config/index.js:7-12,313,925-987
Gateway chain table 137 + 80002 support EIP-3009 payment (tokenDomain {USD Coin,2} / {USDC,2}); 61 + 63 are paymentSupported: false — the live USC token is permit-only, no EIP-3009 — and both are noBatch (their endpoints return batch responses ethers v6 cannot decode) services/relay-gateway/src/config/chains.js:10-63
Read-only proxies (no contracts) Polymarket (polymarket/), Perps (perps/), OpenSea (opensea/), Bitcoin (bitcoin/), News (news/), member API (memberApi/), x402 (x402/) services/relay-gateway/src/ listing

5. External protocol contracts (called, not owned)

Protocol Address(es) Chain(s) Source file Notes
EntryPoint v0.6 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 1, 10, 61, 63, 137, 8453, 42161, 80002 deployments/*-v2.json contracts.entryPoint; frontend/src/config/contracts.js:77,103,237,269,303,315,327,339,351 Canonical v0.6. The paymaster's entryPoint is immutable; alto runs deploy-simulations-contract=false because v0.6 has built-in simulateHandleOp
Safe v1.4.1 singletons singleton 0x41675C099F32341bf84BFc5382aF534df5C7461a, singletonL2 0x29fcB43b46531BcA003ddC8FCB67FFE91900C762, proxyFactory 0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67, fallbackHandler (CompatibilityFallbackHandler, EIP-1271) 0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99, multiSendCallOnly 0x9641d764fc13c8B624c04430C7356C1C7C8102e2 10, 61, 63, 137, 8453, 42161 (+ 80002 DEV-only E2E impersonation) frontend/src/config/safeContracts.js:13-48 Identical on every chain the Safe Singleton Factory reached; hand-maintained (NOT synced by sync:frontend-contracts); all verified live. CUSTODY_SUPPORTED_CHAIN_IDS derives from this map
Across V3 SpokePool 1 0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5; 10 0x6f26Bf09B1C792e3228e5467807a900A503c0281; 137 0x9295ee1d8C5b022Be115A2AD3c30C72E34e7F096; 8453 0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64; 42161 0xe35e9842fceaCA96570B734083f4a58e8F7C5f2A those five frontend/src/config/networks.js:620,683,740,788,854-857; mirrored as acrossSpokePool in each deployments/*-v2.json bridge in networks.js is a build-time display fallback only; the authoritative address, routes, limits and pause state are read from the on-chain BridgeRouter
Across HubPool 0xc186fA914353c44b2E33eBE05f21846F1048bEda Ethereum 1 only frontend/src/config/networks.js:854-857; acrossHubPool: null in every other deployment record An L1 contract by design. addLiquidity has no recipient parameter, so bridge-LP deposits are a direct member call that never touches a FairWins contract — do not copy this address to an L2
Uniswap V3 Canonical set (1, 10, 137, 42161): factory 0x1F98431c8aD98523631AE4a59f267346ea31F984, swapRouter 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45, quoter 0x61fFE014bA17989E743c5F6cB21bF9697530B21e, positionManager 0xC36442b4a4522E871399CD717aBDD847Ab11FE88. Base 8453 differs: factory 0x33128a8fC17869897dcE68Ed026d694621f6FDfD, positionManager 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1, quoter 0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a 1, 10, 137, 8453, 42161 frontend/src/config/networks.js:214-219 (CANONICAL_UNISWAP), :742-747 (Base); uniswapFactory/uniswapPositionManager in deployments/{polygon,optimism,base,arbitrum}-*.json "Addresses are NOT identical across chains… Never copy an address from one chain to another." scripts/ops/verify-protocol-addresses.js re-checks bytecode
ETCswap V3 (Uniswap fork) ETC 61 defaults: quoter 0x4d8c163400CB87Cbe1bae76dBf36A09FED85d39B, positionManager 0x3CEDe6562D6626A04d7502CC35720901999AB699 (factory/router from env) 61, 63 (env-driven, null when unset) frontend/src/config/networks.js:507-516 ETC/Mordor have neither Across nor Uniswap and cannot host the spec-067 routers
ERC-4626 vaults (Morpho) No addresses in config by design — the vault list comes from the Morpho data API; only merklDistributor 0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae (Merkl's canonical claim contract, same address on every chain it supports) and legacyRewardsUrl are config-fixed earn block present on 1 + 137 only (and 80002 under E2E_AMOY_LOCAL) frontend/src/config/networks.js:69 (MERKL_DISTRIBUTOR), :79-83 (earnConfig), :622,859 FeeRouter.depositToVaultWithFee takes the vault as a parameter and forwards into IERC4626contracts/fees/FeeRouter.sol:7. The local E2E stand-in is contracts/mocks/MockERC4626Vault.sol
USDC / USDT / stablecoins Native USDC: 1 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 10 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, 137 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, 8453 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, 42161 0xaf88d065e77c8cC2239327C5EDb3A432268e5831, 80002 0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582. USDT (1) 0xdAC17F958D2ee523a2206206994597C13D831ec7. Mordor 63 Classic USD (USC) 0xDE093684c796204224BC081f937aa059D903c52a frontend/src/lib/screening/sources.js:82-90 (issuer list, native issuers only); paymentToken in each deployments/*-v2.json; stablecoin blocks in networks.js (e.g. :840-846) Wrapped native: wmatic 137 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270, 63 0x1953cab0E5bFa6D4a9BaD6E05fD46C1CC6527a5a, 80002 0x0ae690AAD8663aaB12a671A6A0d74242332de85f; WETH (1) 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 (networks.js:850)
Chainalysis Sanctions Oracle 0x40C57923924B5c5c5455c48D93317139ADDaC8fb on 1, 10, 137, 42161; Base 8453 differs: 0x3A91A31cB3dC49b4db9Ce721F50a9D076c8D739B those five frontend/src/lib/screening/sources.js:63-71 (CHAINALYSIS_ORACLES); interface contracts/interfaces/IChainalysisSanctionsOracle.sol Read via isSanctioned(address). SanctionsGuard treats a configured-but-unreachable oracle as deny-all; an unset oracle (address(0)) means deny-list-only, the deliberate config on networks without it (Amoy uses MockSanctionsOracle 0xa5F0bB3a63C55721078be5ee80653E9cB0927D9E)
Issuer freeze lists Circle isBlacklisted on the native USDC of 1/10/137/8453/42161/80002; Tether isBlackListed on mainnet USDT. Different selectors that hash differently frontend/src/lib/screening/sources.js:78-97 Only native issuer contracts are listed — a bridged token (Polygon PoS USDT, Arbitrum bridged USDT) is a different contract with no freeze function and would report unreadable
Polymarket CTF (Gnosis Conditional Token Framework) 137 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045; 80002 mock 0x95F40ea10E6C51892783c4E7721Ada1425917e22 137 (80002 = MockPolymarketCTF) frontend/src/config/networks.js:609-612; polymarketCTF in deployments/{polygon,amoy,hardhat}-*.json conditionId = keccak256(oracle, questionId, outcomeSlotCount); resolution [1,0]/[0,1]
Polymarket CLOB / Gamma / Data HTTP only — no on-chain address in this repo. gamma-api.polymarket.com, data-api.polymarket.com; CLOB key + L2 creds are gateway-only secrets 137 only (Predict hides off 137) services/relay-gateway/src/config/index.js:640-672; networks.js:611 Builder code is bytes32 env POLYMARKET_BUILDER_CODE (unset ⇒ orders post unattributed, never blocked); taker fee default 50 bps hard-capped 100, maker 0 capped 50 — out-of-range fails boot
UMA Optimistic Oracle V3 Address held in the adapter's own state, not in a config file 137, 80002 interface contracts/interfaces/IOptimisticOracleV3.sol; adapter contracts/oracles/UMAOptimisticOracleV3Adapter.sol Local stand-in contracts/test/MockOptimisticOracleV3.sol
Chainlink (aggregators + Functions router) Per-condition feed addresses in conditions/allowedFeeds; router is immutable on the Functions adapter 137, 80002 contracts/oracles/ChainlinkDataFeedOracleAdapter.sol:26-30; ChainlinkFunctionsOracleAdapter.sol:28 @chainlink/contracts is pinned exactly — a floating 1.3.0→1.5.0 changed this adapter's bytecode (CLAUDE.md spec-075)
Perps venues (read-only, spec 082) Gains diamond: 42161 0xFF162c694eAA571f685030649814282eA457f169, 8453 0x6cD5aC19a07518A8092eEFfDA4f1174C72704eeb, 137 0x209A9A01980377916851af2cA075C2b170452018. GMX v2 (Arbitrum only): exchangeRouter 0x7dE39FF2e232A2203196788d37e234cF8F1b83f1, router 0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6 (approval target), reader 0xfA26cBb46e2614609406de08CA1Dc7f70a684184, dataStore 0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8, orderVault 0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5, eventEmitter 0xC8ee91A54287DB53897056e12D9819156D3822Fb, referralStorage 0xe6fab3F0c7199b0d34d7FbE83394fc0e0D06e99d. Hyperliquid: no address — non-EVM 42161, 8453, 137 frontend/src/config/perps.js:106-110,129-137 "a GMX entry for any other chain would be a fabricated address"
Staking (local E2E stand-ins only) Lido steth 0x82e01223d51Eb87e16A03E24687EDF0F294da6f1, wsteth 0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3; sPOL token 0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650, controller 0xc351628EB244ec633d5f21fBD6621e1a683B1181; Polygon stakeManager 0xFD471836031dc5108809D173A067e8486B9047A3 local E2E node only (E2E_AMOY_LOCAL, import.meta.env.DEV-guarded) frontend/src/config/networks.js:145-190 (localStakingConfig) Real mainnet staking config is declared per-network elsewhere; the StakingRouter itself is undeployed
Semaphore / Poseidon Mordor 63 semaphoreVerifier 0x3A6397C09A287A18af58Bd4711Aebd6c56467B6D, poseidonT3 0xC855eCa5f522Be73cEF1E3Db2bd1E66B2287e687 63 deployments/mordor-chain63-v2.json Abandoned with the ZK pool design — Semaphore was removed so ETC/Mordor could be unblocked

6. Flags: undeployed, planned, legacy, or inconsistent

# Item Status Evidence
1 FundingPoolFactory / FundingPool (spec 103) Code complete, deployed nowhere. No fundingPoolFactory key in any deployments/*-v2.json; '' in the Mordor and Polygon client maps; registered in the storage gate and has a deploy script. Locally deploy:local:funding is the last step of setup:e2e (#1289) contracts/pools/FundingPoolFactory.sol; scripts/deploy/check-storage-layout.js:54; frontend/src/config/contracts.js
2 StakingRouter (spec 066) Undeployed everywhere ('' in every map that declares it), yet two of its fee services (stake.lido, stake.polygon) are registered on the FeeRouter contracts/staking/StakingRouter.sol; feeServices.js:24-25
3 StandardDAOFactory Undeployed; and on Mordor it never will be — pre-Cancun EVM cannot run OZ 5.4.0 Governor (MCOPY), issue #1268. The absence is the answer frontend/src/config/contracts.js:43-47
4 p256Verifier Recorded as null on all eight chains — slot written down, never deployed deployments/*-v2.json
5 CallsignRegistry Polygon 137 only. Mordor + Amoy carry ''; frontend soft-fails to raw addresses / ENS when undeployed frontend/src/config/contracts.js
6 WagerRegistryIntents missing on Amoy 137 and 63 record the facet; 80002 does not — the testnet membership chain has no gasless twin facet. Self-submit is unaffected (never-stranded), but a gasless intent there has no target deployments/amoy-chain80002-v2.json vs polygon-/mordor-
7 FeeRouter absent on ETC 61 61 has custody + account stack only; no fee router, no bridge/liquidity routers (neither Across nor Uniswap exists there) deployments/etc-chain61-v2.json
8 bridge-and-liquidity.md is stale Says "neither router is deployed on any network yet (#966)"; both are recorded on all five EVM mainnets. The open half of #966 is the admin handoff off the deployer EOA — treat DEFAULT_ADMIN_ROLE there as a single hot key docs/developer-guide/bridge-and-liquidity.md:25 vs deployments/*-v2.json; CLAUDE.md
9 Mordor unrecorded-authority write-off 23 unrecorded Mordor contracts, ~60 sole-held roles and 62 Ownable owners still held by the compromised deployer EOA — deliberately abandoned. Do not depend on them; do not read their state as trustworthy deployments/mordor-chain63-v2.json legacyContractsWriteOff
10 Treasury on 1/10/8453/42161 Still the superseded admin Safe 0x8cc564E3…c3fa, whose owner set is documented as having included a compromised hot EOA + a Polygon-only passkey contract deployments/{mainnet,optimism,base,arbitrum}-*.json vs deployments/admin-safe.json
11 FriendGroupMarketFactory (v1) Legacy. Source only in contracts-archive/; no live network configures its address; retained in the client only as a deploy-block + explicit legacy fallback in ledger/report/stats readers contracts-archive/markets/FriendGroupMarketFactory.sol; readers listed in §1.10
12 Semaphore/ZK wager pools Abandoned on Mordor; addresses recorded but "intentionally NOT wired here" frontend/src/config/contracts.js:48-51
13 Sepolia 11155111 / Hoodi 560048 In NETWORKS but in no NETWORK_CONTRACTS entry — zero contracts, no subgraph frontend/src/config/networks.js:897,974; contracts.js:370-381
14 Amoy mocks mockPolymarketCTF 0x95F40ea1…7e22, mockSanctionsOracle 0xa5F0bB3a…7D9E are recorded on the testnet membership chain — real reads, mock upstreams deployments/amoy-chain80002-v2.json mocks
15 Planned, not built FinOps catalogues miniapp licenses and wager platform fee as planned — "exist NOWHERE (no contract has a fee)". Perps execution is FR-018-barred (read-only; an execution wrapper is a follow-up spec) CLAUDE.md spec-089 / spec-082 guardrails
16 Address collisions across chains Nonce-derived reuse means the same literal names different contracts on different chains (Ethereum's liquidityRouter proxy == the BridgeRouter proxy address on 10/8453/42161). Never carry a label across a chain boundary scripts/deploy/check-storage-layout.js:118