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.
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.
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.
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.
Normies (ERC-721C):
0x9eb6e2025b64f340691e424b7fe7022ffde12438 on Ethereum
mainnet. The wrapper composes two pluggable contracts:
INormiesRenderer produces tokenURI. Not
used by Abnormies.INormiesStorage exposes
getTokenRawImageData, getTokenTraits,
isTokenDataSet, isRevealed.
getTokenTraits returns 8 trait IDs (Type, Gender, Age, Hair
Style, Facial Feature, Eyes, Expression, Accessory). It does not contain
a customization bit; the customization signal lives elsewhere,
below.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:
bindingOf(uint256 agentId) returns (Binding { TokenStandard standard; address tokenContract; uint256 tokenId })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:
setRendererContract,
setStorageContract).Documented for transparency. Abnormies has no other external service dependencies; reveal randomness is sourced from EVM blockhashes rather than from any oracle service.
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.
Anyone may call pokeSeed(normieId) on the Abnormies
contract. The function performs view-function reads on the Normies
contracts and updates Abnormies state:
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.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.
Abnormies._beforeTokenTransfer calls pokeSeed
for the transferred Abnormie’s seed as a side effect. Every Abnormie
transfer refreshes its seed’s state.
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.
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.
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:
pokeSeed(normieId) transaction for the Abnormie’s
seed.pokeMany(uint256[]) transaction batching
the user’s holdings.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.
| 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.
Solo. Custom contract deployed by the project. Mint page on abnormies.art. No third-party launchpad or platform dependency.
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.
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.
closePhase1().sealForReveal() after phase1ClosedAt + 14 days
to seal the current receipt list and trigger the reveal sequence,
regardless of remaining supply.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.
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.
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.
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.sealedAtBlock + 8
through sealedAtBlock + 11 inclusive.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).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.
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.
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.
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(...).
Events are processed in chronological order at render time. For each pixel position assigned by an event:
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.
Normies.ownerOf(normieId) since the last poke or initial
snapshot. Recorded via pokeSeed or auto-poke.keccak256(normieId, cirrusIndex, "cirrus").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.
pokeSeed
observes NormiesCanvasStorage.isTransformed(normieId)
returning true. Binary; once per seed.keccak256(normieId, "nimbostratus-source").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.
keccak256(lightningBlockhash, targetTokenId).keccak256(thunderBlockhash, targetTokenId).The renderer replays events from stored counters in chronological order:
cirrusCount[normieId] events (or the
freeze-time snapshot for Static Abnormies), indexed 0 through count -
1.seedCustomized[normieId] = true (or the freeze-time
snapshot for Static Abnormies).Deterministic and reproducible. Anyone can replay the same sequence and produce the same canvas.
Two independent binary axes describe each Abnormie:
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.
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.
| 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 |
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.
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.
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 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.
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.
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.
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:
seedAwakened[normieId], set on-chain via
pokeAwakening, which verifies a user-supplied agentId via
Adapter8004.bindingOf.Abnormies.ownerOf(abnormieId) == Normies.ownerOf(normieId)?Both true: the Abnormie is Aligned. No separate agent-wallet detection needed.
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.
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.
| 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.
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.
immutable, set in the
constructor with no setter.pokeSeed for the seed Normie, paired with a staleness
indicator showing when the Normies API reports newer activity than the
contract has observed. Refresh all button on portfolio
views that calls pokeMany for the viewer’s holdings.
User-facing label is “Refresh” or “Update”; the contract function name
is not exposed.seedAwakened for the Abnormie’s seed AND
the Normies API reports an Adapter8004 binding exists. Resolves the
agentId off-chain via the API and calls
pokeAwakening(normieId, agentId). Users never enter or see
an agentId./history/normie/{id}/versions for fine-grained
customization history (display only, not authoritative). The on-chain
canonical static state remains the only authoritative visual.The canonical visual state lives in the contract. The website is a richer presentation layer; the contract is authoritative.
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.
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)
0x9eb6e2025b64f340691e424b7fe7022ffde124380xC255BE0983776BAB027a156681b6925cde47B2D10xde152AfB7db5373F34876E1499fbD893A82dD336bindingOf(agentId);
caller-supplied agentId#e3e5e4#48494bwithdraw() to that addresssealForReveal() 14 days after Phase 1 close.
reveal() callable from sealedAtBlock + 13
through sealedAtBlock + 240; mixes four blockhashes
(sealedAtBlock + 8 through sealedAtBlock + 11)
into the seed. reseal() callable after the 240-block window
expires unrevealed.resolveReceipts(count) over the unified Phase 1 plus Phase 2
receipt list.