along7 gallery · the build story · July to September 2026

A Database With Good Lighting

I collect digital art on the blockchain. The token is permanent; the picture it points to is not. So over eight weeks a data scientist and an AI turned a wallet into a museum: 3,735 works by 839 artists across six chains, every original file rescued from the source the artist declared, every ownership history replayed from the ledger, and 99 automated checks standing between the database and the public.

Written for two readers at once. If you make art, this is what happens to a work after someone collects it, and what you can do so it survives. If you write code, this is how a museum gets built out of one SQLite file. The fifteen works in the banner above run through the whole page, each one placed where its own story explains the machine.

3,735works · 839 artists · 6 chains
64 GB4,665 verified originals, 99.5% from the artist's source
80,161on-chain events replayed into provenance
5,925static pages, no server, no CDN, no trackers
99 / 0audit checks / failures allowed per deploy
~$1a month to host, no server to keep alive

Counted 2026-09-03. These numbers are recomputed on every build of the gallery and drift upward; the live ones are on the stats page. All fifteen artworks on this page remain the property and copyright of their artists.

01

Where this began

On July 9, 2026 at 3:37 in the morning, an empty folder, an OpenSea API key in a text file, and one prompt.

"Help plan out database collection and download of my NFT art vault. I will give you my opensea link. Lets create a database of the images (this will be hard many different file types, inspect ways to find original file, the address associated with like mints transfer, whatever etc." The opening message, verbatim, typos and all. The only thing in the folder was the key.

I loved art long before I loved crypto. I bought my first Bitcoin at eighteen in 2013, was around for the first NFTs in 2017 and walked away because I did not like the pictures, and it took until 2025 for that to change. When it changed, it changed hard: a game, a guild, then one artist's project on an AI art app, then a thousand works in a year. The picture that did it is the one below.

the grid by DeltaSauce: a blurred white figure rising out of a dark floor ruled into a grid of red points, arms lifted, dissolving into static at the edges
the grid · DeltaSauce, from the FEED series How it got here: won at auction on Fellowship, June 18, 2025. The first NFT I ever actually wanted, after a decade of not wanting any. A figure made of noise climbs out of a grid, and the picture sits exactly where DeltaSauce likes to work, on the line between a memory and static; he builds the FEED series from the glow of dead bedroom televisions and calls himself a curator of nostalgia. This one hangs first because everything else followed it.

Here is the part nobody warns you about, on either side of the transaction. An NFT does not store the art. The token is a permanent line on a blockchain. The picture it points to is a guest on IPFS, or Arweave, or an artist's web host, or a marketplace's cache, and any of those can go dark when the rent stops. When it does, the token is still yours. It just points at nothing. The artist's work is what actually disappears.

For artists

An NFT is a museum plaque with a photo of the painting printed on it. The plaque is bolted to the wall for good. The painting is in a rented warehouse across town. If the warehouse closes, your collector owns a very nice plaque, and your work is gone from the record. Most of this page is about keeping the warehouse from mattering.

For programmers

Concretely: a token's tokenURI() returns a URL to a JSON document; that document's image or animation_url field is another URL; the bytes at that second URL are the art. Two hops, both of them somebody else's server, and only the first hop is on-chain. Every host in the chain can rot independently. The whole project is a cache of the second hop with a checksum, plus a ledger of who owned the token when.

This was not a thought experiment. When the pipeline first pulled the original file for every piece I owned, twelve were already gone, the hosts dead. For nine of them, a marketplace's cached thumbnail was the only copy left anywhere. I found that inside my own collection, the one I thought was safe.

What is in there matters, because this is not a spreadsheet of tokens. 1,934 stills, 1,194 motion pieces, 296 films, and 293 works of live code that run in the browser, by 839 artists on every continent (the four who chose anonymity are filed under Antarctica). A year of Justin Aversano's daily polaroid auctions. Work from 112 of the 500 flagship Art Blocks projects. And the artists' own words: 1,118 lines recovered from public conversation, each with a permalink, sitting beside the works they were about. The database exists to keep all of that intact after the marketplaces move on.

Why this project

I am a data scientist. The problem in front of me was not an art problem, it was a data problem: an inventory nobody had counted, files scattered across hosts that lie about what they are serving, a ledger that is authoritative but awkward to read, and a marketplace API that quietly drops rows. The answer was a database. The gallery is what the database looks like with good lighting.

02

How this was built: eight weeks, sixty-three versions

Version one shipped in a day. Then the conversation kept going. Every version below passed the same gate before it went live: zero audit failures, zero leaks of the private wallet.

Day oneInventory, a 25-work pilot, the fake-video catch, the first gallery, live on a domain by hour fourteen.
Weeks one and twoThe wiki layer, the timeline, the world map, the stories, the artists' recovered voices, the first security review.
The vault upgradeThe collection doubled, went to four chains, gained backups, and survived its first self-inflicted disaster.
HardeningSecurity lockdown, structured data, an open CC0 dataset, an end-to-end QA pass that found nothing to fix.
The TV and TezosA slideshow that plays the whole collection on a television, then the other half of the collection: 1,958 Tezos works.
Fresh eyesA new model re-read the chain, the prose, the indexes, and the database. It found stale numbers everywhere and one real API bug.
March 21st by Justin Aversano: a grainy film photograph of two women embracing in front of the Sphinx and a pyramid at Giza, a light leak running down the left edge
March 21st · Justin Aversano, from Moments of the Unknown How it got here: won at a daily auction on Transient, March 22, 2026. One photograph a day for a year, strangers and friends caught in a single frame, each auctioned the day it was shot; this one is two women holding each other in front of the Sphinx. I bid my way into just over two hundred of them. The gallery's Timeline page exists because of runs like this: the marketplace remembers the day a token moved to cold storage, the chain remembers the night the auction actually closed, and only the second one is the real collecting date.
WhenWhat shippedThe receipt
Jul 9 to 10
day one
893 NFTs across 339 contracts catalogued in the first hour. A random 25-work pilot proved tokenURI → metadata → original file across IPFS, Arweave, and artist servers. 18,110 on-chain events from three sources. 893 originals, 1,078 files, 15.4 GB. Site live on Cloudflare R2. The pilot review caught the marketplace lying (14 fake "videos" that were image stubs). A hostile review under two hats, senior web dev then gallery curator, found 17 holes; all fixed before the full push.
Jul 11 to 22
the wiki run
Artist rooms with bios and locations, a timeline of true collecting dates traced through the hot wallet, marketplace forensics from transaction receipts, a globe of where the artists live, sixteen stories in the collector's voice, and the collector's own X archive mined for what artists said at the time. The audit harness grew from 21 checks to 74. Two full reviews were written as reports rather than fixes, a truth audit and an end-of-day "good night review," before the next feature was allowed in.
Jul 17 to 18
the vault upgrade
987 → 1,708 works, one chain → four (Base, Abstract, Polygon), 286 → 420 artists. A chain-first recount caught works the marketplace API had silently dropped. First 3-2-1 backup. A cleanup script deleted 890 legitimate files. The audit failed the build on the spot (2,255 broken references); the day-old backup restored everything in minutes. The story is below.
Jul 21 to 22
hardening
A full default-src 'self' content security policy, artist-authored interactive works sandboxed at the edge, JSON-LD on every page, a CC0 dataset of the facts, llms.txt, a robots.txt that welcomes AI crawlers on purpose. 80 audit checks. A "max auditor" pass walked 2,979 pages and reported zero defects, and said so instead of inventing work.
Aug 13 to 14
the TV
A /tv page that plays the whole collection: films stream a 1080p transcode tier, gifs loop, live-code pieces run in per-slide sandboxes. Plus a local tool that pushes stills to a Samsung Frame's Art Mode. 358 transcodes, 2.35 GB, zero corrupt. An adversarial review caught 12 real bugs before the first deploy and 4 more before the second.
Aug 26 to 29
Tezos
The other half of the collection ingested from a second blockchain: 1,958 works, about 40 GB, via TzKT and objkt. 476 creators materialized as artist rooms; cross-chain identities folded by handle, website, and name. A complete Spanish mirror was built, audited to zero failures, and parked the same day. Public IPFS gateways throttled the bulk download after ~270 files and denylisted whole artists. Slower waves, gateway rotation, and a fallback to objkt's CDN brought it to 0 failed downloads.
Sep 2
fresh eyes
A new model reviewed five fronts. New factcheck stage re-verifies chain facts into a ledger: custody for all 3,735 works, sampled ownership, deployers, editions, ENS. Prose numbers became build-time tokens. Build time 916s → ~100s. 26 bios and every collection-scale count in the essays were stale. An Etherscan rate-limit response had been indistinguishable from a reverted call. "160 duplicate events" turned out to be 3. 99 checks, 0 FAIL.
The rule that made it honest

Nothing deploys unless a program says it can. The audit runs about a hundred named checks against the built site on every release, and a second grep confirms a private wallet address appears zero times in the output. Both gates are absolute and loud. Twice a deploy was silently blocked by a single failing check while the script still exited 0, so the rule grew a corollary: a deploy is confirmed by fetching the live versioned asset, never by an exit code and never by a cacheable URL.

Why publish the messy middle

The gallery itself is polished. This page is not, on purpose. The deleted files, the fake videos, the stale numbers, the wrong wallet: those taught more than the features did, and a data science portfolio that only shows the wins is showing half the job.

03

TL;DR

Eight bullets, each with a number attached

1 · The artist's file is the only source of truth. Every work's metadata is read from its own on-chain tokenURI, and the file is fetched from the link the artist put there, never from a marketplace preview. Today 99.5% of works are archived from that source: 3,251 files from IPFS, 651 from artist-run servers, 326 from Arweave, 22 fully on-chain. The 127 that survive only as marketplace caches are labeled as exactly that.

2 · Bytes, not labels. Every file's type comes from its magic bytes and it is stored with a SHA-256. Content-Type headers lied often enough that trusting them was the first bug of the project.

3 · Provenance is replayed, not guessed. 80,161 transfer and sale events from two independent sources give 3,652 works a verified mint record and every work a collecting history. Where a piece was bought, the transaction receipt names the marketplace that actually settled it: 3,003 acquisitions traced.

4 · Auctions are reconstructed bid by bid. 740 bids across 215 auctions read straight from calldata, including a sealed-bid drop with 202 bids from 73 collectors. Houses that settle bidding off-chain leave no trail, so the site says "won at auction" more often than it can show bids, and says why.

5 · The database is the product. One SQLite file: works, contracts, collections, artists, events, sales, market events, media files, profiles, fact checks, page hashes. The attribution chain is a join, and a hand-authored curation layer overrides it where the chain cannot know.

6 · The site is disposable, the archive is not. 5,925 pages render from the database and files in about a hundred seconds. No app server, no CDN, no trackers, no external fonts. Lose the site and one command rebuilds it; lose the database and the chain plus the files rebuild most of it.

7 · Every deploy is audited like a bank, not proofread like a blog. 99 named checks, 0 failures allowed. Links, venue proofs, custody, privacy, prices, schema, prose drift, even em dashes.

8 · The AI did the engineering; a human did the seeing. Three Claude models over eight weeks wrote nearly all of the code, read three blockchains, and reconstructed auctions from raw calldata. The collector made every call taste decides and caught what no check could: a lightbox opening the wrong work, an artist's room holding other people's projects, prose that sounded like a machine.

And the vault holds. The custody check reads the latest transfer of every one of the 3,735 works and confirms it lands in a tracked wallet. As of September 2, zero have left.

04

The data model

A Python package with one command per stage, each idempotent and resumable, all writing into one SQLite file. The hard part is not storage. It is answering "who made this?" without a column that says so.

OpenSea · objkt catalog + metadata Etherscan · TzKT transfers + receipts + logs IPFS · Arweave · artist hosts the artist's real files py -m vault <stage> sync · metadata · provenance · download · factcheck vault.db (SQLite) works · artists · events · checks media/ (64 GB) originals + checksums + derivatives curation/ human judgment, JSON + md Static site generator (Jinja2) 5,925 pages · audit · privacy grep Cloudflare R2 · rclone sync site/ only
Every arrow points away from something fragile toward something the collector controls. Only the built site/ folder ever leaves the machine; the database, the curation layer, and the notes never do.

The attribution chain

A token row has no artist column. That is the single most important fact about NFT data, and it explains half the wrong attributions you see on marketplaces. The token belongs to a contract; the contract maps to a collection; the collection has an owner address; the owner address is the artist. Except when it is not, which is often.

For programmers

The whole gallery hangs off one view. The per-token collection field beats the contract-level one, because a shared contract can hold hundreds of projects by different people:

CREATE VIEW v_main AS
SELECT n.id, n.name AS title, n.contract_address, n.token_id, n.chain,
       COALESCE(n.collection_slug, c.opensea_slug) AS collection,   -- per-token beats per-contract
       col.owner_address                            AS artist_key,   -- the artist, usually
       n.minted_at, n.collected_at, n.collected_via, n.acquire_market
FROM nfts n
LEFT JOIN contracts   c   ON c.address = n.contract_address
LEFT JOIN collections col ON col.slug  = COALESCE(n.collection_slug, c.opensea_slug);
-- then: curation/overrides.json wins over every row above
Beverly Hills by SigmaX: a pink hotel with a dark green tower and a looping script sign reading The Beverly Hills, palms and a pink car in front, drawn with an architect's precision
Beverly Hills · SigmaX, from What Cities Hold Why it is here: a one-of-one from a generative drop on 8NAP, where every mint came out different and the rarest cities were scattered at random through the run. SigmaX trained as an architect and builds these the way an architect builds, structure first, feeling arriving through precision. A drop like this is exactly the case the per-token collection field exists for: dozens of unique pieces, one contract, one artist, and a marketplace that would happily file them under the contract's name instead of his.
For artists

If you want to be credited correctly by every archive that ever indexes your work, mint from a contract you deployed yourself, or make sure the token's own metadata carries your name. The archive can prove you made a contract from the chain (the deployer address is a fact). It can only guess at a name in a title.

The canonical run, in order

py -m vault sync                 # inventory the wallet (captures per-token collection)
py -m vault metadata --all       # each token's own metadata document
py -m vault artists --all        # collections + artist accounts
py -m vault provenance --all     # events from the marketplace index + the chain ledger
py -m vault enrich               # derive collected_at / collected_from / sales / classes
py -m vault origin               # mint + acquire venue   (MUST run after enrich)
py -m vault market               # receipt forensics: which marketplace really settled it
py -m vault editions             # true per-token edition size from the contract
py -m vault bids --platform auction   # reconstruct the bidding from calldata
py -m vault download --all       # originals, gateway rotation, sha-256, magic bytes
py -m vault site --base-url https://along7gallery.xyz
py -m vault factcheck            # re-verify chain facts into a ledger
py -m vault audit                # ~100 checks; 0 FAIL required to ship
py -m vault deploy               # rclone sync of site/ only, after the privacy grep

Order matters and the order was learned the hard way. origin labels a work "from the artist" only when the collector received it directly from the minter, and enrich is what fills in who the collector received it from. Run them backwards and a whole batch of gifts quietly become "unknown."

05

Getting the real files

The lazy way is to save whatever image the marketplace shows. That is wrong twice: it is a re-rendered copy, and it can differ from, or outlast, the artist's file.

deluge by ex_mortal: a single frame of a video work, pink blossoms rising out of dark moonlit water, the image breaking into square blocks of digital noise
_ d e l u g e _ · ex_mortal, one frame of a moving work How it got here: won at auction on SuperRare, November 25, 2025. ex_mortal makes video out of dying hardware, circuit-bent devices and modular video synthesis, in Florida. Video is the fragile end of the whole collection: a still can survive as a thumbnail somewhere, a loop that lives on a dead host is simply gone. So the archive keeps the file he actually published, not a preview of it, and this frame is the only still version that exists.
For programmers

The heart of the downloader, simplified. Two lines carry the weight: gateway rotation works because IPFS is content-addressed (the same hash returns the same bytes from any gateway), and the type sniffer reads the file's opening bytes instead of the server's label, because that label lies more than you would guess.

for nft in wallet_nfts(address):
    meta = fetch(nft.token_uri)                       # the token's own record
    url  = meta.get("animation_url") or meta["image"] # the artist's declared source
    data = fetch_with_gateway_rotation(url)           # ipfs:// -> several gateways, politely
    ext  = sniff_type(data[:64])                      # trust the bytes, not the header
    save(f"{contract}/{token}.{ext}", data, sha256(data))

def sniff_type(head: bytes) -> str:
    if head[:3] == b"\xff\xd8\xff":                return "jpg"
    if head[:8] == b"\x89PNG\r\n\x1a\n":           return "png"
    if head[:6] in (b"GIF87a", b"GIF89a"):         return "gif"
    if head[:4] == b"RIFF" and head[8:12] == b"WEBP": return "webp"
    if head[4:8] == b"ftyp":                       # ISO-BMFF: video OR an AVIF still
        return "avif" if head[8:12] in (b"avif", b"heic") else "mp4"
    return puremagic_guess(head)                   # everything else, last
Where the originals liveFilesNote
IPFS (content-addressed)3,251any gateway, any time, if the pin survives
Artist-run servers651the most fragile class; a domain lapse ends them
Arweave (permanent storage)326paid-once, meant to last
Marketplace cache only127the original host is already dead; labeled honestly on the wall
Fully on-chain22the art is the token; a data: URI inside the metadata
For artists: how to make your work survivable
  1. Put the real file on IPFS or Arweave, not only on your website. Arweave is paid once and meant to last; IPFS lasts as long as someone pins it, so pin it in two places.
  2. Make the token's own metadata point at that file. The image field is the still; if the work moves, put the moving file in animation_url. Archives read those two fields, nothing else.
  3. Keep the master. The archive can only keep what was published. If you rendered at 8K and published a 1080p, the 1080p is the work forever.
  4. Mint from your own contract when you can, or through a platform that writes your address as the minter. That is what proves authorship later, not your name in a title.
  5. Say who you are somewhere permanent. Half the artists in this archive have a bio only because they wrote it in a marketplace profile that still happened to be up when the pipeline ran. Nine of them sent theirs directly; those are the ones that will not rot.
Bug caught on day one

The pilot "saved" a batch of videos that were really tiny image stubs. For static works the marketplace serves a preview from its image CDN: an AVIF still inside an ISO-BMFF container, and naive sniffers see the ftyp box at offset 4 and call it MP4. The brand bytes at offset 8 say avif. Fourteen fake videos were purged and the rule that fell out of it became the whole philosophy: trust only what the artist declared, verify the bytes, and label anything you had to swap in. The collector spotted it first, by eye, in the 25-work pilot.

Bug caught in week eight

Two real JPEGs with Exif headers were sniffed as .db files because the general-purpose type library's confidence ordering misfired. They became undisplayable and their pages shipped a literal src="None", which the internal-reference audit caught. The sniffer above now decides the core image types from first-class magic bytes before it consults anything else.

The 403 wall

The Tezos ingest was the first truly bulk download, about 2,100 files. At a quarter-second between requests, the public IPFS gateways throttled the IP after roughly 270 files: uniform 403s from three gateways and 429s from a fourth, on every row, including rows that had worked minutes earlier. A naive retry loop would have marked live art as permanently failed at the three-attempt cap.

Read the error, not the countRows carrying the wall's signature got their attempt counter reset. Throttle victims are not dead pins.
Slow down and rotatePoliteness raised to 1.2s, the gateway list rotated per wave, ten-minute cooldowns, stop after two waves with no progress.
Characterize the residueAbout 173 files still 403'd from every gateway on any IP: a flagged artist's whole collection on a shared denylist, not throttling.
Find another doorobjkt's own CDN serves the same content by CID. Added as the last candidate for bare-CID URIs: 0 failed downloads.
Coming Home To Finite by Mizuyokaii: a diptych, a figure of liquid metal and flowers sitting in a meadow under a starry dusk sky, beside a handwritten poem of the same title
Coming Home To Finite · Mizuyokaii How it got here: sent by the artist on November 12, 2025, the day after I collected its sibling. Mizuyokaii's name joins mizu, water, and yokai, spirit; the figures are built from liquid, smoke, and light and are always on the way to being something else. This one ships as a diptych, the picture beside the poem it grew from, in her own handwriting: maybe forever is not about duration, maybe it is about depth. The file came at 6,625 by 4,608 pixels. That is the master, and it is why the "keep the master" rule above is not theoretical: the archived copy is exactly this, checksummed, and no marketplace ever served it at that size.
06

Reading the chain twice

A marketplace can relist a piece, hide it, or lose its history. The chain cannot. So provenance is replayed from the events the ledger wrote, not the story a website tells today.

Every work's transfer history is read from two independent sources, the marketplace's event index and the chain explorer's account ledger, and merged. A mint is simply the first transfer, sent from the zero address. Purchases go one layer deeper: the transaction receipt and its logs name the marketplace contract that actually settled the sale, which is how a wall label can say where a piece was minted and where it was collected and mean both.

mountain dew daze by Desultor: a skull painted in thick acrylic strokes of soda green, magenta, and yellow, paint dripping from the jaw, the artist's round stamp in the corner
mountain dew daze · Desultor, from drive-thru daydreams How it got here: won at auction on SuperRare, October 6, 2025. A skull the exact green of the soda, in acrylic thick enough to drip, and a token of the painting, and then the painting itself in the mail, because Desultor pairs the physical work with the NFT. His line for it: a file can become a painting, a token can become an object. The auction is one of the 215 the archive rebuilt bid by bid from calldata, so the wall label can show who else was bidding that night, not just that I won.

True collecting dates

Cold vaults receive; hot wallets collect. On day one every work's history ended at the moment it moved into cold storage. Tracing each one back through the collector's hot wallet gave 884 works their real collecting date: 530 mints, 54 purchases, 300 transfers.

TRACED THROUGH THE HOT WALLET

Editions from the contract

"Edition of N" is per token, not per collection. The archive calls totalSupply(id) where the contract implements it and sums mint events where it does not. A polaroid that once read "Edition of 4,290" now reads "Edition of 25."

CAUGHT BY EYE, FIXED BY CLASS

Bids from calldata

Working backward from each auction win, scanning transactions to the settling contract whose calldata carries both the token id and the contract, and carry ETH. Fully captures SuperRare and Transient; a sealed-bid drop needed its own selector scan.

740 BIDS · 215 AUCTIONS

Custody, all 3,735 works

Offline, from the ledger: does the latest transfer land in a tracked wallet? For multi-edition tokens the test is net balance, because other collectors keep trading the same token id after our copy arrived. The "latest hop" version raised 1,249 false alarms.

0 LEFT THE VAULT
For programmers

The custody test, as SQL over the merged event table. Single-copy tokens ask where the last hop landed; multi-edition tokens ask whether the wallets' net balance is still positive, because on an ERC-1155 the "latest transfer" of a token id is usually someone else's copy changing hands:

-- ERC-721 / FA2 one-of-ones: the last hop must land in a tracked wallet
SELECT n.id FROM nfts n
JOIN events e ON e.contract = n.contract_address AND e.token_id = n.token_id
WHERE e.ts = (SELECT MAX(ts) FROM events WHERE contract = e.contract AND token_id = e.token_id)
  AND lower(e.to_address) NOT IN (SELECT address FROM wallets);          -- these have LEFT

-- ERC-1155 editions: net balance across the tracked wallets must stay above zero
SELECT contract, token_id,
       SUM(CASE WHEN lower(to_address)   IN (SELECT address FROM wallets) THEN quantity ELSE 0 END)
     - SUM(CASE WHEN lower(from_address) IN (SELECT address FROM wallets) THEN quantity ELSE 0 END) AS held
FROM events GROUP BY contract, token_id HAVING held <= 0;                 -- these have LEFT
The bug that looked like a fact

The chain-explorer client returned None for a reverted eth_call. It also returned None for a rate-limited one, because the throttle envelope is a well-formed JSON response with a status of 0 and its result was being passed through unread. So a stage could record "this contract has no totalSupply" as a fact while being rate-limited. The fix: the throttle now raises its own exception, calls retry with exponential backoff, and the fact-check ledger records a throttle as "inconclusive," never as a result. Found by the fresh-eyes review in week eight; it had been there since day one.

For artists

If you ask a busy clerk for a file and they say "come back later," you have not learned that the file does not exist. The old code wrote down "file does not exist." The same thing happens to your work on marketplaces every day: a listing that says "no history" or "unknown creator" is usually a summary that gave up, not the chain. The chain still knows. Authorship in this archive is proven by one fact, that the wallet which deployed the contract is yours, and 462 artist contracts checked that way had 0 mismatches.

Technical

Public RPC eth_getLogs caps block ranges at 50k or worse, so full-history mint lookups go through a keyless indexer instead. Authorship is proven by getcontractcreation (deployer == artist wallet), never by name match or "the wallet touched the contract," which sweeps in art the artist merely collected. On Tezos, the indexer's token transfers carry only a transaction id; operation hashes are batch-resolved separately, and mints appear as transfers from null, which the pipeline maps onto its one mint signal, the zero address.

07

The truth machine

A museum should be able to prove what it puts on the wall. So a program checks every build before it ships, and a failed check blocks the release.

Creation by Gül Yıldız: Michelangelo's reclining Adam reaches from a rocky hillside toward a man in a bowler hat and black suit who holds out a green apple, carried on a brown cloud by two angels under a blue sky
Creation · Gül Yıldız How it got here: won at auction on Ninfa, July 12, 2026. Michelangelo's Adam reaching for a green apple held out by a bowler-hatted Magritte figure: two art histories shaking hands. Gül studied engineering before photography, is an Official Fujifilm Photographer, and has taught art history alongside her own practice, which is the kind of background that makes a joke like this land. Her wall label says "collected on Ninfa" because the transaction receipt names Ninfa's contract, and the audit will not let a venue appear on any label unless the address behind it is proven. That is check A3, and it fails the build on a single unproven word.

The audit started on day one with 21 checks and now runs 99, in numbered families. A sample of what they assert, each one born from something that actually went wrong:

CheckWhat it assertsWhy it existsNow
A5 internalEvery internal reference resolves (328,073 checked)The cleanup that deleted 890 filesPASS
A3 venue proofsEvery "minted on X" traces to a proven contract addressLabels once came from namesPASS
A4 no amountsNo purchase or bid amount anywhere on the public siteA museum, not a storefrontPASS
A6 stub tripwireNo sub-20 KB CDN file posing as an originalThe AVIF fake videosPASS
A20 AB tiersThe Art Blocks tier a stage derived is still there after every other stage ranThe blind-stamp regressionPASS
A21 dataset privacyThe CC0 export and the llms files carry no price and no private walletBulk download industrializes a leakPASS
A23 custodyEvery shown work's latest transfer lands in a tracked walletNothing had ever re-read the chainPASS
A23 mint proofSampled mint receipts agree with the stored mint timestamp4 minter-semantics mismatches, no data errorsWARN
A1 prose driftNumber words in the essays match the census"Seventeen hundred works. Four chains." vs 3,735 and sixPASS
A11 no em dashNo em dashes in any editorial text, template, or shipped scriptHouse style, and a tell for machine prosePASS
A24 dangling refsOrphan rows and dangling aliases stay at or below a baselineOne unmerged duplicate artist profileWARN
For programmers

A check is twenty lines, and the harness is a list of them. The shape that made this sustainable: every check has a stable id, a one-line claim, and a failure message that names the offending rows, so the audit report reads like a changelog of what the site promises. This is the venue-proof check, roughly as it runs:

def a3_venue_proofs(conn, check):
    proven = {r[0] for r in conn.execute(
        "SELECT lower(address) FROM address_book WHERE kind IN ('marketplace','platform')")}
    bad = [(r["title"], r["mint_venue"]) for r in conn.execute(
        "SELECT title, mint_venue, mint_venue_addr FROM v_main WHERE mint_venue IS NOT NULL")
        if (r["mint_venue_addr"] or "").lower() not in proven]
    check(not bad, "A3-venue-proofs",
          "every venue label traces to a proven contract address",
          f"{len(bad)} unproven venue labels: {bad[:5]}")

# ...99 of these; one FAIL and `py -m vault deploy` refuses to run
Numbers as tokens

The most data-science-flavored fix was the least glamorous. Sixteen essays and three dozen bios said things like "seventeen hundred works, four hundred artists, four chains" while the census said 3,735, 839, and six. The prose now writes {{n_works}}, {{n_chains}}, {{artist_tdxl}}, and the build renders them as words for small numbers and digits above that. Historical mentions that must not update ("the first day catalogued 893 works") go in an allowlist, and the drift gate parses number words back out of the live prose and compares. Prose is data. Treat it like data.

For artists

What this means on your wall label: nothing there is typed in. The venue, the mint date, the edition size, the collecting date, and the count of your works in the vault all come from the chain or the database, and a program re-checks them before every release. If a label about your work is wrong, it is wrong in the data, and the fix is one row, not one page.

After a deploy, the checks run again against the live pages, because green on a laptop and green on the open internet are two different facts. The CDN caches the bare asset URL for hours and a plain fetch can hit an edge that has the fix when the origin does not; the only proof that counts is a cache-busted fetch of the versioned asset the live HTML actually references.

08

Bugs worth keeping

Each of these is now a gotcha in the project's working notes, so it never has to be relearned. The chip says what caught it.

Rise of the Immortals by 1dontknows: a tall collage of old-master paintings, a youth in red on a rearing white horse, winged Father Time with a scythe above, a golden clock face, a hummingbird, and below them a tiger, a crocodile, and cherubs in the water
Rise of the Immortals · 1dontknows How it got here: won at auction on SuperRare, September 20, 2025. 1dontknows never studied art; he studied languages, lost a design job to COVID, and started rebuilding public-domain Renaissance paintings into new worlds. He leaves a clue in a corner of every picture and I am still looking for this one. Bugs hide the same way, in the corner of a thing that looks finished, which is why every one below has a chip saying who finally noticed.

LIMIT 1 returned the wrong wallet

SELECT address FROM wallets LIMIT 1 walked the primary-key index, which is alphabetical order, and picked the wrong wallet as "the vault." When which row matters, say so in SQL.

PILOT REVIEW

A collection named in math-italic Unicode

Produced a 260-character URL-encoded slug and broke Windows MAX_PATH mid-build. Slugs are now NFKC-normalized and every path segment is capped with a hash.

DAY ONE

The marketplace drops rows

The account API silently omits delisted or flagged works, and a fresh sync never re-adds them. Diffing the chain explorer's authoritative list against the database found 41 works sitting in the vault the whole time. The chain is the only inventory.

THE COLLECTOR NOTICED A MISSING WORK

The near-disaster

An orphan sweep keyed on one table deleted 890 files owned by four others: avatars, logos, hero videos, video posters. The audit failed the build with 2,255 broken references; the day-old backup restored 2,337 files from R2 in minutes. True orphans: 70 files, 33 MB.

AUDIT + BACKUP

The catch-all artist

One artist's room showed 34 works and 4 were his. A shared Art Blocks deployer wallet "owned" every project it hosted, so the build synthesized one fake artist from whichever title sorted first. The fix was a diagnostic query that found all three deployers with the same flaw.

THE COLLECTOR KNEW THE ARTIST

Click one work, get another

CSS specificity: .masonry .card outranked .hidden-card, so "show more" cards rendered visible while still classed hidden, and the lightbox, which excludes hidden cards, opened index 0. The collector narrowed it down from scroll position alone; a browser probe confirmed it.

THE COLLECTOR'S EYE + A CSS PROBE

Consolidated works lost their past

A piece won at auction on the hot wallet then moved to the vault kept only the internal hop in the marketplace's per-token history, hiding the auction. Backfilling the full transfer history from the chain brought 35 auctions and their bids back.

THE COLLECTOR REMEMBERED THE BIDDING

160 duplicates that weren't

A first-pass analysis flagged 160 duplicate events. 80 groups were partial ERC-1155 transfers in one transaction with different quantities; 3 were bundle buys with different prices. True duplicates: 3. Identity must include quantity and price.

FRESH EYES, THEN SELF-CORRECTED

A sealed-bid auction with no token id

One drop's placeBid() and revealAndBid() carry empty calldata, so no bid names a piece. Recovered by scanning the contract's transaction list for those selectors: 202 bids from 73 collectors, stored at the collection level and never asserted per piece.

THE COLLECTOR HAD BID IN IT
The pattern

Diagnose the class, not the instance. The Tanimoto page was a symptom; the query "collection owners hosting two or more distinct bylines that are not flagged as platforms" was the fix, and it left a reusable diagnostic behind. Same shape for the consolidated works, the sealed-bid recovery, and the duplicate events. The instance is the clue.

Impermanent Embrace by Adamtastic: an abstract of red and pink washes with orange flame strokes at the top and a band of blue along the base, threaded with white and black doodled lines
Impermanent Embrace · Adamtastic How it got here: won at auction on SuperRare, November 14, 2025. Adamtastic is a creative director whose stated goal is to spread joy, and the painting is a warm cloud of red and pink with a fire on top and a sea underneath, held together by scribbles. In the banner it is the cell behind the brand plate, mostly covered by my own choice, which felt right for a piece called impermanent. His room is also where two profiles turned out to be one person and had to be folded by hand: the kind of thing no query can decide.
09

Two hands on the keyboard

One human directing. Three Claude models across eight weeks. The AI did the engineering; the human made every call taste decides and did the seeing.

ModelWhenWhat that pass shipped
Claude Fable 5 Day one, and the mid-July vault upgrade The archiver, the database, the first gallery, the pilot-first method, the multi-chain expansion, the backups, the Art Blocks layer.
Claude Opus 4.8 The wiki run through the hardening sprint, then the TV and Tezos The wiki layer, stories, timeline, globe, the artists' recovered voices, marketplace forensics, bid capture, the security lockdown, the SEO and dataset layer, the /tv engine, the second blockchain.
Claude Fable 5.1 September 2 Re-read the chain, the prose, the indexes, and the database with no memory of having written them. The fact-check ledger, the prose-drift gate, the throttle bug, the 9x build speedup.

What the AI could do

The honest headline is speed at scale. A working archive, database, and public gallery existed by the end of the first day. Over the following weeks the same conversation learned to read three blockchains' APIs and one explorer's quirks, reconstruct auctions from raw calldata, transcode three hundred films for a television, fold duplicate artist identities across chains by handle and website and name, and render a 5,925-page site in about a hundred seconds. It built and audited a complete Spanish translation of the whole gallery, thirty thousand words, in one day. And eight weeks in, a fresh model found a bug that had been in the chain client since the first hour, because it read the code the way a reviewer would rather than the way an author does.

Forget Me Not by Lorenipsum: a painterly figure in a pink hat and coat, face left blank, crossing a city street collaged from newspaper, a yellow taxi behind; a frame of an animated work
Forget Me Not · Lorenipsum How it got here: won at auction on Foundation, July 17, 2025, one of the first auctions I ever won. Lorenipsum took the placeholder text as a name and the faceless figure as a signature: an empty vessel for whatever you pour in.
Napkin Woman by josenarciso: a bouquet of flat red, pink, blue, and yellow flowers on black stems in a white vase printed with a pixel-style cartoon figure, two grey circles on the floor beside it
Napkin Woman · josenarciso How it got here: won at auction on SuperRare, August 25, 2025. A Brazilian mixed-media artist; the woman is the pixelated figure printed on the vase, under a bouquet in four flat colours. In the banner it sits beside the brand plate, because its tall shape fought the wide cells everywhere else.
The habit that held it together

Make the machine prove things. When something was in doubt the answer was never "trust me," it was to write the check that settles it and read the check back. "The map sends you to the wrong piece" became a script that verifies which work every map point links to. "Are the numbers in the essays right?" became a parser that reads number words out of the prose. Treat verification as the deliverable, not the chore after it, and a project of this length stops collapsing under its own weight.

What only a human could do

None of the checks know what art is. The collector knew, from a thumbnail, that a "video" was a still. He knew which artist actually made a piece filed under a shared contract, which acquisition was a private deal the receipt forensics had mislabeled as a marketplace buy, which wallet must never appear on the site, and which of two identically named accounts was the real person. He read a room of 34 works and saw that 30 belonged to other people. He decided there would be no prices on the walls, that the artists' words belong next to the art, and that the whole thing should sound like a person and not a product. Every one of those decisions is now encoded in a curation file or a check, so the machine can hold the line he drew.

What the AI got wrong, honestly
  • It deleted 890 files because a cleanup script knew one table's view of a directory that four tables share.
  • It blind-stamped a generic category over hand-derived Art Blocks tiers on every run, and the live site lost them for a day before the collector noticed.
  • It wrote prose that sounded like a machine. The em-dash ban is now an audit check because the tell kept creeping back.
  • It let 26 bios and every collection-scale count in the essays go stale for weeks, because nothing compared hand-written numbers to the census until week eight.
  • It fixed a curated fact in the database three times before fixing it at the seed file, and the fact regressed three times.
  • It auto-linked the word "photography" inside an artist's own bio to a stranger's collection. Bios and artist statements no longer link out to other art at all.
Why a fresh model at the end

Every existing gate compared the database to the built HTML. Nothing re-read the chain and nothing checked the hand-written numbers, and the model that had written both was the least likely to notice. A model with no memory of the project read it the way a reviewer would, and the first thing it found was that the site's own essays disagreed with the site's own statistics page. The second-pass pattern from the wildfire and heat projects held here too: the second pass finds the bug the first pass structurally cannot.

10

Privacy by design

Open and private only fight if you never draw the line. The collection is public art, so the art is public, all of it, and the facts about it are published as a CC0 dataset. But:

I'll ask ChatGPT by vestica: a drawing of a person seen from behind typing at a laptop whose screen shows ChatGPT asking how can I help you, potted plants on the windowsill
I'll ask ChatGPT · vestica How it got here: won at auction on Transient, June 30, 2026, the same night as its sibling, Doodling while AI codes my salary. vestica translates inexplicable world concepts into relatable visuals, and lists their location as Everywhere. A fair piece to hang in the privacy section of a page about a museum an AI built: the joke is on all of us, and the wall label still shows no price.
Why it works

Privacy here is a property of the pipeline, not of carefulness in the moment. The scrub is structural and the tripwires run on every audit, so the one price string that did surface in the machine files was already public, editorial, and flagged rather than silently "fixed." The gates define what a leak is; prose does not get to redefine it.

11

Limits, stated plainly

Coronal Revelations by intrepid: a photograph of a total solar eclipse, the black disc of the moon ringed by the sun's white corona against a black sky
Coronal Revelations · intrepid How it got here: won at auction on SuperRare, June 2, 2026, and the fifteenth cell of the banner, filled late when another artist could not send a file. A photograph of the corona, which you can only see for the minutes the moon covers everything else. Most of the limits above are that kind of limit: the truth is there, the window to read it is narrow, and the honest thing is to say when the window was.
12

Reproduce the idea

Story vs code

This page is the write-up. The production pipeline stays private for now; what is public is the method above, the numbers, and the gallery itself. The shape of it in one screen, for anyone who wants to build their own:

for nft in wallet_nfts(address):                      # 1. inventory: count before you save
    meta = fetch(nft.token_uri)                       # 2. the token's OWN metadata document
    url  = meta.get("animation_url") or meta["image"] # 3. the artist's declared source, never a preview
    data = fetch_with_gateway_rotation(url)           # 4. ipfs:// -> several gateways, politely
    ext  = sniff_type(data[:64])                      # 5. trust the bytes, not the header
    save(f"{contract}/{token}.{ext}", data, sha256(data))
    events = replay_transfers(contract, token)        # 6. provenance from the chain, not the site
audit(site)                                           # 7. nothing ships with a failing check

Some downloads will fail because the original host is already gone. That is not a bug; it is the entire reason to do this.

ChoiceWhy
SQLite, one fileThe whole archive is a database and a folder. It backs up with a copy, rolls back with a copy, and needs no server to query.
Pilot on 25 before committing a gigabyteThe pilot caught the fake-video problem when it was 14 files instead of 400.
Static HTML, no framework, no CDNThe site folder is as archival as the vault. It works on a USB stick in thirty years.
Cloudflare R2 over GitHub Pages or Vercel200 files exceed their size caps (largest film: 288 MB). R2 has zero egress fees; 64 GB costs about a dollar a month.
Hand-authored curation as JSON, never as SQL updatesSeeds re-import the curation files every run; a fact fixed only in the database regresses on the next seed. It regressed three times before this stuck.
Every stage idempotent with a checked-at markerAny stage reruns alone; incremental by default, forced by flag. A killed build never leaves a truncated cache (encode to .part, then atomic rename).
Audit gate plus privacy grep, no exceptionsA gate that can fail quietly is decoration. Both gates are absolute, loud, and confirmed against the live site.
For artists, the short version

You do not need any of this code to protect your own work. Put the file on permanent storage, point the token's metadata at it, mint from an address that is yours, keep your masters, and write your bio somewhere that will still be up in ten years. Everything on this page is a collector doing, after the fact and at scale, what those five habits do for free at the moment of minting.

13

The fifteen

The banner at the top of this page, cell by cell, left to right and top to bottom. Each artist sent the file at full resolution; each tile links to the artist's room in the gallery.

Rijksmuseum 13 by Rebecca Rose: a collage of the Rijksmuseum's great hall, stained glass and brass chandeliers, marble statues, a dog on the carpet, and cut-out figures from old portraits with their faces removed
Rijksmuseum 13 · Rebecca Rose Why it is here: her pick for the banner from a folder of eleven variants. Rebecca Rose is a collage artist, forever cutting and kitbashing things together, and this is a museum kitbashed into a museum, which felt right for the wall of a page about building one.
Untitled by BoredJosei: a figure drawn entirely in loops of coloured scribble on black, holding up a phone whose screen glows with a city in red and blue
Untitled · BoredJosei Why it is here: the file BoredJosei sent for the banner: a figure made of nothing but coloured scribble, lit by the phone in its hand. An Indonesian artist also known as 0xJosei, working across Fragments, Garden of Shapes, and more, and one of the artists this collection keeps returning to.

All fifteen artworks remain the property and copyright of their artists. They appear here as part of the collection's record, not as anything for sale. Back to the top

Home