protocol docs

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:

  1. $UGEN — a fixed-supply 1B ERC-20 that exists on a self-launched Uniswap V4 pool.
  2. UnigenHook — a V4 hook attached to that pool at initialization. It listens to afterSwap and triggers the mint when conditions match.
  3. 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.
On top of that: a Genesis Arena where NFTs fight for ownership (winner steals loser's token), Season Rewards that redistribute $UGEN by power, and a wallet-age Merkle registry that boosts old-Ethereum wallets' chances of pulling rare tiers.

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.

$UGEN price is set by the market. The protocol doesn't define a target price — it defines a mint threshold in ETH for individual buys, and an mcap unlock below which the NFT mint stays dormant. Both are designed to keep bots out and reward real demand.

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:

01
Swap ETH→UGEN
any V4 route; widget for NFT
02
Hook queues
afterSwap() commits
03
Next block
future blockhash settles
04
Reveal call
tier + power rolled
05
Mint
ERC-721 lands in wallet

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)
The widget is the safest route for everyone. The public Uniswap UI works fine for standard EOA wallets (MetaMask, Rainbow, etc). Aggregators usually work too. Smart wallet users: use the widget.

2. The Hook Fires

Uniswap V4 calls the hook's afterSwap() callback. Inside, the hook:

  1. Reads the swap direction — confirms it's ETH→UGEN (a buy)
  2. Reads amountIn(ETH) from the V4 BalanceDelta
  3. If below minBuyEth → returns silently. No mint, no revert.
  4. If the collection is fully minted → returns silently.
  5. Otherwise → proceeds to the mint logic below
Hook callbacks are written non-reverting for any non-mint condition. This is critical — a revert in 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.

Each tier has a fixed pool of tokenIds. Once a tier is rolled, the contract picks a tokenId from that pool via swap-and-pop (O(1)), guaranteeing the minted tokenId always matches its IPFS metadata's declared rarity.

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.

TierCount% of supply
Mythic301.0%
Legendary1204.0%
Epic30010.0%
Rare60020.0%
Uncommon90030.0%
Common1,05035.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:

TierSlots (of 3,000)Power range
Common1,05050 – 149
Uncommon900150 – 299
Rare600300 – 499
Epic300500 – 699
Legendary120700 – 899
Mythic30900 – 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.

A Common can roll up to 149 power, and a Legendary can roll as low as 700. The ranges don't overlap tier-to-tier; rarity strictly dominates power.

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

  1. User connects wallet
  2. Frontend fetches the user's proof from a public JSON endpoint
  3. If found, frontend shows a "Register Genesis boost" CTA
  4. User signs UgenNFT.registerAge(tier, proof[]) — costs gas only
  5. On-chain stores ageTierOf[wallet] = tier permanently
  6. All future mints from this wallet get the corresponding bps reduction
Registration is one-shot, irreversible, gas-only. You can't register a different tier later, you can't transfer the registration. The boost belongs to the wallet, not the human.

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.

Don't enter your favorite unigen if you're not prepared to lose it. Or do — and steal someone else's.

Season Rewards

SeasonRewards distributes a $UGEN pool per season, weighted by each held NFT's power.

Season Lifecycle

  1. Owner calls startSeason(rewardPool) with a $UGEN amount
  2. Season runs for a fixed duration (default 30 days)
  3. Owner calls endSeason() to snapshot all NFTs and their owners
  4. Holders claim via claimRewards(seasonId, tokenIds[]) for each token they owned at the snapshot block

Power-Weighted Formula

tokenReward = (powerOf(tokenId) / totalPowerAtSnapshot) * seasonPool

Example: season pool = 10M UGEN, totalPowerAtSnapshot = 100,000. Owner of a Legendary with 850 power claims:

850 / 100000 * 10_000_000 = 85_000 UGEN

Each (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) → bool

Key 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) → uint16

SeasonRewards

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:

  • hook address — no more swapping hooks
  • baseURI — metadata becomes immutable
  • ageMerkleRoot — 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: afterSwap mint 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?
Because the hook is the mint. Adding a button would either lie (mint from a different contract path) or duplicate the on-chain logic. The swap is the action. Anyone selling you a separate "mint" is selling you nothing.
What happens if I buy under the threshold?
The swap still goes through. You receive UGEN. The hook's afterSwap sees the low amount and returns silently — no NFT queued, no revert.
Why don't NFTs mint right away after launch?
The hook only queues NFT mints once the pool clears its mcap unlock — an ETH-denominated FDV threshold set at deploy. The point is to keep snipe-bots out of the first minutes of trading and reward genuine demand. Once unlocked, every qualifying buy queues a mint.
Why does my NFT take a few seconds to appear after I buy?
The mint is commit-reveal. Your buy queues a pending mint in the same transaction; a separate reveal step (the frontend triggers it automatically, or our bot does) finalizes it after the next block lands. This is what makes the rarity roll unpredictable — and unsnipable.
Can I get a refund if I get a Common when I wanted a Mythic?
No. The mint is on-chain final. The seeded RNG is replayable and auditable — the result is what it is.
Can I buy a unigen on a secondary marketplace?
Yes — once the collection is live, it's a standard ERC-721 transferable on OpenSea, Blur, etc. You don't get the wallet-age boost on secondary purchases — that's tied to the primary mint via the hook.
What if the Arena steals my favorite unigen?
That's the deal. Either don't enter your favorite, or stake it knowing the risk. The protocol won't reverse a combat outcome.
Is the wallet-age boost retroactive if I register after my first mint?
No. The boost is applied at mint time. Mints before your 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?
Connect your wallet on the home page. If your address is in the snapshot, the frontend will surface a "Register Genesis boost" CTA. The proof JSON is fetched on-demand from a public endpoint.
What happens to unclaimed season rewards?
They stay in the contract. There's no expiry on past-season claims — but a future governance proposal could recycle unclaimed pools into the next season.
When mainnet?
Live on Base. The pool is open, the hook is wired, and the collection is mintable once the mcap unlock is crossed.

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.