Skip to main content

Smart Contracts

GlobalPVP contracts are deployed on Base Mainnet (chain ID 8453, production) and Base Sepolia (chain ID 84532, staging).

Contract Addresses

ContractBase Mainnet (8453)Base Sepolia (84532)
NukeGame0x3d54428cc29b0db83f4a1dc4e3493fcd462b94dc0x7f99117285cefcdFdD99c576A477A7DDba358163
GovernanceVotingV2 (ZK roundtable)0x7263cc65f1688fdcf2b412983c45012171f330c20xa682896de2018e47aD02fc9F27d3FD04f946f571
HonkVerifier (ZK verifier)not yet deployed0x189d3fB06Ed8B01B7b64aB81DFc24EA16a180e1C
NukeGameHook0xdc86a826d4e794b34b25eec5d9c9b17f5dc05acc0x65a38F83110a06ea7202341b7724CEE8A6f75acc
PrizePot0x44678424863f7ed2A6a1876e46b680862e539B700xB835C5D8756eCa32e0Cd89eD5BC441E626BA07E9
CountryTokenFactory0x71ff81e2a5e45845db4ae26acaf3f58fe06578e00x7B5a414e611a6072057647C7150f336aDd457425
CountrySwapRouter0x13DDdcB010e065da90D989436EaeC743fC27C38B0x497ff7f96f03ad4238ade5753c85a7e3d70d4f30
PSABoard0x57897019d671Ec99B2a714f8C023eDFdCB1767eB0xb2263f89d61039088c3e14aefa717958af9a62ed
PredictionMarket0x366898AB2cC77e225632Bd69DECe4CA886Aa637f0x03CBC81837B0881510EeB3EbBCc11c5f86b388eb
Uniswap V4 PoolManager0x498581fF718922c3f8e6A244956aF099B2652b2b0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408
Chainlink VRF V2.5 Wrapper0xb0407dbe851f8318bd31404A49e658143C982F230x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed

The V1 GovernanceVoting contract (token-weighted voting) has been deprecated in favour of GovernanceVotingV2 above. Historical Sepolia V1 address: 0xd6967ed5920e8699c36debb2895893f029a32ab0; historical mainnet V1 address: 0x555767e731A55b46f1FCb344Ac47ef57B7fE5dc6 — read-only references for old rounds.

:::info HonkVerifier on Mainnet The Noir-generated HonkVerifier is not yet deployed on Base Mainnet. Until it is wired in via GovernanceVotingV2.setVerifier(), ZK vote proof checks are bypassed. Deploy it standalone (without via-IR) and pass the address via the VERIFIER env to a future redeploy or call setVerifier on the existing governance contract. :::

Contract Overview

NukeGame

The main game controller. Manages the game lifecycle, country initialization, staircase liquidity distribution, vote triggering, nuke execution, ETH redistribution via buyback swaps, and game-over detection. Creates the PrizePot contract on deployment. Inherits from Chainlink's VRFV2PlusWrapperConsumerBase for verifiable randomness.

Key functions:

  • startGame() — Begins the first countdown (owner only)
  • triggerVote() — Opens voting when countdown expires (anyone)
  • executeNuke(roundId) — Phase 1: finalizes the vote, requests Chainlink VRF randomness (anyone)
  • fulfillRandomWords(requestId, randomWords) — Phase 2: VRF callback that executes the nuke and redistributes ETH (called by Chainlink VRF wrapper)
  • cancelPendingNuke() — Fallback: executes the nuke with prevrandao if VRF callback doesn't arrive within 1 hour (owner only)
  • withdrawVrfBalance(to, amount) — Withdraw ETH held for VRF payments (owner only, blocked during active nukes)
  • initializeCountry(name, symbol, countryCode) — Deploys and initializes a country (owner only)
  • batchInitializeCountries(names, symbols, codes) — Batch version of the above

Key state:

  • gameOver — True when only one country remains
  • winningCountry — The bytes2 code of the last surviving country
  • prizePot — Address of the PrizePot contract (created in constructor)
  • nukeInProgress — True between VRF request and callback (blocks new votes/nukes)
  • activeVrfRequestId — The in-flight Chainlink VRF request ID (0 when idle)

Events:

  • GameStarted(countdownEnd)
  • CountryInitialized(countryCode, token, poolId)
  • VoteTriggered(roundId, winnerCountryCode)
  • NukeRequested(requestId, roundId, winnerCode) — Phase 1: VRF randomness requested
  • CountryNuked(countryCode, roundId, nukedBy) — Phase 2: nuke completed
  • NukeCancelled(roundId) — VRF timeout fallback triggered
  • GameOver(winningCountryCode, prizePotAddress, potBalance)

Chainlink VRF integration:

  • Uses VRF V2.5 direct funding (native ETH payment, no LINK needed)
  • Requests 1 random word per nuke; derives 3 values via keccak256 for buyback target selection
  • Contract must hold ETH to pay VRF fees (funded at deployment, replenishable via receive())

NukeGameHook

A Uniswap V4 hook that implements the dynamic fee system, anti-snipe protection, nuke window fees, dual-recipient fee distribution, and liquidity removal restrictions.

Permissions:

  • beforeSwap — Collects fee on exact-input swaps
  • afterSwap — Collects fee on exact-output swaps, updates volatility tracking
  • beforeRemoveLiquidity — Only allows the NukeGame contract to remove liquidity
  • afterInitialize — Seeds initial volatility state

Fee distribution:

  • Fees are split between protocolFeeRecipient (70% default) and treasuryRecipient / PrizePot (30% default)
  • Split ratio configurable via setTreasurySplit()
  • Base fee (flat + dynamic) hard-capped at 5% by immutable ABSOLUTE_MAX_FEE_BPS
  • Flat fee and max total fee are admin-configurable via setFees()

Key state:

  • Per-pool volatility tracking (reference tick, accumulator, timestamps)
  • Anti-snipe activation timestamps per pool
  • Global nuke window flag
  • flatFeeBps (default 100), maxTotalFeeBps (default 500), treasurySplitBps (default 3000)

GovernanceVotingV2

Secret-ballot, ZK-proved roundtable voting. Each round, the top holder of every alive country can cast one rank-weighted encrypted vote. Votes are decrypted only after the voting window closes, then tallied on-chain.

Key functions:

  • startVoteRound(immuneCountry) — Called by NukeGame when triggerVote() fires. Starts a new round and emits VoteStarted(roundId, immuneCountry, voteEnd)
  • setRankingRoot(roundId, root, aliveCount) — Keeper posts a Merkle root of Poseidon(countryCode, rank, topHolder) leaves so voters can generate proofs
  • castVote(nullifier, weight, voteCommitment, encryptedVote, proof) — A leader submits their encrypted vote. The ZK proof attests they're the top holder of some alive country at some rank without revealing which. voteCommitment = keccak256(voterCountry, targetCountry, salt) binds the keeper to the plaintext
  • revealAndFinalize(roundId, reveals[]) — Keeper-only. Decrypts all votes, verifies each keccak commitment matches, tallies weighted votes, emits the target
  • finalize(roundId) — Returns the tallied target to NukeGame during executeNuke
  • getRoundInfo(roundId) — View round data: immune country, ranking root, alive count, voteEnd, finalized, target
  • keeperPublicKey() — The NaCl-box public key the frontend encrypts votes to

Weight formula: weight = aliveCount - rank + 1. Rank 1 = immune country (most powerful vote, cannot be nuked).

Events:

  • VoteStarted(roundId, immuneCountry, voteEnd)
  • RankingRootSet(roundId, root, aliveCount)
  • VoteCast(roundId, voteIndex, nullifier, weight) — nullifier is Poseidon(2, roundId, countryCode), so observers can see which country voted (turnout) but not who they voted for
  • VoteRevealed(roundId, voterCountry, targetCountry, weight) — emitted N times during revealAndFinalize
  • VoteFinalized(roundId, targetCountry)

Trust model: the keeper cannot forge votes (keccak commitments are posted before the keeper sees plaintext) and cannot invent votes (every reveal must match a previously stored commitment). It can refuse to reveal, in which case the round stalls — a future V3 will replace the single keeper with a threshold-decryption committee.

See Voting & Nuking for the full circuit, keeper, and frontend flow.

HonkVerifier

Noir-generated UltraHonk verifier contract. Called from GovernanceVotingV2.castVote to verify each vote proof. Deployed standalone (not via-IR) so the 14.8KB bytecode fits under Base Sepolia's EIP-170 contract-size limit.

PrizePot

Accumulates ETH from the hook's 30% treasury fee split. When the game ends, the winning country's token holders forfeit tokens to claim proportional ETH (the RFV).

Key functions:

  • finalize(winningToken) — Called by NukeGame when the last country remains. Snapshots pot balance and token supply (game only)
  • claim(tokenAmount) — Forfeit winning tokens for proportional ETH. Requires prior approval
  • previewClaim(tokenAmount) — View: returns the ETH amount a claim would yield
  • sweep() — Emergency withdrawal of all ETH (owner only)

Key state:

  • snapshotPot — ETH balance frozen at finalization
  • snapshotSupply — Winning token total supply at finalization
  • totalClaimed — Running total of ETH claimed
  • finalized — Whether the pot has been finalized

Events:

  • Finalized(winningToken, pot, supply)
  • Claimed(user, tokenAmount, ethAmount)
  • Swept(to, amount)

CountrySwapRouter

User-facing trading interface. Simplifies buying and selling country tokens by wrapping Uniswap V4 pool operations.

Key functions:

  • buyTokens(countryCode, minTokensOut) — Buy tokens with ETH (payable)
  • sellTokens(countryCode, tokenAmount, minEthOut) — Sell tokens for ETH

CountryTokenFactory

Deploys and tracks all country token contracts. Maintains a mapping from country codes to token addresses.

Key functions:

  • countryTokens(bytes2 code) — Get token address for a country
  • totalTokens() — Total number of deployed country tokens

CountryToken

Standard ERC20 with a nuked flag and country code identifier. Each has a fixed supply of 1 billion tokens.

PSABoard

Pay-to-post announcement board with dutch auction pricing. Floor price 0.005 ETH, doubles on each purchase, decays back to floor over 6 hours.

Key functions:

  • getCurrentPrice() — Current posting price
  • postMessage(message) — Post a PSA (payable)
  • current() — View the active PSA
  • getRecent(count) — View recent PSA history

Technical Details

  • Solidity version: 0.8.26
  • EVM version: Cancun
  • Optimizer: Enabled, 200 runs, via-IR
  • Uniswap V4: Pools use dynamic fees (0x800000 flag) with tick spacing 60
  • All liquidity is protocol-owned — No external LPs. The NukeGame contract owns all positions.
  • LP fees are zero — All trading fees are split between protocol (70%) and Prize Pot (30%) via the hook