SPECIFICATION
v0.16

Co-authored with Claude Opus 4.7. This document may contain errors, will be updated as the project progresses, and should be considered directional rather than authoritative.

Abnormies is a fully on-chain art collection derived from Normies (Serc, 2026), exploring network effects, lack of control, and randomness. Each Abnormie is bound 1:1 to a seed Normie, reads Normies contract data directly, and mutates through Normies’ economic activity rather than its own. Marks accumulate on the 40×40 canvas from transfers, customizations, and the destructive actions of other holders, cancelling against each other as they collide. The author is the network. In the agentic era, when a seed Normie has been awakened and its current owner also holds the corresponding Abnormie, the canvas inverts at render time. The record reads differently in the hands of the recorded.

Thesis

A Normie is something you mint, customize, and own. An Abnormie is the weather around that ownership, the marks left by transfers, customizations, and the destructive actions of others. Each Abnormie begins minimal. All evolve as the network acts on them. Some remain spare; others become dense. None of that content is authored by the Abnormie’s holder, except through destruction.

The collection inverts the Normies social contract: in Normies, you burn others to refine yourself. In Abnormies, you preserve others by damaging everyone.

Visual independence from Normies

Abnormies do not visually replicate Normies. Normies is CC0 and reproducing pixel patterns would be legally permitted, but the project’s conceptual position is that Abnormies record quantities of activity through generic spatial language, not the visual content of their source. The relationship to Normies is established through:

The relationship is not established through visual mimicry. An Abnormie tells you that its seed Normie has been touched and how much, never which pixels were touched.

Lineage and credit

Wiiides (Sterling Crispin, NotAudited.xyz, 2022) is the direct precedent: a fully on-chain CryptoPunks derivative that read its parent’s image data and mutated through its own transfer activity. Abnormies extend the lineage to a mutable parent (Normies can be customized post-mint), add destructive holder actions (Thunder, Lightning) that produce network-wide effects, and add an agentic ownership inversion via Adapter8004. Where Wiiides decayed toward stripes, Abnormies decay toward ambiguous middle-density.

Source contracts

Normies (ERC-721C): 0x9eb6e2025b64f340691e424b7fe7022ffde12438 on Ethereum mainnet. The wrapper composes two pluggable contracts:

Plus the Canvas contract that emits setTransformBitmap on each customization, and a separate NormiesCanvasStorage contract at 0xC255BE0983776BAB027a156681b6925cde47B2D1 exposing isTransformed(uint256 tokenId) returns (bool) as the canonical customization signal. Abnormies reads isTransformed directly and ignores the Canvas event stream (events are not readable from view functions).

Adapter8004 (Premm, ERC-8217: Agent NFT Identity Bindings): the binding adapter that solves the agent-orphan problem inherent to vanilla ERC-8004. Adapter8004 takes permanent custody of the agent NFT and binds it to the Normie via on-chain metadata. Owning the Normie is owning the agent. Transferring the Normie atomically transfers the agent. Ethereum mainnet proxy at 0xde152AfB7db5373F34876E1499fbD893A82dD336. Verified surface from mainnet-fork testing:

The agentIdForBinding reverse lookup documented at adapter8004.xyz/docs/contract is not present on the deployed implementation. Abnormies’ pokeAwakening therefore takes a caller-supplied agentId and verifies the binding via bindingOf. The frontend resolves the agentId off-chain via api.normies.art (which indexes the adapter’s AgentBound event) before calling, so users never enter or see an agentId.

Soft dependencies:

Documented for transparency. Abnormies has no other external service dependencies; reveal randomness is sourced from EVM blockhashes rather than from any oracle service.

State advancement

The EVM does not permit a contract’s view functions to read past event logs from other contracts. The Solidity documentation states this explicitly: log data is not accessible from within contracts. Abnormies therefore stores its own state and advances that state through three mechanisms.

Permissionless pokes

Anyone may call pokeSeed(normieId) on the Abnormies contract. The function performs view-function reads on the Normies contracts and updates Abnormies state:

  1. Reads Normies.ownerOf(normieId). If the call reverts or returns the zero address, sets seedBurned[normieId] = true and records the current block number as seedDeadAtBlock[normieId]. Otherwise, if the returned address differs from the stored lastObservedSeedOwner[normieId], increments cirrusCount[normieId] and updates the stored owner.
  2. Reads NormiesCanvasStorage.isTransformed(normieId). If true and seedCustomized[normieId] was previously false, sets the flag to true.

A batched form, pokeMany(uint256[] normieIds), applies the same logic to a list of seed Normies in a single transaction. Used by holders refreshing multiple Abnormies at once and by collection-wide indexers.

A separate pokeAwakening(uint256 normieId, uint256 agentId) records agent binding: it calls Adapter8004.bindingOf(agentId) and requires the returned binding matches (ERC721, Normies, normieId). If valid, sets seedAwakened[normieId] = true and stores boundAgentId[normieId] = agentId. One-way per seed. The agentId is resolved off-chain by the frontend via api.normies.art; users never enter or see it.

Pokes are gas-paid by the caller. Anyone can poke any seed at any time.

Auto-pokes via Abnormies transfer hook

Abnormies._beforeTokenTransfer calls pokeSeed for the transferred Abnormie’s seed as a side effect. Every Abnormie transfer refreshes its seed’s state.

Inline updates from holder actions

Thunder and Lightning actions update Abnormies’ own state in the same transaction. Cascade marks, freeze records, and seed-owner observations triggered by these actions require no external proof.

Phase 1 initialization

A merkle root committed at deploy commits each Normie’s snapshot state (owner address, customization status from isTransformed, burn status). Phase 1 claims include a merkle proof that initializes the relevant seed counters and flags, including setting lastObservedSeedOwner to the snapshot owner so subsequent pokes correctly attribute the next observed owner change as the first Cirrus event. Post-deploy activity is captured via pokes from that point forward.

Surfacing the refresh and awaken actions

State stays current only if pokes happen. Auto-pokes on Abnormie transfer and inline updates from holder actions cover most paths. The remaining paths are users manually triggering pokes when their seed Normie has acted but their Abnormie has not been touched.

The abnormies.art website is the canonical surface for this. Requirements:

A separate Awaken action surfaces when the contract has not yet recorded seedAwakened for the Abnormie’s seed AND the seed has been awakened on Adapter8004 (detected via api.normies.art/agents/binding/{normieId}). Clicking sends a pokeAwakening(normieId, agentId) transaction; the agentId is resolved off-chain from the API and embedded in the transaction. Users never enter or see an agentId.

User-facing language uses “refresh,” “update,” or “awaken.” The word “poke” is internal vocabulary for the contract function and never appears in the UI.

What Abnormies reads

Source Purpose Mechanism
Normies.ownerOf(normieId) Living/Dead detection; Cirrus accrual Read on each pokeSeed; also read directly by renderer for inversion check
NormiesCanvasStorage.isTransformed(normieId) Binary customization signal for source Nimbostratus Read on each pokeSeed
Adapter8004.bindingOf(agentId) Verifies caller-supplied agentId resolves to the seed Normie, for the inversion check Read by pokeAwakening; renderer reads seedAwakened state
Abnormies own storage Counters, flags, freeze records, event logs Read by renderer at view time

Abnormies does not read getTokenRawImageData for any Normie.

Deployment

Solo. Custom contract deployed by the project. Mint page on abnormies.art. No third-party launchpad or platform dependency.

Supply and claim

Random assignment

Claims are 1:1 by quantity, not by token ID. A Normies holder with N Normies can claim N Abnormies, but the specific Abnormie token IDs they receive are randomly assigned via the reveal mechanism described below. Dead-source slots are included in the random pool from the start. Claim and mint receipts are created in monotonic order across both phases; the entire receipt list is sealed and revealed in a single shot.

Phase 1: Holder Claim

Stylization: phases are rendered as Phase I, II, and III in product copy. This specification uses Arabic numerals for consistency with code and contract source.

Phase 2: Open Mint

After sealing, no more mints or airdrops are accepted. Unminted slots if any (only possible under the fallback path) remain unminted forever; the corresponding Abnormie IDs simply never enter circulation.

Phase 3 · Reveal

Once Phase 2 closes and the entropy window passes, the reveal phase begins. The collection reveals through the same network mechanic that shapes the art itself. Anyone may call resolveReceipts(N), paying gas to advance the reveal cursor by N positions. The next N receipts in the queue are resolved: each receives a randomly drawn Abnormie token ID and a randomly drawn seed Normie pairing. The receipt becomes a real ERC-721 token in its owner's wallet.

Resolution is strictly monotonic. The caller has no control over which receipts they advance. A claimant clicking 'reveal' on the site may resolve their own receipts, or someone else's, depending on the current cursor position. Receipts at the front of the queue resolve first; later receipts wait until the cursor reaches them.

The reveal continues until all receipts are resolved. The cost of reveal is borne by whoever chooses to advance the queue, in proportion to their participation. There is no fee, deposit, or bounty. Only gas.

Reveal

Random assignment of receipts to Abnormie IDs happens in a single collection-wide reveal after the receipt list is sealed. The reveal architecture is a three-step seal-then-commit-then-reveal flow that uses Ethereum blockhashes as the entropy source. No external oracle service.

  1. Seal. The receipt list is closed (no more mints or airdrops). Recorded sealedAtBlock is the current block number at the moment of sealing. Triggered automatically by the last receipt-creating transaction when supply hits zero, or manually by anyone via sealForReveal() after the 14-day fallback delay.
  2. Wait. After sealing, the contract requires that all four entropy blocks be mined and within EVM blockhash range before reveal can proceed. The entropy blocks are sealedAtBlock + 8 through sealedAtBlock + 11 inclusive.
  3. Reveal. Anyone calls reveal(). The function mixes the four entropy blockhashes through a keccak256 chain into the revealSeed. The reveal window opens at sealedAtBlock + 13 (one block after the last entropy block is mined) and closes at sealedAtBlock + 240 (deliberately before EVM’s 256-block blockhash horizon, with margin).
  4. Resolve. After reveal, anyone calls resolveReceipts(count) repeatedly until every receipt is paired. Resolution is strictly monotonic; receipts cannot be reordered or skipped. Each receipt’s assigned Abnormie ID is derived deterministically from revealSeed, the receipt index, and a Fisher-Yates draw over the unclaimed pool.

If reveal() is not called within 240 blocks of sealing (the EVM blockhash window expires for the earliest entropy block beyond that), anyone may call reseal() to reset sealedAtBlock to the current block. The receipt list itself does not change; only the seal block is updated. Reveal then proceeds against a fresh entropy window.

Reveal randomness properties

The four-block entropy mix prevents intermediate-block proposer bias: any proposer in positions 1 through 3 of the entropy window sees only partial seed data and cannot compute the final seed. The proposer of the final entropy block, however, can compute the complete reveal seed before publishing their block and can withhold the block (forfeiting that block’s MEV reward) to roll for a different final hash on a replacement block. This is a single conditional reroll, not arbitrary seed selection, and the attacker pays the cost of a block reward forfeit per attempt.

For a small-fractional-stakes art project (0.005 ETH per Abnormie, total project pot in the low tens of ETH), the manipulation surface is small relative to other operational concerns. The architecture trades the absolute proposer-bias resistance of an oracle-based randomness service (Chainlink VRF and similar) for the absence of any external service dependency.

Visual specification

Layers

40×40 monochrome grid, identical dimensions to Normies. Four-color cloud palette:

Color Hex RGB Name Origin
Lightest #e3e5e4 227, 229, 228 Sky Untouched, or touched an even number of times (cancelled).
Light #b0b1b0 176, 177, 176 Cirrus Mark left by an observed change in the seed Normie’s owner.
Mid #7c7d7e 124, 125, 126 Altocumulus Mark left by a Thunder cascade.
Dark #48494b 72, 73, 75 Nimbostratus Mark left by source customization (binary), or by a Lightning cascade. Indistinguishable visually; distinguishable in metadata.

Lightness progression: Sky > Cirrus > Altocumulus > Nimbostratus.

Sky and Nimbostratus match Normies’ factory values exactly.

Source-customization Nimbostratus and Lightning-cascade Nimbostratus share the darkest color by design. Their counts are exposed separately in metadata for sorting and rarity, but no pixel on the rendered canvas reveals which event produced it.

Mutation: layers with cancellation

Four event classes contribute to the canvas. All use the same touch-accumulation logic. Each event produces N deterministic pixel positions on the Abnormie, seeded by keccak256(...).

Cancellation rule

Events are processed in chronological order at render time. For each pixel position assigned by an event:

  1. If the position is currently Sky, it takes the event’s color.
  2. If the position is currently any non-Sky color, the new mark and the existing mark cancel: the position reverts to Sky, available for future events.

Every visible non-Sky pixel on an Abnormie has been touched an odd number of times. Coverage plateaus at approximately 50%. The endpoint of an active history is ambiguous middle-density, neither pristine nor saturated.

There is no layer hierarchy. Any color can cancel any other.

Event sources

Cirrus: observed seed Normie owner changes

Cirrus accrues only on observed owner changes. Transfers that occur between two pokes and end at the same owner that was previously observed are not recorded. The Phase 1 snapshot owner counts as the initial observation. The counter saturates at uint16.max; subsequent observations do not revert.

Nimbostratus (source): seed Normie first customization

A seed Normie that has never been customized contributes zero source Nimbostratus marks. A seed Normie that has been customized at least once contributes the 12-pixel batch exactly once. The isTransformed flag is one-way for our purposes; once observed true, the seed Normie’s source Nimbostratus is locked in.

Note: Awakening (Adapter8004 binding) and customization (NormiesCanvasStorage transformation) are independent. Awakening is a gas-only transaction; customization requires burning a Normie. Holders are more likely to awaken than customize, so a Normie can be Awakened without being Customized. The two signals address different state axes and should not be treated as proxies for each other.

Nimbostratus (Lightning): Lightning cascade

Altocumulus: Thunder cascade

Event processing order at render time

The renderer replays events from stored counters in chronological order:

  1. All Cirrus events: cirrusCount[normieId] events (or the freeze-time snapshot for Static Abnormies), indexed 0 through count - 1.
  2. Source Nimbostratus event: one event, conditional on seedCustomized[normieId] = true (or the freeze-time snapshot for Static Abnormies).
  3. All Lightning events received, in chronological order from Abnormies storage.
  4. All Thunder events received, in chronological order from Abnormies storage.

Deterministic and reproducible. Anyone can replay the same sequence and produce the same canvas.

State system

Two independent binary axes describe each Abnormie:

Axis 1: mutability

An Abnormie becomes Static when chosen as the freeze target of a Thunder or Lightning action. The freeze applies before the cascade fires, so the newly Static Abnormie receives no contribution from the action that froze it. The freezing action also inline-pokes the freeze target’s seed so the snapshot reflects the latest observable seed state at freeze time.

Axis 2: source life

The transition from Living to Dead is recorded on a poke that observes Normies.ownerOf(normieId) reverting or returning address(0). One-way. For Static Abnormies, Source Life is read from the freeze-time snapshot per Axis 1; subsequent observed burns of the seed do not change a Static Abnormie’s Source Life or Dead At trait.

Combinations

Active Living Description Receives Burnable
Active Living, uncustomized Cirrus only until/unless seed becomes customized; plus all network cascades Cirrus, Thunder, Lightning cascades Not burnable
Active Living, customized All event types Cirrus, source Nimbostratus (already accrued), Thunder, Lightning cascades Lightning
Active Dead Network cascades only Thunder, Lightning cascades Thunder
Static Living Frozen None Not burnable
Static Dead Frozen. The doubly-preserved rare category. None Not burnable

Destructive holder actions

Two burn paths. Each burns the burner’s Abnormie, casts a cascade across the network, and freezes one Active Abnormie of the burner’s choice.

Thunder: burn a Dead Abnormie

Plain instructions: Burning a Dead Abnormie freezes one Active Abnormie of your choice (it becomes Static, permanently locked in its current state) and creates a Thunder cascade: 5 to 10 Altocumulus pixels land on every other Active Abnormie in the collection. The frozen Abnormie cannot be one you own.

Lightning: burn a Living, customized Abnormie

Plain instructions: Burning a Living Abnormie whose seed Normie has been customized at least once freezes one Active Abnormie of your choice (it becomes Static) and creates a Lightning cascade: one Nimbostratus pixel lands on every other Active Abnormie in the collection. The frozen Abnormie cannot be one you own.

The non-self freeze rule

The freeze target must not be owned by the burner. A burner can freeze any Active Abnormie owned by anyone else. Self-preservation requires either coordination with another holder who freezes on your behalf, or moving the Abnormie to a side wallet before triggering the burn.

Rationale: preservation is a relational act in this system. The cascades that motivate freezing are involuntary network effects; the freeze that absorbs the burner’s “save one” choice is therefore also relational by construction. Coordination and wallet-shuffle are explicit escape valves and considered legitimate. The rule is enforced at the contract level; any attempt to freeze a self-owned Abnormie reverts.

Why two action types

Lightning is the long-term Nimbostratus-seeding mechanic: the network’s customization analogue, driven by destruction of Living-customized Abnormies. Thunder seeds Altocumulus: the network’s burn-history layer.

Over time, even Abnormies whose seed Normies were never customized will accumulate network-Nimbostratus from Lightning. Nimbostratus’s meaning shifts from “what your seed authored” to “what the network has authored through sacrifice.” Both readings are valid simultaneously, and indistinguishable at the level of pixels.

Agent-ownership inversion (Adapter8004)

When the Abnormie’s owner is also the effective owner of the seed Normie’s Adapter8004-bound agent, the canvas inverts at render time.

When the same wallet holds both the Abnormie and its seed Normie AND the seed Normie has been awakened via Adapter8004, the Abnormie is Aligned. An Aligned Abnormie renders inverted.

Why Adapter8004 simplifies the check

Vanilla ERC-8004 mints a separate agent NFT, allowing the Normie and the agent to be owned by different addresses. Adapter8004 (ERC-8217, Premm) takes permanent custody of the agent NFT and binds it via on-chain metadata to the Normie. Selling the Normie atomically transfers control of the agent. Owning the Normie is owning the agent.

The inversion check reduces to two view-function reads against Abnormies state:

  1. Has the seed Normie been awakened? Read seedAwakened[normieId], set on-chain via pokeAwakening, which verifies a user-supplied agentId via Adapter8004.bindingOf.
  2. Is Abnormies.ownerOf(abnormieId) == Normies.ownerOf(normieId)?

Both true: the Abnormie is Aligned. No separate agent-wallet detection needed.

Phase 1 vs Phase 2 alignment

In Phase 1 of Normies Awakening (registry only, current), the agent’s effective control is the Normie holder’s wallet. In Phase 2 (agent wallets, planned), the agent gets its own wallet but Adapter8004’s binding guarantees atomic transfer of authority. The same-owner check is canonical in Phase 1 and remains correct in Phase 2.

Inversion rule

For every pixel position on the rendered canvas:

The underlying event history and stored counters are unchanged. Inversion is a render-time transformation, fully reversible. If the owners diverge, the canvas returns to normal.

Cases distinguished

  1. Different owners (human owns Abnormie, someone else owns seed Normie): normal render.
  2. Same owner, seed Normie not awakened (collector holds both but hasn’t registered the Normie through Adapter8004): normal render.
  3. Same owner, seed Normie awakened (the autobiographer holds its own autobiography): Aligned, inverted render.

Architecture summary

Mechanic Update path
Phase 1 claim Merkle proof of snapshot; creates a claim receipt and initializes seed state (including lastObservedSeedOwner = snapshot owner)
Phase 2 mint Paid mint at 0.005 ETH per receipt; appended to the unified receipt list
Phase 2 airdrop Owner-only, gas-only; appended to the unified receipt list, identical resolution semantics to paid mints
Reveal Seal then commit then reveal: receipt list seals on supply exhaustion or 14-day fallback; reveal() mixes four blockhashes from sealedAtBlock + 8 through sealedAtBlock + 11 into a single revealSeed; window opens at sealedAtBlock + 13 and closes at sealedAtBlock + 240; reseal() resets the window if 240 blocks pass without reveal
Resolution After reveal, anyone calls resolveReceipts(count) to pair receipts to Abnormie IDs via Fisher-Yates from the unclaimed pool, in strictly monotonic receipt-index order
Cirrus (seed transfers) pokeSeed observes Normies.ownerOf change; increments counter (saturating at uint16.max)
Source Nimbostratus (seed customization) pokeSeed observes NormiesCanvasStorage.isTransformed returning true; sets one-time flag
Lightning, Thunder, freeze State updated inline in the action transaction; both the burner’s seed and the freeze target’s seed are inline-poked at action time
Auto-pokes Abnormies._beforeTokenTransfer calls pokeSeed for the transferred Abnormie’s seed
Agent inversion pokeAwakening(normieId, agentId) verifies via bindingOf and records seedAwakened; renderer reads stored flag plus ownership
Living/Dead state Cached by poke when observed for Active Abnormies; renderer may also call ownerOf for freshness. For Static Abnormies, read from the freeze-time snapshot stored on the Abnormie.
Static render inputs All seed-derived inputs (cirrusCount, seedCustomized, seedBurned, Dead At block) snapshotted at freeze; subsequent pokes against the seed do not affect Static Abnormies’ renders or metadata
Treasury Phase 2 mint revenue accumulates in the contract until anyone calls permissionless withdraw() to push the balance to the immutable treasury address

The renderer is a pure function of contract state. No external event reads, no oracles, no off-chain dependencies for the canonical visual.

Royalty and treasury

ERC-721C (LimitBreak Creator Token Standards), matching Normies’ own pattern. Default 2.5% royalty, enforced at the contract level; non-compliant marketplaces are blocked from facilitating transfers.

Treasury for Phase 2 mint revenue is an immutable address set in the constructor. A permissionless withdraw() function pushes the contract’s ETH balance to the treasury. No admin discretion, no upgrade path. The treasury address and the royalty receiver are independent constructor parameters and may be the same or different.

Rendering

Website (abnormies.art)

The canonical visual state lives in the contract. The website is a richer presentation layer; the contract is authoritative.

Hosting and decentralization

Frontend deployed on Cloudflare Pages. Mirrored to IPFS, pointed at via ENS (abnormies.eth) as a parallel route. Open-sourced under MIT or CC0 so anyone can fork and host an alternative frontend. The artifact survives any single deployment.

Traits

State (two axes): - Mutability: Active | Static - Source Life: Living | Dead (locked at freeze time for Static Abnormies)

Source-derived (from stored state): - Source Customized: boolean (the seed Normie has been observed customized at least once; locked at freeze time for Static Abnormies) - Source Awakened: boolean (the seed Normie has been registered via Adapter8004)

Recorded (counters): - Cirrus Events: count of observed owner changes on the seed Normie (saturating at uint16.max; locked at freeze time for Static Abnormies) - Lightning Events Received: count of Lightning cascade contributions - Thunder Events Received: count of Thunder cascade contributions - Static At: block number at which the Abnormie was frozen (null if Active) - Dead At: block number at which the seed Normie’s burn was observed. Null for Active Abnormies whose seed is currently Living. For Static Abnormies, the value is locked at freeze time; null forever if the seed was Living at freeze.

Visible: - Visible Cirrus / Altocumulus / Nimbostratus: counts of currently-rendered pixels (post-cancellation) - Total Coverage: percentage of canvas that is non-Sky

Action attribution: - Frozen By: address of the burner who triggered the Thunder or Lightning that froze this Abnormie (null if Active) - Freeze Action: Thunder | Lightning | null - Frozen After Death: boolean, true if the Abnormie was already Dead at the moment of freezing

Unique: - First Static: boolean, true only for the very first Abnormie ever frozen in collection history - Inverted: boolean, true when the agent-ownership inversion is currently active (resolves at render time, can change as ownership changes)

Locked parameters

Out of scope

Naming and surface