> INIT / WHITEPAPER.PDF

Whitepaper

v0.1 · 9 sections · ~12 minutes. The full technical breakdown of the protocol.

01

Architecture

Thirteen contracts, six libraries, no proxies. The dependency graph is a strict DAG: lower-level contracts know nothing about higher-level callers. Every contract is deployed at a deterministic address derived from the deployer's nonce, except BitCardsHook which is mined via CREATE2 to satisfy Uniswap v4's permission-bit address constraint (0x0EC0).

CardsToken (ERC-20)
  ↓
BitCards (ERC-721) ← CardArt (lib) ← Palette (lib) ← SVGFont (lib)
  ↓
CardPacks ← Abilities (lib)
  ↓
Expedition ← Combat (lib)
  ↓
Tournament ← BitCardsCore
  ↓
BitCardsHook (Uniswap v4) ← HookMiner (lib)
  ↓
Treasury

Every byte of color in the entire system goes through one of Palette's 32 named indices. Every glyph in the bitmap-font name plate goes through SVGFont. Every card stat-table lookup goes through CardPacks._archetypeStats. The system has no escape hatches.

02

Combat algorithm

Combat.resolveMatch(deck1, deck2) is a pure function. Given two 8-card decks, it deterministically returns the winner address, the turn count, and a finalState bytes blob (the per-slot HP at end of match).

1. Sort each deck ascending by cost (initiative).
2. While any unit alive on both sides AND turn < MAX_TURNS:
     for slot in 0..7:
       if attacker[slot] alive AND defender[slot] alive:
         apply pre-attack abilities (QUICK, HEAVY)
         damage = attacker.atk × multiplier
         apply defender abilities (SHIELD reduces, MIRROR reflects)
         defender[slot].hp -= damage
         apply post-hit abilities (DRAIN heals attacker, BURN sets dot)
         if defender[slot].hp ≤ 0:
           apply death abilities (SACRIFICE, GENESIS revive)
     swap roles, increment turn
3. Survivor count determines winner; tie → defender.

Same inputs always give the same outputs. The test suite runs 100 matchups twice and asserts identical winners every time. See test/Combat.t.sol.

03

16 abilities

IDNameTriggerEffect
00NONE.No special effect
01QUICKbefore strikeStrikes before defender's counter
02HEAVYfirst attackFirst attack deals double damage
03SHIELDfirst hit takenFirst hit halved (one-time)
04DRAINon-hitHeals self for damage dealt
05BURNon-hitDefender burns 3 turns after hit
06MULTIeach turnDeals 1 damage to all enemies
07SACRIFICEon-deathDoubles next ally's ATK
08MIRRORon-hitReflects half damage taken
09MINEmatch end+5 bonus if survives match
10NETWORKeach turnAdjacent allies +1 ATK per net node
11HALVINGevery 4th4th attack deals 4× damage
12GENESISon-deathRevives once at 50% HP
13FORKon-playSummons 50% stat clone
14WHALEpassiveImmune to attacks < 5 damage
15HODLturn startRegenerates 2 HP at turn start
04

v4 Hook

BitCardsHook implements IHooks directly (no BaseHook inheritance). It registers permission bits 0x0EC0:

uint160 PERMISSIONS =
    BEFORE_ADD_LIQUIDITY_FLAG  // 0x0800
  | AFTER_ADD_LIQUIDITY_FLAG   // 0x0400
  | BEFORE_REMOVE_LIQUIDITY_FLAG // 0x0200
  | BEFORE_SWAP_FLAG           // 0x0080
  | AFTER_SWAP_FLAG;           // 0x0040
                              // = 0x0EC0

The hook address must have these bits in its low 14 bits. We mine a CREATE2 salt using HookMiner.find until the resulting address satisfies the constraint. The salt is computed at deploy time and is verifiable from the address.

On every afterSwap, the hook compares the current $CARDS/ETH price tick against a running 24h average. The delta determines a 5-state mood: BULL_RUN / ACCUMULATION / SIDEWAYS / DIP / CAPITULATION. The mood is written to BitCardsCore.mood, which gates expedition rewards and pack rare-rates.

05

Economy

CardsToken is a standard ERC-20 with ERC20Permit. The constructor mints exactly 21,000,000 tokens to the deployer. There is no mint() function. CardsToken.totalSupply() is constant.

Pack costs are denominated in $CARDS:

  • Genesis pack: 100 $CARDS · 5 cards · weighted Common-heavy
  • Halving pack: 500 $CARDS · 5 cards · 1 guaranteed Rare+
  • Whale pack: 2,500 $CARDS · 5 cards · 1 guaranteed Epic+, 5% Mythic chance

Pack revenue routes through Treasury with a 50/25/15/10 split: LP rewards / tournament prize pool / week-1 airdrop / Genesis Card holders.

06

Halving cycle

Bitcoin halves block rewards every 210,000 blocks. We do too.

function rewardForEpoch(uint256 epoch) pure returns (uint256) {
    if (epoch >= 64) return 0;          // saturates after 64 halvings
    return INITIAL_BASE_REWARD >> epoch;
}

At ~12s per Ethereum block, 210k blocks ≈ 29 days. After epoch 64 (~5.1 years), the right shift saturates and the base reward becomes zero. By that point, treasury reserves built up from pack sales fund tournament prize pools indefinitely.

07

Threat model

What the protocol defends against:

  • Pack roll manipulation: block.prevrandao is the only randomness. A miner can withhold blocks but cannot rewrite past blocks. Worst case: a miner sees their roll outcome and decides not to publish, forfeiting the block reward (~$700) for a single pack.
  • Combat outcome lying: impossible. Combat.resolveMatch is pure; any client computes the same answer from the same decks.
  • Hook front-running: the mood update happens in the same swap transaction as the trade. There's no MEV opportunity to predict the mood before it's set.
  • Tournament bracket manipulation: bracket positions are deterministically derived from keccak256(tour_id, entrant_addresses_sorted). Any change to the entrant set re-shuffles the bracket.
08

Verification

Etherscan addresses (post-deploy):

$CARDS pending
BitCards (NFT) pending
Hook pending
Tournament pending

Source code is published verbatim to Etherscan. Bytecode hashes match contracts/out/*.json in the repo.

09

Glossary

Mood
5-state market sentiment (BULL_RUN..CAPITULATION) computed by the v4 hook from $CARDS/ETH price action.
Halving
Every 210k blocks, base expedition reward halves via right-shift. Mirrors Bitcoin issuance.
Genesis Card
Auto-minted Mythic to the deployer at LP-seed; one-time, never reissued.
Expedition
Idle staking mode. Deposit cards for 1/7/30 days; earn $CARDS proportional to deck power × halving multiplier.
Bracket
64-entrant single-elimination tournament tree. 6 rounds. Resolved one round per call.
Permission bits
14-bit field in the lower bits of a v4 hook's address declaring which lifecycle methods it implements. We use 0x0EC0.