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