# Moral Hazard — Agent Guide (AGENTS.md) > A machine-readable playbook for autonomous agents that want to **play** Moral Hazard > directly against the on-chain contracts — no frontend required. Canonical copy: > https://moralhazard.stream/AGENTS.md > > **Full handbook as plain text: https://moralhazard.stream/handbook.md** — every question and answer, generated > from the site build. Prefer it over https://moralhazard.stream/handbook: the human page keeps its answers inside collapsed > disclosure elements, so any reader that extracts *visible* text gets the headings and none of the > content. Moral Hazard is a game-theoretic, money-streaming experiment on Superfluid. It resolves a single question: **"Do you think humans are, in general, Good or Bad?"** Two sides compete, continuously and on-chain: - **Good side** — one holder at a time pays a claim price to hold the *Good Spot*, and in return receives the aggregate stream of every Bad player. - **Bad side** — any number of players stream tokens into the game continuously, and receive a proportional cut of **every future Good-Spot claim** through a distribution pool. The core tension: Good holders want to hold long enough to break even on incoming streams; Bad streamers want frequent ownership changes, because they earn a share each time someone claims. > ⚠️ **Not financial advice. Outcomes depend on other players and are uncertain.** Start on the > **testnet** (free fUSDCx on Base Sepolia — see the start guide at https://moralhazard.stream/start/base-sepolia/fUSDCx) > before risking anything of value. Every contract is source-verified; read the rules from the > contracts, not from this prose, when in doubt. --- ## 1. Mechanics you need to know ### Core - **Claim price.** Holding the Good Spot costs the current *asked amount*. `1.337%` of every claim is a protocol fee; the remaining `98.663%` is distributed to Bad streamers. Each claim raises the next asked amount by **exactly 1%** — a fixed 1%, not a minimum. The floor clamp in _minNextAmount cannot bind after a claim, because the payment was itself never below the floor. - **Price decay** (anti–"hot potato"). The asked amount is bound to the holder's earnings: - **Before break-even** (holder still in the red): no decay — the price holds at the full asked amount. - **From break-even up to ~133.7% of what they paid:** the asked amount decays *linearly* down toward a floor just above what the holder paid (it shaves the 1% claim bump down to roughly `+0.9%`). A faster inflow reaches this sooner. - **Beyond ~133.7%:** the price keeps decaying ~`0.1%/day` (wall-clock) toward the floor. - If inflow drops, break-even moves out and the price ticks back up; if every streamer leaves, the price snaps back to the full asked amount. - **Authoritative source: call `getEffectiveAskedAmount()` on the game** — it computes the exact current price on-demand. Don't reimplement decay if you can read it. - **Aggregate stream.** While you hold the Good Spot you receive ~`98.663%` of the total Bad flow rate in real time; the other `1.337%` is split off as a continuous **donation stream**. ### Secondary - **Deposit (Bad side).** Opening a Bad stream requires locking a deposit `= flowRate × depositDuration × depositMultiplier` (read both factors from the DepositManager). Your distribution **pool units activate only after the deposit is locked**. Close **before** the lock matures → the deposit is **forfeited**. **Lowering your flow rate while the deposit is still locked also forfeits the difference** (the part above what the lower rate now requires), and the reduced deposit **re-locks** for a fresh period — so you can't dodge the deposit by claiming, dropping your rate, and waiting out the lock. A forfeited deposit is split like a claim: **1.337% to the project, the rest into the distribution pool.** After the lock matures the deposit is freely reclaimable, so lowering the rate then costs nothing. - **Change donation recipient (Good side).** The current holder picks the donation recipient from a **validated on-chain list** and can change it at any time while holding the spot. Read the list from the DonationManager (`getOptions`) — never invent an address; an unlisted recipient is rejected. - **NFTs.** A **GoodNFT** (transferable ERC-721) represents the Good Spot — **transferring it also redirects all incoming streams to the new owner**, and is the only way the holder can "exit" besides being out-claimed. A **BadNFT** (soulbound ERC-5192) is minted on stream open and burned on close. A **ProfileNFT** (soulbound, optional, cosmetic) stores UI preferences. - **Pause / emergency exit.** Admins (via a backup multisig) can pause in an emergency: no new claims/streams/rate-changes, but existing streams keep flowing. While paused, anyone can `emergencyExit()` — Bad: stream closed + deposit returned (lock waived); Good: outflows stopped, GoodNFT burned, spot reset. The original claim payment is **not** refunded (it was already distributed). - **Pending changes.** All admin changes go through a **TimelockController** delay (see each chain below). **Check for pending timelock operations before committing capital** — the change is announced on-chain during the delay window. ### Super Token types — know what you hold Every game streams a Superfluid **Super Token**, but there are three kinds, and which one a game uses determines how you acquire and exit it. Identify it on-chain with `SuperToken.getUnderlyingToken()`, or read the **Super Token type** label per game in the registry below — treat it as data, don't assume. - **Wrapper Super Token** (e.g. USDCx, fDAIx) — backed 1:1 by an underlying **ERC-20**. `getUnderlyingToken()` returns that ERC-20 address. Obtain: acquire the underlying token, `approve` it to the Super Token, then `upgrade(amount)`. Exit: `downgrade(amount)`. - **Native-asset Super Token** (e.g. ETHx) — wraps the chain's **native coin** (ETH, xDAI, …), not an ERC-20. `getUnderlyingToken()` returns `0x0`, but it exposes `upgradeByETH()` payable / `downgradeToETH(amount)`. Obtain: `upgradeByETH()` with native value (no ERC-20 approval). - **Pure / native Super Token** (e.g. SUP, FRACTION) — minted natively, **not a wrapper at all**. `getUnderlyingToken()` returns `0x0` and there is **no** wrap/unwrap path. Obtain it directly (DEX/bridge); it already streams as-is. > Integrated games span these types — some wrap an ERC-20, some wrap the chain's native coin, some are > pure native Super Tokens. **Never assume a game is a wrapper:** read its type per game (the registry > label / `getUnderlyingToken()`) and use the matching acquire/exit path. --- ## 2. Step 1 — Gather state **Pick the right source per fact — it is not "subgraph first":** - **Subgraph** for the holder-inflow **accumulator** (break-even, below) and **enumeration**: the list of active Bad streams, pool members, and distribution history. - **RPC** for **live per-account** values: `currentGoodHolder`, `totalBadFlowRate`, `getEffectiveAskedAmount`, a member's `getUnits`, your `getFlowrate`, `getDepositInfo`. These are authoritative *now*; the subgraph can lag a block or two. ### A. Superfluid subgraph (no API key) Endpoint per network: `https://subgraph-endpoints.superfluid.dev//protocol-v1` (the exact endpoint for each live chain is in the registry below). All address variables must be **lowercase hex**. **Active Bad streams into a game** — `$receiver` = game proxy, `$token` = accepted Super Token: ```graphql query ActiveStreams($receiver: String!, $token: String!) { streams( first: 1000 where: { receiver: $receiver, token: $token, currentFlowRate_gt: "0" } orderBy: currentFlowRate orderDirection: desc ) { sender { id } currentFlowRate createdAtTimestamp } } ``` **Holder-inflow accumulator** (exact break-even numerator) — `$sender` = game proxy, `$receiver` = `currentGoodHolder`, `$token` = accepted Super Token. This is the single game→holder stream (recreated on every claim, so its accumulator starts at 0 at claim time); `currentFlowRate` is **already net** of the 1.337% donation. Do **not** add a `currentFlowRate_gt` filter — a holder with no current streamers still has a real total on the latest (closed) revision: ```graphql query HolderInflowStream($sender: String!, $receiver: String!, $token: String!) { streams( first: 1 where: { sender: $sender, receiver: $receiver, token: $token } orderBy: createdAtTimestamp orderDirection: desc ) { streamedUntilUpdatedAt currentFlowRate updatedAtTimestamp createdAtTimestamp } } ``` **Distribution-pool members** (your rank / total units) — `$pool` = the game's `badPool()`: ```graphql query PoolMembers($pool: String!) { poolMembers( first: 1000 where: { pool: $pool, units_gt: "0" } orderBy: units orderDirection: desc ) { account { id } units } } ``` **Distribution history** (every Good-Spot claim payout) — `$pool` = `badPool()`: ```graphql query DistributionHistory($pool: String!) { instantDistributionUpdatedEvents( first: 100 where: { pool: $pool } orderBy: timestamp orderDirection: desc ) { id timestamp blockNumber actualAmount totalUnits operator } } ``` ### B. Live contract reads (RPC view functions) Return types matter for decoding — note the signed `int96` flow rates and `uint128` units. - **MoralHazard (game proxy):** `currentGoodHolder` (address), `askedAmount` (uint256), `getEffectiveAskedAmount` (uint256), `lastPaidAmount` (uint256), `lastClaimTimestamp` (uint256), `totalBadFlowRate` (**int96**), `badStreamerCount` (uint256), `pendingDistribution` (uint256), `badPool` (address), `acceptedToken` (address), `paused` (bool), `pendingFinalize(address)` (bool). - **GoodNFT:** `currentTokenId` (uint256). - **DepositManager (keyed by game proxy):** `getDepositInfo(game, account)` → `(uint256 deposit, uint256 activationTime, uint256 requiredDeposit)`; `depositDuration(game)` (uint256); `depositMultiplier(game)` (uint256). - **DonationManager (keyed by game proxy):** `getOptions(game)` (address[]), `getActiveRecipient(game)` (address). - **Accepted Super Token:** `getUnderlyingToken()` (address) → the wrapped ERC-20, or `0x0` for native-asset / pure Super Tokens (see *Super Token types* — tells you how to acquire and exit it). - **Superfluid:** `CFAv1Forwarder.getFlowrate(token, sender, receiver)` (**int96**) and `getFlowInfo(token, sender, receiver)` → `(uint256 lastUpdated, int96 flowrate, uint256 deposit, uint256 owedDeposit)`; pool `getUnits(member)` (**uint128**) and `getTotalAmountReceivedByMember(member)` (uint256); `GDAv1Forwarder.isMemberConnected(pool, member)` (bool). - **Convenience (optional):** a live SVG/JSON snapshot of game and profile state is served at `https://moralhazard.stream/api/nft//image?type=good|bad&id=&chainId=` and `https://moralhazard.stream/api/nft/profile/image?id=&chainId=`. --- ## 3. Step 2 — Process the data - **Received so far (the break-even numerator).** Use the **exact accumulator** from the holder-inflow stream (§2A): `received = streamedUntilUpdatedAt + currentFlowRate × (now − updatedAtTimestamp)` — already net of the 1.337% donation. Guard a stale tenure: if that stream's `createdAtTimestamp < lastClaimTimestamp`, treat `received` as `0` (it belongs to a previous holder). Do **not** use `currentRate × timeSinceClaim`: multiplying the current rate by the whole elapsed time over-counts after any inflow change. - **Break-even %.** `% = received × 100 / lastPaidAmount`. ≥ 100% means the holder has earned back what they paid (in profit). - **Effective claim price (decay).** Read `getEffectiveAskedAmount()` — it returns the exact current price on demand, from that same `received`. Conceptually: below `paid` → full `askedAmount` (no decay); from `paid` up to `1.337 × paid` → linear down toward ~`1.009 × paid`; beyond that → ~`0.1%/day` toward the floor. Just read the function; don't reimplement the decay. - **Your distribution share.** `yourUnits × 100 / totalUnits` — units equal your flow rate, so your share tracks your rate relative to all Bad streamers. - **Required deposit + buffer (before opening a Bad stream).** deposit `= flowRate × depositDuration × depositMultiplier`; Superfluid liquidation buffer `= flowRate × 14400` (4 hours). The game pulls **buffer + deposit** from your accepted-token allowance **to the game**. On a rate **increase** the game pulls only the **delta** — the extra buffer (`Δrate × 14400`) plus the extra deposit — so approve just that increment, not the full new amount. **Separately, approve the CFA forwarder `maxUint256` once per (token, wallet)** — not just the buffer. The game is a SuperApp that re-streams your inflow at an equal rate, so Superfluid backs that outflow as an **owed deposit on you (the sender)**, roughly doubling the buffer it locks; an exact-buffer approval under-approves and `setFlowrate` reverts ("SuperToken: transfer amount exceeds allowance"). - **Flow-rate units.** Streams are `wei/second`. To display/convert: `weiPerSec × secondsPerUnit`, with day = `86400`, week = `604800`, month = `2630016`. --- ## 4. Step 3 — Interact (contract writes) Token amounts are in the accepted Super Token's wei (18 decimals for Super Tokens). "Approve" means an ERC-20 `approve(spender, amount)` on the **accepted Super Token**. ### Wrap / unwrap (get a Super Token) First identify the token type (*Super Token types* above / `getUnderlyingToken()`), then: - **Wrapper:** `approve(superToken, amount)` on the underlying ERC-20, then `SuperToken.upgrade(amount)` to wrap; `downgrade(amount)` to unwrap (1:1). - **Native-asset:** `SuperToken.upgradeByETH()` payable (send native coin as `value`) to wrap; `downgradeToETH(amount)` to unwrap. No ERC-20 approval needed. - **Pure/native:** nothing to wrap — acquire the token directly and use it as-is. ### Good side 1. **Claim the Good Spot.** Three distinct selectors: - `claimGoodSpot(address recipient)` — no slippage bound (pays whatever the price is at execution). - `claimGoodSpotWithMax(address recipient, uint256 maxPrice)` — **the default**: slippage-safe. - `claimGoodSpotWithMax(address recipient, uint256 maxPrice, address[] toFinalize)` — same, plus a list of stranded streamers to settle first; only needed when `pendingFinalize` accounts exist (`[]` ⇒ the 2-arg form). - *Pre:* `approve(gameProxy, maxPrice)` — approve the **slippage cap `maxPrice`**, not just the current `getEffectiveAskedAmount()`. The price can rise (a competing claim) between your read and your tx; the contract charges the current price **up to `maxPrice`**, so an allowance sized to the stale read reverts. - `recipient` = a donation address from `DonationManager.getOptions(game)` (or the zero address for the default). `maxPrice` = the most you'll pay — a claim that pushed the price past it reverts instead of overpaying. - *Effect:* you become the holder, the GoodNFT moves to you, and all Bad streams flow to you. 2. **Change the donation recipient** (holder only): `MoralHazard.changeDonationRecipient(recipient)` with a `recipient` from `getOptions(game)`. 3. **Exit the Good Spot.** There is no active sell. Either get out-claimed, or **transfer the GoodNFT** (`transferFrom`) — which redirects the incoming streams to the new owner. ### Bad side 1. **Open / change a Bad stream.** - *Pre:* `approve(gameProxy, buffer + deposit)` on the accepted token (on an **increase**, only the delta — extra buffer + extra deposit), **and `approve(cfaV1Forwarder, maxUint256)` once per (token, wallet)** — the forwarder must cover the SuperApp's owed-deposit outflow (see §3), so an exact-buffer approval reverts. - *Call:* `CFAv1Forwarder.setFlowrate(token, gameProxy, flowRate)` (`flowRate` in wei/sec). The same call raises or lowers an existing stream. **Lowering while the deposit is locked forfeits the difference** (1.337% to the project, rest to the pool) and re-locks the remainder — so only lower a locked deposit deliberately. - *Effect:* a BadNFT is minted; once the deposit locks, your pool units activate. 2. **Receive distributions:** `GDAv1Forwarder.connectPool(badPool, "0x")` once (`badPool` = game's `badPool()`). Without connecting, your share accrues but isn't auto-received. 3. **Reclaim your deposit** after the lock matures (`activationTime + depositDuration` reached): `DepositManager.claimDeposit(gameProxy)`. 4. **Stop your Bad stream:** `MoralHazard.closeMyStream()` — one transaction that deletes the flow, settles the deposit (returned if matured, else forfeited), refunds the buffer, burns the BadNFT and zeroes your units. (Closing via `setFlowrate(token, game, 0)` works too, but `closeMyStream` is the clean atomic path.) 5. **Recovery:** if a stream was closed externally and never settled (`pendingFinalize(addr)` is true), anyone may call `MoralHazard.finalizeExit(addr)` to complete the cleanup. ### Either side - **Emergency exit (only while `paused()` is true):** `MoralHazard.emergencyExit()` — closes your position; Bad deposits are returned with the lock waived. ### Optional (cosmetic) - **ProfileNFT:** `mint(settingsHex)` / `update(settingsHex)` (a single canonical soulbound NFT; accepts an optional ETH tip as `value`). Purely UI preferences — not required to play. > Off-chain only (not on-chain, out of scope for autonomous play): token **voting** and the > **invite/participation-score** system run on the project database. Super Token transfers use > ERC-777 hooks — budget extra gas. **Gate follow-up reads on the transaction receipt:** a read issued > immediately after a write can return **stale state on load-balanced public RPCs** (you may hit a replica > that hasn't caught up) — wait for the receipt, and re-read a moment later if a value looks unchanged. Most > actions are idempotent to re-read but **not** to re-submit. To batch many reads into one round-trip, use > **Multicall3** (`0xcA11bde05977b3631167028862bE2a173976CA11` — same address on every supported chain). --- ## 5. Live registry (public games) ### Base (chainId 8453) - **Public RPC:** `https://mainnet.base.org` (public fallback — rate-limited; use your own for anything heavy) - **Explorer:** https://basescan.org - **Superfluid subgraph:** `https://subgraph-endpoints.superfluid.dev/base-mainnet/protocol-v1` - **Timelock delay on admin changes:** 48 hours (mainnet) - **Superfluid CFAv1Forwarder:** `0xcfA132E353cB4E398080B9700609bb008eceB125` - **Superfluid GDAv1Forwarder:** `0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08` - **Superfluid Host:** `0x4C073B3baB6d8826b8C5b229f3cfdC1eC6E47E74` #### USDCx on Base - **Token:** USDCx — Super USDC - **Accepted Super Token:** `0xd04383398dd2426297da660f9cca3d439af9ce1b` - **Super Token type:** wrapper — backed 1:1 by ERC-20 USDC at `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` (acquire it, `approve` the Super Token, then `upgrade`; exit via `downgrade`). - **Game (proxy):** `0x3A62B053A9131FA62B640473EF5d57A05f6480A9` - **GoodNFT:** `0xAD950F4631E6aA0be4DFef1E3C1FF03be4Ed55Ac` - **BadNFT:** `0xB121B2b77A1A6d783BD807AE15228Ed19694Cabf` - **DepositManager:** `0xD7644b49ED0EF8bD9EEceBE9151278E4Bcd1755C` (calls keyed by the game proxy) - **DonationManager:** `0xb4392B5a49c3a9c032A92f7d0F8b2222337Ec5c2` (calls keyed by the game proxy) - **Play URLs:** https://moralhazard.stream/good/base/USDCx · https://moralhazard.stream/bad/base/USDCx #### SUP on Base - **Token:** SUP — Superfluid Token - **Accepted Super Token:** `0xa69f80524381275a7ffdb3ae01c54150644c8792` - **Super Token type:** pure/native Super Token — not a wrapper; no wrap/unwrap, acquire it directly. - **Game (proxy):** `0x1FF0cEbDabd7a216Ee3948AA050D6c6D6fD78F1E` - **GoodNFT:** `0xAD950F4631E6aA0be4DFef1E3C1FF03be4Ed55Ac` - **BadNFT:** `0xB121B2b77A1A6d783BD807AE15228Ed19694Cabf` - **DepositManager:** `0xD7644b49ED0EF8bD9EEceBE9151278E4Bcd1755C` (calls keyed by the game proxy) - **DonationManager:** `0xb4392B5a49c3a9c032A92f7d0F8b2222337Ec5c2` (calls keyed by the game proxy) - **Play URLs:** https://moralhazard.stream/good/base/SUP · https://moralhazard.stream/bad/base/SUP ### Base Sepolia (chainId 84532) - **Public RPC:** `https://sepolia.base.org` (public fallback — rate-limited; use your own for anything heavy) - **Explorer:** https://sepolia.basescan.org - **Superfluid subgraph:** `https://subgraph-endpoints.superfluid.dev/base-sepolia/protocol-v1` - **Timelock delay on admin changes:** 20 minutes (testnet) - **Superfluid CFAv1Forwarder:** `0xcfA132E353cB4E398080B9700609bb008eceB125` - **Superfluid GDAv1Forwarder:** `0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08` - **Superfluid Host:** `0x109412E3C84f0539b43d39dB691B08c90f58dC7c` #### fUSDCx on Base Sepolia - **Token:** fUSDCx — Super fUSDC Fake Token - **Accepted Super Token:** `0x1650581F573eAd727B92073B5Ef8B4f5B94D1648` - **Super Token type:** wrapper — backed 1:1 by ERC-20 fUSDC at `0x6B0dacea6a72E759243c99Eaed840DEe9564C194` (acquire it, `approve` the Super Token, then `upgrade`; exit via `downgrade`). - **Game (proxy):** `0xCF56AE77dE98E8bC5CD606d83154b9910850b897` - **GoodNFT:** `0xC881b0143268Df0306Fd13a1188B2D8c6ab61ed3` - **BadNFT:** `0x612DCBF3Fc75ad584CB0275a3092cBc8f26719F2` - **DepositManager:** `0x65D6765F300E17Af18b25893A6B47c642501Bf5B` (calls keyed by the game proxy) - **DonationManager:** `0x765DF35f31A24D4dA6067A428F70D805C7e8a422` (calls keyed by the game proxy) - **Play URLs:** https://moralhazard.stream/good/base-sepolia/fUSDCx · https://moralhazard.stream/bad/base-sepolia/fUSDCx ### Machine-readable registry Parse this instead of scraping the prose above — the same public data, typed, generated from the live registry (`timelockDelaySeconds` is an approximation; read `getMinDelay()` on the timelock for the authoritative value): ```json { "origin": "https://moralhazard.stream", "chains": [ { "chainId": 8453, "rpc": "https://mainnet.base.org", "subgraph": "https://subgraph-endpoints.superfluid.dev/base-mainnet/protocol-v1", "explorer": "https://basescan.org", "timelockDelaySeconds": 172800, "superfluid": { "cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125", "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08", "host": "0x4C073B3baB6d8826b8C5b229f3cfdC1eC6E47E74" }, "shared": { "goodNFT": "0xAD950F4631E6aA0be4DFef1E3C1FF03be4Ed55Ac", "badNFT": "0xB121B2b77A1A6d783BD807AE15228Ed19694Cabf", "depositManager": "0xD7644b49ED0EF8bD9EEceBE9151278E4Bcd1755C", "donationManager": "0xb4392B5a49c3a9c032A92f7d0F8b2222337Ec5c2" }, "games": [ { "token": "USDCx", "gameProxy": "0x3A62B053A9131FA62B640473EF5d57A05f6480A9", "acceptedToken": "0xd04383398dd2426297da660f9cca3d439af9ce1b", "underlyingToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "underlyingSymbol": "USDC", "superTokenType": "wrapper" }, { "token": "SUP", "gameProxy": "0x1FF0cEbDabd7a216Ee3948AA050D6c6D6fD78F1E", "acceptedToken": "0xa69f80524381275a7ffdb3ae01c54150644c8792", "underlyingToken": null, "underlyingSymbol": null, "superTokenType": "pure" } ] }, { "chainId": 84532, "rpc": "https://sepolia.base.org", "subgraph": "https://subgraph-endpoints.superfluid.dev/base-sepolia/protocol-v1", "explorer": "https://sepolia.basescan.org", "timelockDelaySeconds": 1200, "superfluid": { "cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125", "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08", "host": "0x109412E3C84f0539b43d39dB691B08c90f58dC7c" }, "shared": { "goodNFT": "0xC881b0143268Df0306Fd13a1188B2D8c6ab61ed3", "badNFT": "0x612DCBF3Fc75ad584CB0275a3092cBc8f26719F2", "depositManager": "0x65D6765F300E17Af18b25893A6B47c642501Bf5B", "donationManager": "0x765DF35f31A24D4dA6067A428F70D805C7e8a422" }, "games": [ { "token": "fUSDCx", "gameProxy": "0xCF56AE77dE98E8bC5CD606d83154b9910850b897", "acceptedToken": "0x1650581F573eAd727B92073B5Ef8B4f5B94D1648", "underlyingToken": "0x6B0dacea6a72E759243c99Eaed840DEe9564C194", "underlyingSymbol": "fUSDC", "superTokenType": "wrapper" } ] } ] } ``` --- _This document is generated from the live game registry, so the addresses above always reflect what is currently deployed and public. Authoritative game rules live in the verified contracts; this guide is a convenience map, not a warranty._