Unigen Genesis
A 3,000-piece skeleton-unicorn collection that auto-mints through a Uniswap V4 hook on Base. No mint button, no waitlist, no allowlist drama — just buy $UGEN above the threshold and your unigen appears.
Overview
Unigen Genesis is built around a single idea: the trade is the mint. Three layers stack to make that work:
- $UGEN — a fixed-supply 1B ERC-20 that exists on a self-launched Uniswap V4 pool.
- UnigenHook — a V4 hook attached to that pool at initialization. It listens to
afterSwapand triggers the mint when conditions match. - UgenNFT — a fixed-cap ERC-721 collection with on-chain rarity, power, and a wallet-age registry. Only the hook can mint. No public mint function.
Tokenomics
$UGEN is the protocol's utility token. Fixed supply, no inflation, no mint after deploy.
- Symbol
- $UGEN
- Total supply
- 1,000,000,000 (1B)
- Decimals
- 18
- Chain
- Base mainnet
- Mint authority after deploy
- Nobody. Fixed.
- Trading pair
- ETH / $UGEN on Uniswap V4
- LP fee
- Dynamic (1% default, 0.01% for tier-S buyers)
- Initial LP lock
- Locked at launch
Distribution
A majority of supply is seeded into the V4 pool at launch (with a paired ETH amount), establishing the initial price. No vesting schedule, no airdrop, no public mint. The token lives or dies by trading liquidity.
Season rewards stream
A configurable portion of $UGEN can be allocated to SeasonRewards by the contract owner at season start. That pool is then claimable by NFT holders, weighted by their token power (see Season Rewards).
How It Works
The end-to-end mint path, from your wallet click to a Genesis NFT landing in your wallet, in one V4 transaction:
1. The Swap
$UGEN is a public Uniswap V4 token on Base. Anyone can buy it from the V4 pool — through our buy widget, through the public Uniswap UI, through any aggregator that routes to V4 pools. The pool, the hook, and the token all live on-chain and respond to anyone.
Most routes mint the NFT to the wallet that signs the trade. When the swap forwards your address in hookData (our buy widget does this automatically), the hook credits that exact address. Otherwise the hook falls back to tx.origin — the EOA that signed the transaction — so buys through the public Uniswap UI and most aggregators still mint the NFT to you. The one exception is ERC-4337 smart wallets (Coinbase Smart Wallet, Safe in 4337 mode) — those execute through a bundler, so tx.origin resolves to the bundler. Smart wallet users should use the unigen.dev widget for the NFT to land correctly.
- Buy on unigen.dev widget
- Get UGEN + NFT (always correct)
- Buy on app.uniswap.org (public UI) — EOA wallet
- Get UGEN + NFT
- Buy via aggregator (1inch / Matcha) — EOA wallet
- Get UGEN + NFT
- Buy via ERC-4337 smart wallet through any route except our widget
- Get UGEN — NFT mints to bundler
- Mint threshold per buy — Base
- 0.05 ETH
- Mcap unlock for NFT minting
- Announced at launch
- UGEN sells (any route)
- Never mint an NFT (one-way)
2. The Hook Fires
Uniswap V4 calls the hook's afterSwap() callback. Inside, the hook:
- Reads the swap direction — confirms it's ETH→UGEN (a buy)
- Reads
amountIn(ETH)from the V4BalanceDelta - If below
minBuyEth→ returns silently. No mint, no revert. - If the collection is fully minted → returns silently.
- Otherwise → proceeds to the mint logic below
afterSwap would undo the entire swap, including the UGEN delivery. Users always get their UGEN, NFT or not.3. Buyer Resolution
In Uniswap V4, the msg.sender inside afterSwap is the router, not the actual buyer. To know who gets the NFT, the buyer address must travel through hookData — an arbitrary bytes payload that the router appends to the swap call.
// Inside the Unigen buy widget:
universalRouter.execute(
commands,
inputs,
abi.encode(buyer) // <-- hookData (exactly 32 bytes)
);
// Inside UnigenHook._afterSwap:
address buyer = abi.decode(hookData, (address));The Unigen frontend encodes the connected wallet here automatically. If a router forgets to pass hookData — or passes malformed bytes — the hook silently no-ops (no NFT queued) and never reverts the swap. This is why aggregators don't mint you a unigen even when they route to the right pool.
4. RNG + Age Bias
Your buy queues a pending mint. A reveal step (callable by anyone — frontend, you, or our bot) finalizes it using on-chain entropy from a future block. That entropy isn't available at the moment of your swap, so nobody — not bots, not the team — can predict which rarity tier you'll roll. The mint materializes a few seconds after your buy confirms.
Wallet-age boost: if the buyer has called registerAge() with a valid Merkle proof, their assigned tier (S/A/B/C) subtracts basis points from the rolled rng before tier matching. Lower rng = rarer tier.
- Tier S — boost
- −4000 bps (−40 percentage points)
- Tier A — boost
- −2000 bps
- Tier B — boost
- −800 bps
- Tier C — boost
- −200 bps
- Not registered
- 0 bps (raw rng)
Example: rolled rng = 1200 (raw). Buyer is registered tier-S. Adjusted rng = 1200 − 4000 = −2800, clamped to 0 → falls into the Mythic bucket. A non-registered buyer with the same roll lands in Epic.
5. Tier Walk-Down Fallback
If the rolled tier is exhausted (all 30 Mythics already minted, say), the contract walks down to the next available tier: Mythic → Legendary → Epic → Rare → Uncommon → Common.
Important: the walk is always downward, never upward. A tier-S buyer can't accidentally mint a Common because Mythic is empty — they get a Legendary instead. Only if every tier above Common is empty would they roll Common.
The Collection
Supply
3,000 unigens total. Each one is a unique skeleton-unicorn — costumes, weapons, props, palettes. Every soul has a tier (Common → Mythic), a rolled power within its tier's range, and a one-way mint path through the V4 hook. Once the cap is hit, the collection closes forever.
Tier Distribution
The 3,000 unigens are split across 6 rarity tiers. Each minted token comes from a fixed pool of IDs, ensuring the rarity advertised in the metadata is the rarity you get.
| Tier | Count | % of supply |
|---|---|---|
| Mythic | 30 | 1.0% |
| Legendary | 120 | 4.0% |
| Epic | 300 | 10.0% |
| Rare | 600 | 20.0% |
| Uncommon | 900 | 30.0% |
| Common | 1,050 | 35.0% |
Metadata & IPFS
Each tokenId has a fixed JSON metadata file pinned on IPFS. The metadata declares the unigen's name, concept, trait breakdown, and IPFS image URL.
{
"name": "Unigen Genesis #1 — Ascended",
"description": "The first soul. Mythic tier.",
"image": "ipfs://IMAGE_CID/1.png",
"attributes": [
{ "trait_type": "Rarity", "value": "Mythic" },
{ "trait_type": "Power", "value": 950 },
{ "trait_type": "Concept", "value": "Ascended" }
]
}The UgenNFT.baseURI is set to ipfs://METADATA_CID/ once at deploy. tokenURI(id) returns ipfs://METADATA_CID/{id}.json. The base URI can be frozen via freezeBaseURI() (owner-only, one-way) to make the metadata immutable post-mint.
Rarity Tiers
Each tier has a fixed slot count and a power range:
| Tier | Slots (of 3,000) | Power range |
|---|---|---|
| Common | 1,050 | 50 – 149 |
| Uncommon | 900 | 150 – 299 |
| Rare | 600 | 300 – 499 |
| Epic | 300 | 500 – 699 |
| Legendary | 120 | 700 – 899 |
| Mythic | 30 | 900 – 1000 |
Power is rolled within the tier's range at mint time using the same seed (deterministic, replayable). It's the unigen's combat stat in the Arena and the weight used by Season Rewards.
Wallet-Age Boost
Snapshot Source
A Merkle tree is computed from a historical Ethereum genesis wallet snapshot. The on-chain contract stores only the root; the full proof JSON is served on-demand to claiming wallets.
Tier Thresholds
The snapshot maps each eligible address to one of four tiers:
- S — Genesis allocation wallet
- Touched ETH in the first 12 months
- A
- Active before block 4M (mid 2017)
- B
- Active before block 8M (early 2019)
- C
- Active before block 12M (mid 2021)
- Not eligible
- Outside snapshot; 0 boost
Registration Flow
- User connects wallet
- Frontend fetches the user's proof from a public JSON endpoint
- If found, frontend shows a "Register Genesis boost" CTA
- User signs
UgenNFT.registerAge(tier, proof[])— costs gas only - On-chain stores
ageTierOf[wallet] = tierpermanently - All future mints from this wallet get the corresponding bps reduction
RNG Bias Math
The rng → tier table (assumes already age-adjusted):
- rng < 100
- Mythic (1%)
- rng < 500
- Legendary (4%)
- rng < 1500
- Epic (10%)
- rng < 3500
- Rare (20%)
- rng < 6500
- Uncommon (30%)
- rng ≥ 6500
- Common (35%)
Out of 10,000 cumulative bps. A tier-S buyer drops their rng by 4,000 before this table is consulted — pushing their effective odds way up the curve.
Genesis Arena
Each unigen can fight. The Arena is a PvP combat system where winner steals loser's NFT. Combat is deterministic, settled on-chain in one transaction.
Entering Combat
The owner of an NFT must first approve(arena, tokenId) or setApprovalForAll(arena, true). Then call:
GenesisArena.enterCombat(tokenId)This locks the NFT as the wallet's active fighter. Each wallet can have at most one active fighter at a time.
Challenge Flow
A challenger picks one of their owned NFTs and calls:
GenesisArena.challenge(myTokenId, defenderAddress)The defender must have an active fighter. The combat resolves instantly in the same transaction.
Combat Resolution
Combat is a probability roll weighted by each fighter's effective power:
effectivePower(token) = baseRarityPower(token) * ageMultiplierBps(owner) / 10_000
attackerOdds = effectivePower(attackerToken) / (effectivePower(attackerToken) + effectivePower(defenderToken))
rng = uint256(keccak256(blockhash, attackerToken, defenderToken)) % 10_000
attackerWins = rng < (attackerOdds * 10_000)Outcome is emitted via the CombatResolved event.
Power × Age Multiplier
The wallet-age boost doesn't just bias mints — it also boosts your combat power.
- S-tier owner
- × 1.40 (14000 bps)
- A-tier owner
- × 1.20
- B-tier owner
- × 1.10
- C-tier owner
- × 1.05
- No registration
- × 1.00 (raw power)
The multiplier follows the current owner of the token, not the minter. Transfer an NFT to a tier-S wallet, it gets the boost immediately.
Cooldown
After any combat (win or lose), both NFTs enter a 6-hour cooldown. They can't fight again until cooldownUntil(tokenId) ≤ block.timestamp. Cooldown is per-token, not per-wallet — you can fight your other unigens while one is cooling down.
NFT Theft
When a combat resolves, the loser's NFT is transferFrom(loser, winner, loserTokenId). Permanent. No undo, no escrow grace period.
Season Rewards
SeasonRewards distributes a $UGEN pool per season, weighted by each held NFT's power.
Season Lifecycle
- Owner calls
startSeason(rewardPool)with a $UGEN amount - Season runs for a fixed duration (default 30 days)
- Owner calls
endSeason()to snapshot all NFTs and their owners - Holders claim via
claimRewards(seasonId, tokenIds[])for each token they owned at the snapshot block
Power-Weighted Formula
tokenReward = (powerOf(tokenId) / totalPowerAtSnapshot) * seasonPoolExample: season pool = 10M UGEN, totalPowerAtSnapshot = 100,000. Owner of a Legendary with 850 power claims:
850 / 100000 * 10_000_000 = 85_000 UGENEach (seasonId, tokenId) can only be claimed once. If you transfer the NFT before claiming, the new owner can't claim for past seasons.
Networks
Base Mainnet
- Chain ID
- 8453
- Mint threshold
- 0.05 ETH per buy
- Mcap unlock
- Announced at launch
- Pool fee
- Dynamic 1% / 0.01% tier-S
- Explorer
- basescan.org
Public Testnet
- Chain ID
- 11155111 (Sepolia)
- Mint threshold
- 0.05 ETH
- Purpose
- Rehearsal of the full mint flow before mainnet
Smart Contracts
All on-chain addresses are publicly verifiable. The testnet set below is stable and lives on Sepolia.
UgenToken (ERC-20)
- Address (testnet)
- 0xae327FAefD77535dF15035da7a0bFdBe6cde7EF2
- Type
- Fixed-supply ERC-20
- Total supply
- 1,000,000,000 UGEN
- Decimals
- 18
UgenNFT (ERC-721 + Enumerable)
- Address (testnet)
- 0xcD78d11c83d1BB7A7c1D98EEABDFF9Ef0CCD2D8f
- Mint authority
- UnigenHook only
- Base URI
- ipfs://METADATA_CID/
Key read functions:
totalSupply() → uint256
balanceOf(address) → uint256
ownerOf(uint256 tokenId) → address
tokenOfOwnerByIndex(address, uint256) → uint256
tokenURI(uint256) → string
rarityOf(uint256) → uint8 (0..5)
powerOf(uint256) → uint16
mintedAtOf(uint256) → uint40
remainingPerTier(uint8) → uint16
ageTierOf(address) → uint8
ageRegistered(address) → boolKey writes (user-facing):
registerAge(uint8 tier, bytes32[] proof)UnigenHook (V4 Hook)
- Address (testnet)
- 0x83087fF09622c9081959241293976112c32690c0
- Permissions
- afterInitialize, beforeSwap, afterSwap
- minBuyEth
- Immutable, set at deploy
- minMintFdvEth
- Immutable, ETH-denominated mcap unlock
- LP fee defaults
- DEFAULT_LP_FEE = 10000 bps (1%)
- Tier-S fee override
- TIER_S_FEE = 100 bps (0.01%)
The hook is constructed with minBuyEth (per-buy floor) and minMintFdvEth (mcap unlock). Both are immutable post-deploy. The hook's afterSwap queues a pending mint; a public revealMints(n) call finalizes queued mints using future-block entropy.
GenesisArena
- Address (testnet)
- 0x2B67013Fe769E2FFAe3107a6b53377c93E9774D7
- Cooldown
- 6 hours
- Active fighter limit
- 1 per wallet
enterCombat(uint256 tokenId)
challenge(uint256 myTokenId, address defender)
activeTokenOf(address) → uint256
cooldownUntil(uint256) → uint40
ageMultiplierBps(address) → uint16SeasonRewards
- Address (testnet)
- 0xD2E9D13f04D70D2Fab34afc58332B94ea3AbdC88
- Reward token
- UGEN
- Default duration
- 30 days per season
currentSeasonId() → uint256
seasons(uint256 id) → Season struct
claimedTokenInSeason(uint256 seasonId, uint256 tokenId) → bool
claimRewards(uint256 seasonId, uint256[] tokenIds)Owner-only: startSeason, endSeason.
Frontend Architecture
Stack:
- Next.js 15 (App Router) + TypeScript
- Tailwind CSS with custom "cozy bones" design tokens
- wagmi + viem for chain reads and writes
- RainbowKit for wallet connect
- TanStack Query as wagmi's cache layer
- Hosted on Vercel — edge CDN, automatic image optimization
Page map
- /
- Hero + Genesis Wall marquee + narrative sections + roadmap
- /library
- Connected wallet's owned unigens, filterable by tier
- /collection
- Public gallery of all unigens with rarity overlay
- /arena
- Combat UI, archetype roster, rules
- /buy
- One-tx swap + NFT mint via the V4 hook
- /docs
- You're here
Security
Audit status
Reviewed internally with a full Foundry test suite and Slither static analysis. The commit-reveal mint architecture (future-block entropy) closes the rarity-sniping vector documented in earlier NFT launches. The canonical pool key is locked in the hook at deploy to prevent front-running of pool initialization. External audit engagement scheduled post-launch.
freezeConfig()
After deployment, the owner calls freezeConfig() on UgenNFT (one-way, irreversible). This locks:
hookaddress — no more swapping hooksbaseURI— metadata becomes immutableageMerkleRoot— snapshot is final
This is called immediately after verifying the deployment, removing any owner discretion over the rules.
Known limitations
- MEV on the swap: a sophisticated searcher could front-run a big buy at the swap level. The NFT mint itself is protected by future-block entropy — the tier you get cannot be predicted at swap time even by the searcher who front-ran you.
- Hook gas:
afterSwapmint costs ~150k extra gas. Users above threshold pay that. Not a vuln, just a cost. - Arena DoS: a defender can grief by entering combat with their weakest NFT. The challenger always picks their fighter — they accept the matchup.
Glossary
- $UGEN
- The ERC-20 utility token. 1B fixed supply.
- Unigen / unicorn / NFT
- Used interchangeably. The 3,000-piece ERC-721 collection.
- Hook
- Uniswap V4's afterSwap callback contract.
- hookData
- Arbitrary bytes the router passes to the hook — used to encode the buyer address.
- Tier
- Rarity bucket: Common, Uncommon, Rare, Epic, Legendary, Mythic.
- Power
- Combat stat rolled within the tier's range at mint time.
- Age tier
- S/A/B/C — the buyer's wallet historic boost (separate from NFT rarity).
- Mint threshold
- Minimum ETH per buy that queues an NFT mint. 0.05 ETH on Base.
- Mcap unlock
- ETH-denominated FDV the pool must clear before the hook starts queueing mints.
- Commit-reveal
- Mints are queued at swap time and finalized via a public reveal call using future-block entropy.
- Walk-down
- If rolled tier is empty, mint walks downward (Mythic → Common) until a tier has supply.
- Season
- Time-windowed UGEN reward distribution, weighted by held NFT power.
FAQ
› Why is there no mint button?
› What happens if I buy under the threshold?
afterSwap sees the low amount and returns silently — no NFT queued, no revert.› Why don't NFTs mint right away after launch?
› Why does my NFT take a few seconds to appear after I buy?
› Can I get a refund if I get a Common when I wanted a Mythic?
› Can I buy a unigen on a secondary marketplace?
› What if the Arena steals my favorite unigen?
› Is the wallet-age boost retroactive if I register after my first mint?
registerAge() call use the raw RNG. Register first if you want the bias.› How do I know if my wallet is in the Genesis snapshot?
› What happens to unclaimed season rewards?
› When mainnet?
Resources
- Block explorer — search any contract address from Contracts on Basescan. The Sepolia testnet addresses are on Etherscan Sepolia.
- Buy + summon — see /buy for the one-tx swap-and-mint demo.
- The Genesis Vault — browse the full collection at /collection.
- Arena roster — /arena for the archetypes and their signature moves.
