Smart Contracts
GlobalPVP contracts are deployed on Base Mainnet (chain ID 8453, production) and Base Sepolia (chain ID 84532, staging).
Contract Addresses
| Contract | Base Mainnet (8453) | Base Sepolia (84532) |
|---|---|---|
| NukeGame | 0x3d54428cc29b0db83f4a1dc4e3493fcd462b94dc | 0x7f99117285cefcdFdD99c576A477A7DDba358163 |
| GovernanceVotingV2 (ZK roundtable) | 0x7263cc65f1688fdcf2b412983c45012171f330c2 | 0xa682896de2018e47aD02fc9F27d3FD04f946f571 |
| HonkVerifier (ZK verifier) | not yet deployed | 0x189d3fB06Ed8B01B7b64aB81DFc24EA16a180e1C |
| NukeGameHook | 0xdc86a826d4e794b34b25eec5d9c9b17f5dc05acc | 0x65a38F83110a06ea7202341b7724CEE8A6f75acc |
| PrizePot | 0x44678424863f7ed2A6a1876e46b680862e539B70 | 0xB835C5D8756eCa32e0Cd89eD5BC441E626BA07E9 |
| CountryTokenFactory | 0x71ff81e2a5e45845db4ae26acaf3f58fe06578e0 | 0x7B5a414e611a6072057647C7150f336aDd457425 |
| CountrySwapRouter | 0x13DDdcB010e065da90D989436EaeC743fC27C38B | 0x497ff7f96f03ad4238ade5753c85a7e3d70d4f30 |
| PSABoard | 0x57897019d671Ec99B2a714f8C023eDFdCB1767eB | 0xb2263f89d61039088c3e14aefa717958af9a62ed |
| PredictionMarket | 0x366898AB2cC77e225632Bd69DECe4CA886Aa637f | 0x03CBC81837B0881510EeB3EbBCc11c5f86b388eb |
| Uniswap V4 PoolManager | 0x498581fF718922c3f8e6A244956aF099B2652b2b | 0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408 |
| Chainlink VRF V2.5 Wrapper | 0xb0407dbe851f8318bd31404A49e658143C982F23 | 0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed |
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 withprevrandaoif 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 remainswinningCountry— The bytes2 code of the last surviving countryprizePot— 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 requestedCountryNuked(countryCode, roundId, nukedBy)— Phase 2: nuke completedNukeCancelled(roundId)— VRF timeout fallback triggeredGameOver(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
keccak256for 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 swapsafterSwap— Collects fee on exact-output swaps, updates volatility trackingbeforeRemoveLiquidity— Only allows the NukeGame contract to remove liquidityafterInitialize— Seeds initial volatility state
Fee distribution:
- Fees are split between
protocolFeeRecipient(70% default) andtreasuryRecipient/ 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 whentriggerVote()fires. Starts a new round and emitsVoteStarted(roundId, immuneCountry, voteEnd)setRankingRoot(roundId, root, aliveCount)— Keeper posts a Merkle root ofPoseidon(countryCode, rank, topHolder)leaves so voters can generate proofscastVote(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 plaintextrevealAndFinalize(roundId, reveals[])— Keeper-only. Decrypts all votes, verifies each keccak commitment matches, tallies weighted votes, emits the targetfinalize(roundId)— Returns the tallied target to NukeGame duringexecuteNukegetRoundInfo(roundId)— View round data: immune country, ranking root, alive count, voteEnd, finalized, targetkeeperPublicKey()— 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 isPoseidon(2, roundId, countryCode), so observers can see which country voted (turnout) but not who they voted forVoteRevealed(roundId, voterCountry, targetCountry, weight)— emitted N times duringrevealAndFinalizeVoteFinalized(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 approvalpreviewClaim(tokenAmount)— View: returns the ETH amount a claim would yieldsweep()— Emergency withdrawal of all ETH (owner only)
Key state:
snapshotPot— ETH balance frozen at finalizationsnapshotSupply— Winning token total supply at finalizationtotalClaimed— Running total of ETH claimedfinalized— 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 countrytotalTokens()— 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 pricepostMessage(message)— Post a PSA (payable)current()— View the active PSAgetRecent(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