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.
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 · 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 · 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.
When
What shipped
The 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.
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
One contract, many artists. Art Blocks and fx(hash) host hundreds of projects under a handful of shared contracts. Each token's own metadata names its project, so the per-token collection field is what keeps a generative drop together and credited to the right person.
Platform-owned collections. SuperRare-style contracts where the "owner" is the platform. Attribution falls to the minter, or to an "Artist" trait, or to parsing "by NAME" out of the collection title, depending on the venue. Each mode is a named override.
Tezos. Attribution always follows the per-token creator recorded by the indexer, never the contract, because per-artist contracts there often have no owner at all. Learning that healed about 114 artist rooms in one fix.
Humans. A curation layer of JSON overrides (merges, collaborations, platform accounts that are not artists, works to hide, captions that are the artist's recovered words) wins over everything above. It is the only place taste is allowed to override the chain.
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.
_ 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 live
Files
Note
IPFS (content-addressed)
3,251
any gateway, any time, if the pin survives
Artist-run servers
651
the most fragile class; a domain lapse ends them
Arweave (permanent storage)
326
paid-once, meant to last
Marketplace cache only
127
the original host is already dead; labeled honestly on the wall
Fully on-chain
22
the art is the token; a data: URI inside the metadata
For artists: how to make your work survivable
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.
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.
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.
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.
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 · 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 · 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 · 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:
Check
What it asserts
Why it exists
Now
A5 internal
Every internal reference resolves (328,073 checked)
The cleanup that deleted 890 files
PASS
A3 venue proofs
Every "minted on X" traces to a proven contract address
Labels once came from names
PASS
A4 no amounts
No purchase or bid amount anywhere on the public site
A museum, not a storefront
PASS
A6 stub tripwire
No sub-20 KB CDN file posing as an original
The AVIF fake videos
PASS
A20 AB tiers
The Art Blocks tier a stage derived is still there after every other stage ran
The blind-stamp regression
PASS
A21 dataset privacy
The CC0 export and the llms files carry no price and no private wallet
Bulk download industrializes a leak
PASS
A23 custody
Every shown work's latest transfer lands in a tracked wallet
Nothing had ever re-read the chain
PASS
A23 mint proof
Sampled mint receipts agree with the stored mint timestamp
4 minter-semantics mismatches, no data errors
WARN
A1 prose drift
Number words in the essays match the census
"Seventeen hundred works. Four chains." vs 3,735 and six
PASS
A11 no em dash
No em dashes in any editorial text, template, or shipped script
House style, and a tell for machine prose
PASS
A24 dangling refs
Orphan rows and dangling aliases stay at or below a baseline
One unmerged duplicate artist profile
WARN
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 · 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 · 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.
Model
When
What 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 · 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 · 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:
Quiet wallets stay quiet. The pipeline knows which addresses are the collector's private ones, treats their activity as internal plumbing, suppresses them as minters, skips their bids, and a grep for the address across the built site must return zero hits before any deploy.
No prices, anywhere. Sales history lives in the database because it is provenance. The walls show venues, dates, and counts. A check fails the build on any amount, including inside story prose, with a two-item allowlist for a memoir's public numbers.
Only the built site leaves the machine. The database, the curation layer, the notes, the collector's own art files, and the API keys never ship. The dataset builder can only emit fields already public on the pages, so a leak would have to be visible on a wall first.
Artist code cannot touch the gallery. 293 interactive HTML works and 28 SVGs ship on the apex origin, because hosting the real interactive art is the point. They run in a sandboxed iframe without same-origin, and the edge serves a sandbox CSP on the media path, so even direct navigation loads them in an opaque origin.
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
Some bids are invisible by design. Foundation packs its auction state into events that need the verified ABI to decode; Ninfa settles via a generic signed-order call with off-chain bids. The site counts those as auctions won and does not invent bids for them.
Tezos venue proofs are labels, not addresses. Acquisition venues on Tezos come from the indexer's sale records and a marketplace-contract map; the proof column stays empty and the audit exempts it on purpose.
"Minter" is a blend. For most works it is the mint transaction's sender; for auction-house mints it is the first real owner. The fact-check treats a minter mismatch as a warning and only a block-timestamp disagreement as a failure.
127 works are cache copies. Their original hosts are dead. Nothing can bring the artist's file back; the label says so.
Layer-2 editions may stay unknown. The free explorer tier serves Ethereum log history but blocks it on Base and Polygon, so some edition sizes there are honestly null.
Curation debt is human debt. 17 artist bios are still stubs, 11 works are unattributed in a declared section, and one duplicate profile is waiting on a merge. The audit shows these as warnings, not failures, because they are decisions, not defects.
The ledger goes stale between runs. The chain fact-checks carry a date; a check older than 30 days warns. The custody claim above is true as of the date on it, not forever.
The pipeline is private. This page documents the method and the numbers; the production code, the curation layer, and the database stay on one machine. What ships publicly is the gallery and its CC0 dataset of facts.
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.
Choice
Why
SQLite, one file
The 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 gigabyte
The pilot caught the fake-video problem when it was 14 files instead of 400.
Static HTML, no framework, no CDN
The site folder is as archival as the vault. It works on a USB stick in thirty years.
Cloudflare R2 over GitHub Pages or Vercel
200 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 updates
Seeds 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 marker
Any 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 exceptions
A 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 · 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 · 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