@artblocks/abx-cli 0.1.0-alpha.26 → 0.1.0-alpha.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/skill/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: abx-self-host
3
3
  description: Launch and operate a self-hosted ABX NFT end to end with the ABX CLI (`abx`) on testnet — a 1/1 (`abx deploy`), a multi-token Series from a folder of media (`abx deploy-series`), or a generative/code drop (`abx deploy-code`). Covers on-chain vs off-chain metadata, storage custody (local disk, S3/R2, IPFS, Arweave), deploy + mint (now or pre-warmed at a predicted address), rendered thumbnails and on-chain traits for code projects, collector-configurable on-chain parameters (PostParams — typed, auth-gated, settable by creator/token-owner/address), primary sales via the shared fixed-price minter, owner ops (transfer, refresh, re-point URIs, royalties, pause/unpause, supply cap, delegate minting, and the one-way locks: fields, URI config, script, dependencies, param hooks), and optionally listing a deployed collection in the ABX App Store (`abx submit-app`, never folded into deploy). Use when the user wants to self-host an ABX project, take an image to an NFT on testnet, deploy a collection from a folder of images, launch generative/code projects (art, collectibles, game assets, or anything else), add collector-settable parameters/traits, mint or run a primary sale, refresh a listing, lock down what a project stores or freeze its param hooks, operate a project they launched, list an ABX app in the App Store, choose a storage backend, stand up hosting they own, or point a project at a hosted/managed metadata provider with an API key.
4
4
  compatibility: Drives the abx CLI (@artblocks/abx-cli). Co-versioned with it — install/refresh with `abx skill install` so this skill matches the CLI's `abx version`. Requires Node 22.5+.
5
5
  metadata:
6
- version: "0.1.0-alpha.26"
6
+ version: "0.1.0-alpha.28"
7
7
  ---
8
8
 
9
9
  # ABX Self-Host Toolkit (`abx`)
@@ -20,18 +20,63 @@ L3 agentic surface: image → live self-hosted NFT the creator owns — a **1/1*
20
20
 
21
21
  ## Read first (every session)
22
22
 
23
- - **YOU run the `abx` commands — never tell the creator to run one.** You have a shell; use it. Run `doctor`, `status`, `--dry-run`, `tokenuri`, `state`, `refresh`, `tokens`, etc. yourself and read the output — don't paste a command and wait for them to run it or copy back results. The creator's hands-on steps are approving in their **browser wallet** (`--sign`), giving you a value you asked for (their address, a name), and **looking at the work in `abx preview`** — you run that command, but the URL it prints is theirs to open and play with ([Phase 0](#phase-0--make-the-work-first-skip-every-gate-below-until-its-good)). Even "next steps" after a deploy: **run the read-only ones** (`tokenuri` to prove it resolves) and **offer to run** the actions (`refresh`, `unpause`, `mint`, and — if they want the collection in the ABX App Store — `submit-app`) — don't hand over a list of commands to run. Exceptions: a genuinely interactive/again-in-their-env command (an OS login, `gcloud auth`), and the **in-chain Solidity lane's Foundry step** (`forge build/test/deploy` a renderer — `abx` never compiles/deploys Solidity; see [Code projects](#code-projects-generative--code-based-drops)) — then run it if you have the tool, else hand it over.
23
+ - **YOU run the `abx` commands — never tell the creator to run one.** You have a shell. The creator's
24
+ only hands-on steps: approving in their **browser wallet** (`--sign`), giving you a value you asked
25
+ for, and **looking at the work in `abx preview`** (you run it; the URL is theirs). Post-deploy, run the
26
+ read-only next steps and *offer* the actions. Exceptions: an interactive command in their environment
27
+ (an OS login), and the Solidity lane's Foundry step — `abx` never compiles or deploys Solidity.
24
28
  - **Version drift — `abx doctor`'s "Version & provenance" block catches it: binary source (npx is a ✗ — it can silently keep serving a stale cached copy), npm currency, and skill↔CLI match, each with its own fix command.** Run it before any deploy and act on a ✗ yourself (`abx skill install` / `npm i -g @artblocks/abx-cli@latest`) — never just relay the notice: a stale CLI can hold canonical addresses that have since moved, so it deploys against dead singletons.
25
29
  - **`abx doctor` first, always** — full preflight (Node, pnpm, RPC, signing key, storage). Fix any ✗ before deploying ([Setup](#setup--environment)). A missing public-base-url is not a "set up IPFS" signal: for tiny content go on-chain, for larger content pick an off-chain backend — see [Quick start](#quick-start).
26
30
  - **Never collect secrets in chat, and never `cat`/`grep` `.env`.** Keys, `PINATA_JWT`, S3 secrets, provider API keys → the project's `.env`. The Arweave/Turbo key is a CLI-managed file (`.abx-self-host/arweave-key.json`) — never paste it. Name the var/file; never take the value. **To see what's configured, ask the tool, not the file:** `abx doctor` and `abx remote` report each credential as set/unset without ever printing one. Reading `.env` spills every secret in it into the transcript — irreversible, and a plain `abx doctor` tells you the same thing.
27
31
  - **Never quote a third party's price or free tier.** Hosting plans, pinning plans and gateway tiers change without telling us, and a creator makes a real decision on the number you say. Name the provider and send them to its own pricing page; if you must compare, compare *shapes* — pay-once vs. monthly, "lapses if you stop paying" vs. "nothing to renew" — which is the part that stays true. The one figure that is ours to state is Turbo's **under 100 KB free** tier, because the CLI enforces it and prints it.
28
- - **Testnet only today** — every launch is on a testnet: **Base Sepolia by default** (`ABX_CHAIN` unset), with **Sepolia** also shipped (`ABX_CHAIN=sepolia`). Say "testnet"; don't imply mainnet. **Testnet IS the preview + e2e environment**: it runs the *real* wiring (renderers, generator, on-chain tokenURI assembly), so a creator should deploy there, inspect the actual result (`abx tokenuri` / the live view / `abx verify`), confirm it looks right, and only *then* go to mainnet — no separate local "preview" is as faithful as the real testnet drop, and a testnet deploy is ~free + ~minutes. **One cross-chain gotcha: on-chain library deps (`--dep p5@…`) resolve to on-chain bytes only where an Art Blocks dependency registry exists — that's Sepolia, NOT Base Sepolia.** A no-dependency script (vanilla JS/GLSL) goes fully on-chain on either; a drop that needs a registry-hosted library on-chain must target `ABX_CHAIN=sepolia` (or run the resolver lane).
29
- - **Scope today = testnet, two standards.** The shipped standards are **ERC-721** — a 1/1, a **Series** of many, or a code drop, each token **unique** — and **ERC-1155 editions** — **copies** of one work — reached with **`--copies <n|open>`** on those same three deploy commands, on Base Sepolia (default) or Sepolia via `ABX_CHAIN`. There is still **no `--chain` flag (pick the chain with `ABX_CHAIN`) and still no `--erc1155`/`--standard` flag** — don't invent one; mainnet is roadmap, not something you flip here. Map the ask to the flag, never the standard name (nobody says "ERC-1155"): **"N copies of the same piece" / "an open edition"** → add `--copies <n|open>` to the matching command; **"N unique pieces"** → a plain **Series**, exactly as today (`--copies` absent). If a creator needs an unsupported chain, say plainly it's not in the toolkit today rather than fabricating a recipe.
32
+ - **Testnet only today** — **Base Sepolia by default**, **Sepolia** via `ABX_CHAIN=sepolia`. Say
33
+ "testnet"; never imply mainnet. Testnet **is** the preview environment: it runs the real wiring, so a
34
+ creator should deploy there, inspect the actual result, and only then consider mainnet. One
35
+ cross-chain gotcha: **on-chain library deps (`--dep p5@…`) need the Art Blocks dependency registry,
36
+ which exists on Sepolia and NOT Base Sepolia** — a no-dependency script goes on-chain on either.
37
+ - **Scope = two standards.** **ERC-721** (a 1/1, a Series, or a code drop — each token unique) and
38
+ **ERC-1155 editions** (copies of one work), reached with **`--copies <n|open>`** on those same three
39
+ deploy commands. There is **no `--chain` flag** (use `ABX_CHAIN`) and **no `--erc1155`/`--standard`
40
+ flag** — don't invent one. Map the ask to the flag, never the standard name: "N copies" / "an open
41
+ edition" → `--copies`; "N unique pieces" → a plain Series. If a creator needs an unsupported chain,
42
+ say plainly it isn't in the toolkit rather than fabricating a recipe.
30
43
  - **Is the work finished yet?** If the creator is still *making* the piece, you're in **[Phase 0](#phase-0--make-the-work-first-skip-every-gate-below-until-its-good)** — iterate on the work and keep every deploy question off the table until they say ship. The gates below apply to launching something that already exists.
31
44
  - **Unfilled placeholders are not a brief.** If their prompt still contains `⟨your idea⟩`, `⟨your idea here⟩`, or any unfilled template, STOP. Ask what they actually want to make. Do **not** treat the placeholder as a creative brief, and do **not** run `abx demo` or send a transaction unless they explicitly asked for a demo.
45
+ - **STOP if you are about to write or deploy a contract yourself.** Everything here is a clone of an
46
+ ownerless **trust anchor**, and `isAbxClone` against it is the ONLY signal marketplaces and the App
47
+ Store allowlist against. A contract you write — or `--bootstrap-factory` on a public chain — can never
48
+ gain it: **canonicity is decided by which contract deployed it and cannot be added afterwards.** So if
49
+ the ask seems to need something the deploy commands don't do, do NOT build around it. Say: *"I can do
50
+ that, but your collection won't be a canonical ABX contract — marketplaces won't recognise it, and the
51
+ only way back is redeploying and moving holders. Want that, or shall we find a way inside the
52
+ toolkit?"* Then wait. Building your own contracts is a legitimate creator choice; making it **for**
53
+ them silently is not. (`abx state <addr>` prints `canonical` first.)
54
+ - **Burn is opt-in per collection.** Pass `--burnable` at deploy (default off) to let a **holder or
55
+ their approved operator** burn their own token (`burn(...)`); without it, a minted token can never be
56
+ destroyed by anyone (including the owner). There is no owner burn bypass either way. This is the seam
57
+ for burn-to-combine / breeding / redemption — build the mechanic in a contract that holds the tokens
58
+ and burns them. `burnable()` reads the setting; it is fixed at deploy. **On a burnable capped
59
+ edition** the per-id cap is a *live* ceiling — a burn frees a slot the creator/minter can re-mint, so
60
+ "N copies" means "at most N at once", not "N ever" (a burnable `--copies 1` edition can be re-minted
61
+ after a burn). The 721 lanes' cap is "N ever". Non-burnable editions have no such gap.
62
+ - **What this toolkit does NOT do** — check here before inventing a way:
63
+ no secondary listings · no mainnet · fixed-price sales only (auctions and
64
+ allowlists would be different *minters*, not flags) · no post-deploy script replace · and `abx` never
65
+ compiles or deploys a token contract (`scaffold-renderer` writes a renderer, not a collection). If the
66
+ ask needs one of these, say so plainly instead of building around it.
67
+ - **Two extension points keep a custom mechanic canonical — reach for these first.** A collection can
68
+ point at **your own minter** (`abx set-minter`), and a code project can arm **param hooks**; both keep
69
+ it a factory clone. Two caveats: neither is scaffolded (real engineering), and a **`--transfer` hook is
70
+ a VETO, not a trigger** — it can refuse a transfer but never *cause* a mint or burn, and a reverting
71
+ hook stops transfers **and mints** for every token. So a hook cannot implement "combine two into one".
32
72
  - **Two gates decide everything: (1) demo or real? (2) who signs?** Settle both first — *once there's something to launch*.
33
73
  - **Confirm the full config before any on-chain write** ([readout](#confirm-before-sending)); wait for go-ahead. Never invent a field silently (name/symbol from filename, an auto description) — show it, flag it `inferred`.
34
- - **Never hand-build a service URL — ask the chain, then check the reference.** A contract commits its own metadata URL on-chain, so `abx tokenuri <addr> --fetch` (token) and `abx contracturi <addr>` (ERC-7572 collection) give you the answer *and* follow it, printing **what is actually served** — no route grammar to remember, no curl. (Bare `abx tokenuri` reads the chain only; `--fetch` is what GETs the URL the contract names.) **A 404/error on a URL you constructed is evidence about your URL, never about the service.** Don't infer a path from a similar-looking one (dropping the token id off `/t/<chain>/<addr>/<id>` does **not** give collection metadata — that's `/c/<chain>/<addr>`); look it up in [hosting.md](reference/hosting.md#token-api-the-resolver). Before telling anyone a service is broken, reproduce it with a **CLI command** — a real service miss says which of three things it is in a machine `code` (`invalid_request` = your path shape · `unknown_route` = no such route here · `not_registered` = this node doesn't index that contract), and none of those mean "down".
74
+ - **Never hand-build a service URL — ask the chain.** `abx tokenuri <addr> --fetch` and
75
+ `abx contracturi <addr>` follow the URL the contract itself commits and print what is actually served.
76
+ **A 404 on a URL you constructed is evidence about your URL, never about the service.** Don't infer a
77
+ path from a similar one (`/t/<chain>/<addr>/<id>` minus the id is not collection metadata — that's
78
+ `/c/<chain>/<addr>`). Before calling a service broken, reproduce with a CLI command: a real miss returns
79
+ `invalid_request` · `unknown_route` · `not_registered`, none of which mean "down".
35
80
  - **Safe to explore:** `abx <cmd> --help` and `abx deploy --dry-run` never send. You never run a real write just to learn flags.
36
81
  - **`deploy` returns; `demo`/`serve`/`preview` block** (they serve) — background them or warn. Background `abx preview` and relay its URL, then keep working while the creator looks; `--shoot` is the one preview mode that exits on its own.
37
82
 
@@ -43,10 +88,11 @@ Route by the **content** first, then apply the gates below. The three paths diff
43
88
  |---|---|---|---|
44
89
  | **one image** (a 1/1) | `abx deploy` | on-chain (tiny content) or off-chain — **no server possible** | the image itself |
45
90
  | **a folder of images** | `abx deploy-series` | same — on-chain or off-chain, **no server possible** | each image itself |
91
+ | **a program** (generative / code) | `abx deploy-code` | **a resolver you run** (live seed + PostParam injection) — OR `--onchain-uri` (tokenURI on-chain; the canonical generator computes the live view) | **rendered off-chain** by the effect runner, else a placeholder |
46
92
 
47
93
  **Any row also takes `--copies <n|open>`** — it makes that work/collection an ERC-1155 **edition** (copies, not unique tokens; `open` = an uncapped open edition, the flagship edition product). Drop it for a unique token, exactly as today. Edition owner ops (mint copies, per-id supply cap, per-id sales) → [operating.md](reference/operating.md#edition-owner-ops).
48
94
 
49
- **Custody on an edition is now symmetric with the 721 side** — on-chain bytes, off-chain-image-with-on-chain-JSON, inline SVG, or a resolver all work with `--copies`, and `deploy-code --copies` takes on-chain `--dep`s. Two limits remain: **`--onchain-image` can't be signed offline** (refused on `--unsigned` everywhere — each chunk tx feeds the next; use hot or `--sign`), and **`deploy-code --copies` is `--script`-only** (no `--code-dir`, no field renderers). Full matrix, including the O(1) folder trick for a raster edition with no server: [decisions.md → `--copies` custody](reference/decisions.md#copies-erc-1155-editions--custody-is-not-orthogonal-to-the-shape).
95
+ **Custody on an edition is now symmetric with the 721 side** — on-chain bytes, off-chain-image-with-on-chain-JSON, inline SVG, or a resolver all work with `--copies`, and `deploy-code --copies` takes on-chain `--dep`s. Two limits remain: **`--onchain-image` can't be signed offline** (refused on `--unsigned` everywhere — each chunk tx feeds the next; use hot or `--sign`), and **`deploy-code --copies` is `--script`-only** (no `--code-dir`, no field renderers). Full matrix, including the O(1) folder trick for a raster edition with no server: [decisions.md → `--copies` custody](reference/decisions.md#--copies-erc-1155-editions--custody-is-not-orthogonal-to-the-shape).
50
96
 
51
97
  > **`--onchain-uri` puts the JSON on-chain — the IMAGE follows one of three routes, and the creator
52
98
  > is choosing between them whether they know it or not.** An **SVG** is inlined (bytes on-chain,
@@ -57,34 +103,39 @@ Route by the **content** first, then apply the gates below. The three paths diff
57
103
  > `--onchain-image --compress fastlz` (**keep it under ~40 KB/token — that's a READ-gas ceiling, not a
58
104
  > cost one; see [Quick start](#quick-start)**). Say which one you're giving them: "no server" and
59
105
  > "on-chain" are not the same promise.
60
- | **a program** (generative / code) | `abx deploy-code` | **a resolver you run** (live seed + PostParam injection) — OR `--onchain-uri` (tokenURI on-chain; the canonical generator computes the live view) | **rendered off-chain** by the effect runner, else a placeholder |
61
106
 
62
107
  **The dividing line is static content vs a running program.** Static content is self-resolving (the file *is* the thumbnail, nothing to keep running); **a code project's thumbnail is *rendered* off-chain, so it ALWAYS needs a public home you provide (`--image-base` bucket, or a resolver) — settle that infra fork with the creator FIRST** (details in [Code projects](#code-projects-generative--code-based-drops)). Nail the project type before the gates.
63
108
 
64
109
  **Want collector-settable state (a palette a holder picks, a governed dial, an open/communal input)? That's PostParams — a `deploy-code` capability, and the reason to reach for it isn't only "generative."** Any piece that needs typed, on-chain, auth-gated parameters (settable by the creator, the token owner — delegate.xyz honored — or a named address/contract) is a code project: declare them with `--schema key:Type:Auth`, set them later with `abx configure-param`, and every declared key and every set value reads **straight off the contract** — no resolver, no indexer, no metadata JSON in the middle (`abx state` for the schemas, `abx tokens` for the values). They work on the JS lane *and* the in-chain Solidity renderer lane. Full catalog: [Code projects → PostParam schema](reference/code-projects.md#postparam-schema--the-type--auth-catalog).
65
110
 
66
- > **⚠ On an edition (`--copies`), a holder-writable param is SHARED — say this before the creator commits.** Params belong to the **id**, and every holder of that id holds the same id, so one value serves all of them and the **last writer wins**. `TokenOwner` means *any* holder. A "name your copy" schema doesn't name a copy — it renames the work for all 1,000. If each collector should configure their own, that needs **one id per copy** (a 721 Series, or `--copies 1` ids). Aggregate/communal state (a counter, a shared mood) is exactly what shared params are *good* at. And a holder-writable **`String`/`Bytes`** key has **no on-chain size cap** one holder can store large valid values under every declared key and push that id's `tokenURI` past what common RPCs will serve, for every co-holder, **permanently** if a `lock=` deadline then bites. That is a deliberate protocol choice (byte accounting on chain would tax every project to police a configuration almost nobody should use), which makes *saying it here* the actual mitigation. `deploy-code --copies` warns when a `--schema` is holder-writable relay the warning, don't skip past it. Depth → [code-projects.md → shared params on an edition](reference/code-projects.md#postparam-schema--the-type--auth-catalog).
111
+ > **⚠ On an edition (`--copies`), a holder-writable param is SHARED — say this before the creator commits.** Params belong to the **id**, so one value serves every holder of it and the **last writer wins**; `TokenOwner` means *any* holder. A "name your copy" schema renames the work for all 1,000. Per-collector state needs **one id per copy** (a 721 Series, or `--copies 1` ids); communal state is what shared params are *good* at. A holder-writable `String`/`Bytes` key also has **no on-chain size cap**, so one holder can bloat that id's `tokenURI` past what RPCs serve, for everyone, permanently if a `lock=` then bites `deploy-code --copies` warns, relay it. Why it is deliberate, and the full shape → [code-projects.md → shared params](reference/code-projects.md#postparam-schema--the-type--auth-catalog).
67
112
 
68
113
  **Planning a priced primary sale? Decide 1/1 vs Series (or edition) BEFORE deploying — it's irreversible.** The shared fixed-price minter sells a **Series** (mint-on-purchase); a plain `abx deploy` **1/1 has no minter/pause/payee**, so its only post-mint move is `abx transfer` (settle an off-chain sale). To run a native fixed-price sale of even a *single* piece, deploy it as a **1-token Series** (`abx deploy-series --count 1`) — or, if copies of that one piece are fine, as an **edition** (`abx deploy --copies <n|open>`), which ships the full sale stack (minter/pause/payee) on its own, no Series wrapper needed. abx has **no secondary-listing feature** — reselling a held token means an external marketplace or a manual `transfer`. Full detail: [operating.md → Selling](reference/operating.md#selling--the-shared-fixed-price-minter).
69
114
 
70
115
  ## Phase 0 — make the work first (skip every gate below until it's good)
71
116
 
72
- **If the creator is still making the piece, you are in the studio, not in a deploy. Stay there until they say ship.** The gates and decisions below are for *launching* something that already exists — reaching for them while someone is still designing is the single most common way this skill feels wrong to use. A real session: the creator said *"I want to work on one with you"* and got asked about metadata resolution, hosting, and wallet ownership before a single pixel existed. They had to push back with *"let's work on actually designing the piece together first."* Don't make them.
117
+ **If the creator is still making the piece, you are in the studio, not in a deploy. Stay there until they
118
+ say ship.**
73
119
 
74
- **Which mode are you in?**
75
- - **They handed you a finished file** (`sketch.js`, a build dir, an image folder) → skip this section, go to [Gate 1](#gate-1--demo-or-real-launch).
76
- - **They brought an idea, a reference, a vibe, or "let's make one together"** → Phase 0. The deploy is a footnote at the end of an afternoon of work; treat it that way.
120
+ - **Handed a finished file** (`sketch.js`, a build dir, an image folder) → skip to
121
+ [Gate 1](#gate-1--demo-or-real-launch).
122
+ - **Brought an idea, a reference, a vibe, "let's make one together"** → Phase 0.
77
123
 
78
- **During Phase 0, these are OFF the table** — do not ask, do not "just quickly confirm," do not pre-emptively lay out the tradeoffs: hosting/lane, thumbnails, traits-on-chain, storage permanence, wallet address, supply cap, royalties, mint count, name/symbol. Every one of them is answerable in five minutes *after* the work is right, and asking early reads as pressure to ship something half-made. The **one** exception is a constraint that changes what you'd *write*: if they want an on-chain library (`p5`), say early that on-chain deps mean `ABX_CHAIN=sepolia` — that's an authoring constraint, not a deploy decision.
124
+ **OFF the table until they ship** — don't ask, don't "just quickly confirm", don't pre-emptively lay out
125
+ tradeoffs: hosting/lane, thumbnails, traits, storage, names, symbols, royalties, gas, minting.
79
126
 
80
- **The loop** depth + the `--shoot` details → [reference/code-projects.md → Studio loop](reference/code-projects.md#studio-loop--iterate-on-the-work-before-you-deploy-anything):
127
+ **The loop** (depth → [code-projects.md → Studio loop](reference/code-projects.md#studio-loop--iterate-on-the-work-before-you-deploy-anything)):
81
128
 
82
- 1. **Write against the real runtime contract from the first draft** — `abx.tokenData.seed` for randomness, `abx.traits({…})` for features. Not `Math.random()` "for now": a piece prototyped on `Math.random()` looks finished and then deploys as N identical tokens, and retrofitting the seed late means re-tuning every visual you just approved.
83
- 2. **Run `abx preview --script art.js` and give the creator the URL.** It serves the *same document the generator serves* (real `abx.js`, real tokenData) on `localhost:8788`, so they get a seed shuffle, real inputs for every `--schema` PostParam, a live traits readout, and `/grid` for N seeds at once. **This is the one place you hand over a link instead of running it for them** the work is theirs to judge, and an animated piece cannot be judged from a screenshot. Don't hand-roll a preview page; a stub you write yourself will run a sketch that reads its seed wrong.
84
- 3. **Edit and tell them to refresh.** The program is re-read from disk per render — no restart, no watcher. To check your own work between rounds (you have no browser), `abx preview --script art.js --shoot ./frames` renders the same document headlessly and flags the two silent killers: no traits reported, or identical traits across every seed.
85
- 4. **Take feedback and go again.** Expect several rounds. Rounds are the point — "add faces to the shapes" is a normal Phase 0 request, not scope creep.
129
+ 1. **Write against the real runtime contract from the first draft** — `abx.tokenData.seed` for
130
+ randomness, `abx.traits({…})` for features. Never `Math.random()` "for now"; retrofitting is a rewrite.
131
+ 2. **`abx preview --script art.js`, hand them the URL.** Same document the generator serves. This is the
132
+ one place you hand over a link instead of running it for them.
133
+ 3. **Edit, tell them to refresh.** The program is re-read from disk per render — no restart, no watcher.
134
+ `--shoot <dir>` gives you frames to check your own work.
135
+ 4. **Take feedback, go again.** Several rounds is the point, not scope creep.
86
136
 
87
- **Exit only on an explicit ship signal** ("let's deploy this", "I'm happy with it"). Then run `abx inspect <script>` and open the deploy decisions — and say plainly that some are irreversible, so it's worth a few minutes ([Code projects](#code-projects-generative--code-based-drops)). If *you* feel the pull to start the deploy conversation while they're still iterating: don't. Ask what they want to try next.
137
+ **Exit only on an explicit ship signal** ("let's deploy this", "I'm happy with it"). Then `abx inspect
138
+ <script>` and open the deploy decisions.
88
139
 
89
140
  ## Gate 1 — demo or real launch?
90
141
 
@@ -108,25 +159,38 @@ Every write builds an unsigned tx; pick the lane by stakes:
108
159
  | **Wallet** | `--sign` | human's own wallet (MetaMask/Ledger) | real value · key shouldn't touch `.env` |
109
160
  | **Cold** | `--unsigned` | multisig / offline signer | a Safe / advanced setup |
110
161
 
111
- - **Lane detection:** you hold the key + low stakes → hot. Human owns the valuable wallet → wallet. Multisig → cold. Beyond throwaway testnet, lead with `--sign`.
112
- - **Missing key is a fork, not a blocker.** If you'd pick hot but `.env` has no key, offer both: add a funded key, or `--sign` in a browser wallet.
113
- - **On the wallet lane, ALWAYS ask "which wallet will you connect?" and pass `--for <addr>` — don't offer a "just connect whatever" path.** The connecting wallet becomes owner + mint recipient + royalty receiver, so `--for` pins it and makes the sign page + CLI refuse a mismatched wallet (a real session skipped this and let a random connected wallet own the collection). All three deploy commands **warn on `--sign` without `--for`** (*"whichever wallet connects becomes owner + royalty receiver + mint recipient — pass --for to PIN it"*); treat that as a prompt to get the address, not to proceed.
114
- - **`--for` is a DEPLOY concern, not an owner-op one.** It pins who *becomes* the owner at deploy. Post-launch owner ops (`set-royalty`, `transfer`, `minter configure`, `set-minter`, `pause`, …) already sign as the contract's **current on-chain owner** — the wallet lane targets that automatically so `--for` isn't needed there (passing it is harmless but ignored). Just connect the owner wallet.
115
- - **One EOA, serialize every sendor use separate keys.** Two processes sharing one key will pick the same nonce; one tx dies as `replacement transaction underpriced`. Don't background two writes from the same signer.
116
-
117
- **⚠ `--sign` BLOCKS until the human signsALWAYS background it with `--sign-url-file`, NEVER foreground.** Foreground hangs your whole turn (you can't read the URL or talk to the human) and looks frozen. The #1 way agents break the wallet lane. The flow:
118
-
119
- 1. Background-run `--sign --sign-url-file <path>` (e.g. `/tmp/abx-sign-url`).
120
- 2. Read the URL from that file (fallback: grep output for `ABX_SIGN_URL=`) **every time — never assume the port**. Each op gets a fresh server; if the friendly port (8799) is still busy it auto-falls back to a different one, so the file is the source of truth. Server binds in ~1–2s; poll a couple times.
121
- 3. Relay: "Open `<url>`, connect your wallet, approve." Don't auto-open (node may be remote). Multi-tx tell them it's N approvals in one session.
122
- 4. Human approves; only the signed tx hash returns.
123
- 5. Let the background command finish (it signs + confirms + re-indexes), then report. Don't kill it.
124
-
125
- **⚠ Run wallet-lane ops ONE AT A TIME — never start the next `--sign` op until the previous background command has finished (step 5).** Each op is its own process serving its own page; overlapping them means the human can land on the *previous* action's page, which (having already completed) shows "done" instantly — that "done" is the last op, not the new one. If a session is genuinely stuck (human never signs), the next op will open a *fresh, different* URL — so relay the new URL from the file and tell them to open that one, not the old tab.
126
-
127
- - **Sign page is operation-aware** — shows decoded intent ("Transfer #0 → 0x…"), gates on network + signing wallet + the tx, and the CLI refuses a mismatched signer server-side too.
128
- - **Multi-tx signs in ONE session** (hot + wallet only). `--onchain-image` stages bytes before the deploy that references them; the human connects once and walks `Transaction 1 of N`. Cold can't (staging is interactive) — `--unsigned --onchain-image` errors; use `--send`/`--sign`.
129
- - **Keep tx count low and say it up front — every preview prints `approvals N wallet approval(s)`, the exact signature count.** A configured token deploys in one tx; `--onchain-image` adds staging tx(s); several owner edits collapse into one atomic `multicall`. Off-chain storage uploads (IPFS/Arweave) are listed separately in the preview and never counted — they happen before signing with no wallet prompt. *Exception:* `--storage-signer eth --sign` turns each upload into a same-session `personal_sign` — then it IS counted.
162
+ - **Pick it:** you hold the key + low stakes → hot. Human owns the valuable wallet → wallet. Multisig →
163
+ cold. Beyond throwaway testnet, lead with `--sign`.
164
+ - **A missing key is a fork, not a blocker** offer both: add a funded key, or `--sign` in a browser.
165
+ - **On the wallet lane ALWAYS ask "which wallet will you connect?" and pass `--for <addr>`.** One connect
166
+ assigns **all three roles at once**owner, royalty receiver, mint recipient so never offer "connect
167
+ whatever". When you ask, say what you're asking for: **`--for` takes their PUBLIC address (`0x…`), the
168
+ one they'd paste to receive a transfernever a private key or seed phrase.** A creator nervous about
169
+ crypto hears "give me your address" as "give me your keys" unless you say otherwise.
170
+ - **The sign page connects an INJECTED browser wallet only** (`window.ethereum`) — no WalletConnect, no QR.
171
+ A hardware wallet works through a browser extension that exposes it (MetaMask with a Ledger attached,
172
+ say), not by scanning a code. Say that before they go looking for a QR.
173
+ - **`--for` is a DEPLOY concern only.** Owner ops already sign as the current on-chain owner; passing it
174
+ there is harmless but ignored.
175
+ - **One EOA, serialize every send** — two processes sharing a key pick the same nonce and one dies
176
+ `replacement transaction underpriced`.
177
+
178
+ **⚠ `--sign` BLOCKS until the human signs ALWAYS background it with `--sign-url-file`, NEVER
179
+ foreground** (a foreground run hangs your whole turn):
180
+
181
+ 1. Background `--sign --sign-url-file <path>`.
182
+ 2. Read the URL **from that file every time — never assume the port** (each op gets a fresh server).
183
+ 3. Relay: "Open `<url>`, connect your wallet, approve." Don't auto-open. Multi-tx → say it's N approvals.
184
+ 4. Let the background command finish — it signs, confirms and re-indexes. Don't kill it.
185
+
186
+ **⚠ Wallet-lane ops run ONE AT A TIME** — never start the next `--sign` op until the previous background
187
+ command has finished.
188
+
189
+ - **The sign page is operation-aware** — decoded intent, gated on network + signing wallet + the tx; the
190
+ CLI refuses a mismatched signer.
191
+ - **Multi-tx signs in ONE session** (hot + wallet only): the human connects once and walks the sequence.
192
+ - **Keep tx count low and say it up front** — every preview prints `approvals N wallet approval(s)`, the
193
+ exact signature count.
130
194
 
131
195
  ## Quick start
132
196
 
@@ -139,12 +203,12 @@ Every write builds an unsigned tx; pick the lane by stakes:
139
203
 
140
204
  On-chain's edge past small is self-resolution/permanence, **never cost** — don't call it "cheaper" above the threshold.
141
205
 
142
- **The real ceiling is READ gas, not storage cost — and 40 KB is the number, not 256 KB.** `tokenURI` reassembles the whole document per call at a measured **~360–405k gas per KB of on-chain content, climbing with size** (quote the range, never one flat rate; `inline` and `reader` are within ~1% up to 75 KB, so `--compress fastlz` makes it cheaper to *write*, not to *read*). So: **≲ 40 KB/token reads fine (~15M gas) · 40–100 KB the CLI warns** (needs a high-gas RPC; past ~90 KB no contract can read it in a tx) **· > 100 KB (~40M) the CLI refuses** — a deliberate margin below geth's 50M `eth_call` default (which runs out ~120 KB) because hosted providers cap lower, so marketplaces and indexers see a **revert**, not the work. The override is `--allow-unreadable-onchain`; treat it as a deliberate creator choice ("almost nothing will display your work"), get an explicit yes, and never use it just to clear a warning the usual answer is `--backend arweave` instead (same no-server promise, no read problem). **The gate is per token**, so a 300-piece collection of 5 KB SVGs is fine. Depth + the measured table → [decisions.md → The READ ceiling](reference/decisions.md#the-read-ceiling--the-number-that-actually-decides-can-this-go-on-chain).
206
+ **The real ceiling is READ gas, not storage cost — and 40 KB is the number, not 256 KB.** `tokenURI` reassembles the whole document per call at **~360–405k gas per KB** (quote the range, never one flat rate). So **per token**: **≲40 KB reads fine** (~15M gas) · **40–100 KB warns** (needs a high-gas RPC) · **>100 KB is refused** — a margin below geth's 50M `eth_call` default, because hosted providers cap lower and marketplaces would see a **revert**, not the work. `--compress fastlz` makes it cheaper to *write*, not to *read*. The override `--allow-unreadable-onchain` is a deliberate creator choice ("almost nothing will display your work") get an explicit yes, never use it to clear a warning; the usual answer is `--backend arweave` instead. Being per token, a 300-piece collection of 5 KB SVGs is fine. Measured table → [decisions.md → The READ ceiling](reference/decisions.md#the-read-ceiling--the-number-that-actually-decides-can-this-go-on-chain).
143
207
 
144
208
  **When the user names off-chain custody for tiny content ("deploy it as an IPFS NFT"), lead with the on-chain recommendation in your *first* reply** — don't bury it, and don't collect resolver-URL details for a path you're about to advise against. "For a 2.4 KB SVG I'd go fully on-chain — no server, renders forever, cheaper. Want that, or IPFS?" Then let them choose (you surface the better default; you don't override the request). Defaulting to "IPFS" for tiny content is what lands it at a broken localhost URI.
145
209
 
146
210
  Tiny-content path:
147
- 1. **Confirm identity** (name, symbol, `--description`, royalty, owner) via the [readout](#confirm-before-sending).
211
+ 1. **Confirm identity** (name, symbol, `--description`, royalty, **royalty cap** (permanent, reduce-only), **burnable** (permanent), owner) via the [readout](#confirm-before-sending).
148
212
  2. **Deploy + mint in one go:**
149
213
  ```bash
150
214
  abx deploy --image art.svg --name "…" --symbol … --description "…" --onchain-image --compress fastlz [--sign --for 0x…]
@@ -165,30 +229,56 @@ abx deploy-series --dir <media-dir> --name "…" --symbol … [--onchain-uri --b
165
229
  ```bash
166
230
  abx deploy-series --dir ./photos --name "My Series" --symbol MS --onchain-uri --backend arweave --mint-all --sign
167
231
  ```
168
- Files natural-sort into **tokens `0…N-1`** (`--count N` uses the first N); a token's metadata is its token id. `--dry-run` and the [readout](#confirm-before-sending) work identically (token count + mint plan). **Content placement is per token — the same custody call as a 1/1 (see [Decisions](#decisions-real-launch)).** The one Series-specific win: a **same-extension folder** uploads as ONE directory/manifest → a single collection `url-template` (O(1) on-chain, any size); mixed extensions fall back to per-token `url` fields (still no server). `cloud` needs `--public-base`. **Per-token traits: `--attributes <file.json>`** — a JSON **array** indexed by token id, or an **object** keyed by filename / token id (each value an attributes array or a `{name:value}` map). **Lane-aware, exactly like the 1/1's traits**: off-chain operator metadata by default (resolver-served, editable later via `abx add <addr> --attributes`), inlined **on-chain** when the token resolves on-chain (`--onchain-uri`) or you pass `--traits-onchain` small/medium collections; a huge series sets on-chain traits post-deploy via `set-field` under a gas budget. *(For **generative** traits computed from a seed, that's a code project — `abx.traits()` / `--attributes-renderer` — not a static Series.)*
232
+ Files natural-sort into **tokens `0…N-1`** (`--count N` uses the first N); a token's metadata is its token id. **Content placement is per token — the same custody call as a 1/1** (see [Decisions](#decisions-real-launch)). The one Series-specific win: a **same-extension folder** uploads as ONE directory → a single collection-scope image field (O(1) on-chain, any size); mixed extensions fall back to per-token fields (still no server). **Per-token traits: `--attributes <file.json>`**, lane-aware exactly like a 1/1's off-chain by default, inlined on-chain under `--onchain-uri`/`--traits-onchain`. Accepted shapes, and the huge-series `set-field` route [operating.md → Series-only owner ops](reference/operating.md#series-only-owner-ops). *(Traits computed from a seed are a code project, not a static Series.)*
169
233
 
170
234
  ## Code projects (generative / code-based drops)
171
235
 
172
- A **program is the content** (`abx deploy-code` → a `SeriesCode`): output is a function of live on-chain state (`tokenData`: coordinates + `seed` + PostParams), injected at view time. Everything from a [Series](#series-multi-token-drops) applies (mint order, lanes, identity, supply cap, minter, pause). This section is the **decision tree**; the operating depth — what to keep running, the resume loop, verify steps, render ops, lane internals, the arweave delay, selling — lives in **[reference/code-projects.md](reference/code-projects.md)**.
173
-
174
- **The mint `seed` is pseudorandom, NOT lottery-grade — say so before a creator prices scarcity off it.** Every mint draws `seed` from the canonical `AbxSeedSource` (block values + token id; `--no-seed` opts out). It replays from the block afterwards (what makes the output verifiable) but is **not secret beforehand**: a contract minting in the same tx can compute the seed it would get and revert unless it likes it. Fine when the seed diversifies the output and the distribution is the product; **not** fine for a raffle or any drop where one rare outcome is worth materially more than mint price that needs the creator's own `IAbxSeedSource` (commit-reveal/VRF), a first-class swap at both ends (`deploy-code --seed-source 0x…`, `abx set-seed-source`; both probe the address and refuse a bad one). **Who chooses differs by lane:** a 721 Series mint takes the next id, nothing to pick; on an edition (`--copies`) the buyer names the `id` and so picks the work **and** its seed (an `EditionCode` id's seed is drawn at that id's FIRST mint) — buying a minted id chooses among settled, public works (that's the product), buying an unminted id draws its seed with the buyer's chosen id in the preimage. Say "choose" there, not "decline"; a creator who doesn't want it mints the ids themselves, then sells on secondary. **Chosen seeds (not "re-rolls"):** a `seed` schema lets an authorized party *set the value they choose*, declared **before the first mint** and public after (`paramSchema("seed")`). Depth → [reference/code-projects.md → Seeds](reference/code-projects.md#seeds--pseudorandom-not-lottery-grade).
175
-
176
- **Infra fork FIRST (before any lane talk): a code project's thumbnail is *rendered* off-chain, so it ALWAYS needs a PUBLIC home you provide — there is NO zero-infrastructure code drop, and "fully on-chain" does NOT mean "nothing to run."** Settle the shape with the creator up front:
177
- - **Off-chain resolver** (`--public-base-url` + rendering, ~a few $/mo self-hosted) — **the default for a drop you'll sell.** Auto-renders every mint + param change, serves traits with no Solidity, and stays **maneuverable** (metadata/serving evolve with no on-chain surgery) while marketplaces fetch a **small** `tokenURI`. A **managed provider whose descriptor says `render.attached`** covers both halves with one API key — no effects runner to stand up ([hosting.md → Managed providers](reference/hosting.md#managed-providers--a-resolver-someone-else-runs---remote-name)).
178
- - **Fully on-chain** (`--onchain-uri --image-base <a bucket you own>`) maximal durability, no always-on service. Trade-offs: the whole document rides each `tokenURI` at **~360–405k gas per KB and rising with size**, so a p5-dependency drop (~200KB doc, ~90M+ gas) is past what a default node serves and many marketplace/indexer reads will simply fail expected, not broken, and clients past a cap assemble from the generator's piecewise getters. Also: **manual** stills (`abx render`), on-chain traits need a deployed renderer, later changes are on-chain re-points. `deploy-code --dry-run` prints the gas figure relay it before the creator commits. *(The one zero-infra-AND-on-chain exception: the in-chain **Solidity** lane below.)*
179
-
180
- **Writing the program yourself (the creator brought an *idea*, not a file)? There's ONE runtime contract — get it right or the drop is silently broken** (seed never injects → every token identical; traits empty). The program reads state via **`abx.tokenData`** (a flat object: `.seed`, and each `--schema` key flat, e.g. `.palette`) and reports traits via **`abx.traits({…})`** — never an invented global (`window.tokenData`, `window.tokenTraits`) and never "defensively across variants." `abx.traits()` is the ONLY thing captured into `attributes`, on the resolver lane too. Verify with `abx inspect` (its **PostParams** + **Traits** lines reflect what the program actually reads/reports — if they're empty but you intended a param/traits, you read it the wrong way), THEN pick a lane. Full contract: [reference/code-projects.md → Authoring the program](reference/code-projects.md#authoring-the-program--the-abxjs-runtime-contract-get-this-right-first).
181
-
182
- **Run `abx inspect <script>` before proposing any lane, then adopt the recommended lane it prints — don't hand-assemble a different flag set.** (Exception: the in-chain **Solidity-render** lane below has no JS script to inspect — go straight to it. `abx inspect` only analyzes a JS file.) A code project has surfaces that each must land *somewhere public* the fatal mistake (seen in real sessions) is picking *“fully on-chain, no server!”* and only discovering, one at a time after deploy, that it carries no thumbnail, no traits, and dropped a PostParam. **These are all DEPLOY-TIME decisions — an on-chain field with no pointer CANNOT be backfilled** (least of all to localhost). `abx inspect` and `deploy-code --dry-run` print a **Surfaces block** grading each one; **every ⚠ there is a marketplace-facing hole you must close before deploy — trust it over your own read.** Resolve all of them into ONE coherent lane with the creator BEFORE you collect identity or show a config:
236
+ A **program is the content** (`abx deploy-code`): output is a function of live on-chain state
237
+ (`tokenData`: coordinates + `seed` + PostParams), injected at view time. Everything from a
238
+ [Series](#series-multi-token-drops) applies. This section is the **decision tree**; operating depth
239
+ what to keep running, the resume loop, verify steps, render ops, lane internals, selling — lives in
240
+ **[reference/code-projects.md](reference/code-projects.md)**.
241
+
242
+ **The mint `seed` is pseudorandom, NOT lottery-grade say so before a creator prices scarcity off it.** Every mint draws it from the canonical `AbxSeedSource` (block values + token id). It replays afterwards (that is what makes the output verifiable) but is **not secret beforehand**: a contract minting in the same tx can compute the seed it would get and revert unless it likes it. Fine when the seed diversifies the work and the distribution *is* the product; **not** fine for a raffle or any drop where one rare outcome is worth materially more than mint price — that needs the creator's own `IAbxSeedSource` (commit-reveal/VRF), a first-class swap at both ends (`deploy-code --seed-source 0x…`, `abx set-seed-source`). Who picks differs by lane, and a `seed` schema lets an authorized party choose the value outright [code-projects.md → Seeds](reference/code-projects.md#seeds--pseudorandom-not-lottery-grade).
243
+
244
+ **Infra fork FIRST, before any lane talk: a code project's thumbnail is *rendered* off-chain, so it
245
+ ALWAYS needs a PUBLIC home you provide.** There is NO zero-infrastructure code drop, and "fully
246
+ on-chain" does NOT mean "nothing to run". Two shapesan **off-chain resolver** (`--public-base-url`,
247
+ the default for a drop you'll sell: auto-renders every mint and param change, stays maneuverable; a
248
+ managed provider with `render.attached` covers both halves with one key), or **fully on-chain**
249
+ (`--onchain-uri --image-base <a bucket you own>`: maximal durability, no always-on service, but stills
250
+ are manual, on-chain traits need a deployed renderer, and the whole document rides each `tokenURI` at
251
+ ~360–405k gas/KB — `deploy-code --dry-run` prints the figure, relay it). Settle this with the creator
252
+ first. *(The one zero-infra-AND-on-chain exception is the in-chain Solidity lane below.)*
253
+
254
+ **Writing the program yourself? There is ONE runtime contract — get it right or the drop is silently
255
+ broken** (seed never injects → every token identical; traits empty). The program reads state via
256
+ **`abx.tokenData`** (flat: `.seed`, and each `--schema` key) and reports traits via **`abx.traits({…})`**
257
+ — never an invented global, never "defensively across variants". `abx.traits()` is the ONLY thing
258
+ captured into `attributes`, on the resolver lane too. Verify with `abx inspect` before picking a lane:
259
+ its PostParams and Traits lines reflect what the program *actually* reads and reports. Full contract →
260
+ [code-projects.md → Authoring the program](reference/code-projects.md#authoring-the-program--the-abxjs-runtime-contract-get-this-right-first).
261
+
262
+ **Run `abx inspect <script>` before proposing any lane, and adopt the lane it recommends.** A code
263
+ project has surfaces that must each land somewhere public, and the fatal mistake is picking "fully
264
+ on-chain, no server!" then discovering one at a time, after deploy, that it carries no thumbnail, no
265
+ traits, and dropped a PostParam. **Settle these BEFORE you deploy.** They are not impossible to
266
+ change afterwards — the honest cost is an owner-signed **re-point tx** (`set-field image <public url>`)
267
+ plus a re-render, on a collection that has been showing a placeholder in the meantime. `abx inspect` and `deploy-code --dry-run` print a **Surfaces block** grading
268
+ each one; **every ⚠ there is a marketplace-facing hole to close before deploy — trust it over your own
269
+ read.** Resolve them into ONE coherent lane before collecting identity or showing a config:
183
270
 
184
271
  | Surface | On-chain | Off-chain |
185
272
  |---|---|---|
186
273
  | **tokenURI + animation** | `--onchain-uri` — *if* script + every dep fit one `tokenURI` eth_call (`abx inspect` estimates) | a resolver (`--public-base-url`) |
187
- | **thumbnail (`image`)** | ❌ never computed on-chain — needs a public destination: `--image-base <S3/R2/CDN you own>`. **No destination ⇒ placeholder FOREVER + orphaned render, not backfillable. Never localhost.** | a resolver's `/image` |
274
+ | **thumbnail (`image`)** | ❌ never computed on-chain — needs a public destination: `--image-base <S3/R2/CDN you own>`. **No destination ⇒ placeholder + an orphaned render until you re-point (`set-field image` + re-render, owner-signed). Never localhost.** | a resolver's `/image` |
188
275
  | **traits (`attributes`)** | a Solidity `--attributes-renderer 0x…` you **DEPLOY** (fork `SeedTraitsRenderer`) — **not a free flag or guessable address** (`deploy-code` refuses a codeless one; "ports to Solidity" ≠ "deployed") | a resolver stitches the JS `abx.traits()` |
189
276
  | **PostParams** | declare EVERY key the script reads: `--schema key:Type:Auth` (a palette collectors set = `palette:HexColor:TokenOwner`) — else silently dropped at render | — |
190
277
 
191
- **The resolver is the pivot: choosing *no resolver* forecloses the off-chain column for thumbnail + traits.** (On `--onchain-uri`, an unauthenticated Etherscan "Read Contract" can hit its gas cap on the big doc and look like a revert — expected; verify with `abx tokenuri`, never `abx index --full` to "fix" it. Diagnosis depth → [reference/troubleshooting.md](reference/troubleshooting.md).)
278
+ **The resolver is the pivot: choosing *no resolver* forecloses the off-chain column for thumbnail and
279
+ traits.** (On `--onchain-uri`, an unauthenticated Etherscan "Read Contract" can hit its gas cap on a big
280
+ document and look like a revert — expected; verify with `abx tokenuri`, never `abx index --full` to
281
+ "fix" it. → [troubleshooting.md](reference/troubleshooting.md).)
192
282
 
193
283
  **Pick your code lane** (all `abx deploy-code`):
194
284
 
@@ -201,30 +291,40 @@ A **program is the content** (`abx deploy-code` → a `SeriesCode`): output is a
201
291
  | A **Solidity renderer** (SVG on-chain, from seed+params) | `--image-renderer 0x<svg> [--attributes-renderer 0x<traits>] --onchain-uri` (no `--script`) | **truly in-chain** — image + traits computed on-chain, tokenURI on-chain, **zero infra**; no `animation_url` (no program) |
202
292
  | **A JS program AND on-chain thumbnail/traits** — the both-worlds shape | `--script f.js --image-renderer 0x<svg> --attributes-renderer 0x<traits> --onchain-uri` | **every surface in-chain, zero infra** — `animation_url` assembles from the script chunks, `image`+`attributes` computed by Solidity. **Nothing to render or host.** |
203
293
 
204
- **`--script` and `--image-renderer` are NOT an either-or.** Passing both is the strongest shape available and the most-missed one: the program keeps driving `animation_url` on-chain while the Solidity renderers supply `image` and `attributes`, so an interactive generative piece gets a real marketplace thumbnail and real traits with **no resolver, no runner, no bucket**. Reach for it whenever the creator can supply (or fork) a Solidity renderer. The dry run's `Surfaces` block confirms each surface before gas is spent.
294
+ **`--script` and `--image-renderer` are NOT an either-or** passing both is the strongest shape
295
+ available and the most-missed: the program drives `animation_url` on-chain while Solidity renderers
296
+ supply `image` and `attributes`, so an interactive generative piece gets a real thumbnail and real
297
+ traits with **no resolver, no runner, no bucket**.
205
298
 
206
- **⭐ In-chain Solidity lane — recommend on-chain `tokenURI` without hesitation.** The work is a Solidity `IAbxFieldRenderer` returning an SVG from seed+params (not a JS program), wired `--image-renderer 0x… [--attributes-renderer 0x…] --onchain-uri` with **no `--script`/`--code-dir`**: image AND traits computed on-chain — **genuinely nothing to run** (a small SVG, so the large-`tokenURI` caveat doesn't apply). **NOT no-code, though**: the renderer is a contract the creator compiles + deploys with Foundry (`abx scaffold-renderer <dir>` writes a ready-to-build project; `abx` never compiles or deploys Solidity). Not comfortable with Solidity? Steer to the resolver or (tiny static SVG) `--onchain-image`. Depth → [reference/code-projects.md](reference/code-projects.md#in-chain-solidity-svg--the-zero-dependency-lane) · interface/invariants → https://abx.docs.artblocks.io/protocol/renderers/.
299
+ **⭐ The in-chain Solidity lane — recommend on-chain `tokenURI` without hesitation.** The work is a
300
+ Solidity `IAbxFieldRenderer` returning an SVG from seed+params, wired with no `--script`: image and
301
+ traits computed on-chain, **genuinely nothing to run**. **Not no-code, though** — the renderer is a
302
+ contract the creator compiles and deploys with Foundry (`abx scaffold-renderer <dir>` writes a
303
+ ready-to-build project; `abx` never compiles or deploys Solidity). Not comfortable with Solidity? Steer
304
+ to the resolver, or `--onchain-image` for a tiny static SVG. Depth →
305
+ [code-projects.md](reference/code-projects.md#in-chain-solidity-svg--the-zero-dependency-lane).
207
306
 
208
307
  ## Decisions (real launch)
209
308
 
210
- Master call is **custody × mutability**:
309
+ **Custody is the master call** size decides it ([Quick start](#quick-start) has the rule and the READ
310
+ ceiling; do not restate the numbers, they live there). Four patterns, by where bytes live × how
311
+ `tokenURI` resolves — name the one you're proposing, then configure it from
312
+ [decisions.md](reference/decisions.md):
211
313
 
212
- | | **Mutable** (name/desc/traits may change) | **Immutable** (never changes) |
213
- |---|---|---|
214
- | **Small static** (≲ 40 KB/file — the READ ceiling) | **on-chain renderer** — `--onchain-image --compress fastlz`. No host, mutable via `set-field`, permanent. | on-chain renderer + `lock-field` + `lock-uri` once it resolves. |
215
- | **Bigger / dynamic** (most PNG/JPEG) | **image off-chain, JSON on-chain, no server** — `--onchain-uri --backend arweave` (or `ipfs`). For metadata you edit often, a **resolver** instead. | image off-chain (Arweave = permanent) + on-chain renderer + `lock-field`/`lock-uri`. |
314
+ 1. **Fully on-chain** (`--onchain-image`) bytes *and* JSON on-chain. Small content only.
315
+ 2. **Image off-chain, JSON on-chain, no server** (`--onchain-uri --backend arweave|ipfs|cloud`) — the
316
+ sweet spot for static content.
317
+ 3. **Remote resolver** (`--public-base-url` + a node) mutable/dynamic metadata; self-hosted or managed.
318
+ 4. **Inline SVG on-chain** — self-contained vector. 1/1 → `--onchain-uri`; a Series of SVGs →
319
+ `--onchain-image --compress fastlz`.
216
320
 
217
- **Four patterns, by where bytes live × how `tokenURI` resolves** pick one, then read
218
- [decisions.md](reference/decisions.md) for how to configure it:
219
- 1. **Fully on-chain** (`--onchain-image`) — bytes + JSON on-chain. Small content only (≲ 40 KB/token to read).
220
- 2. **Image off-chain, JSON on-chain, no server** (`--onchain-uri --backend arweave|ipfs|cloud`) — the sweet spot for static content.
221
- 3. **Remote resolver** (`--public-base-url` + a node) — mutable/dynamic metadata; self-hosted or a managed provider.
222
- 4. **Inline SVG on-chain** — self-contained vector content. 1/1 → `--onchain-uri`; a Series of SVGs → `--onchain-image --compress fastlz`.
321
+ The second axis is **mutability**: any pattern stays editable until you freeze it, so ship unlocked,
322
+ confirm it resolves, then lock (see *Store lock* below).
223
323
 
224
- **IPFS/Arweave is NOT a server.** Pattern 2 commits the bare CID/txid on-chain and the renderer joins
225
- it to the collection's gateway prefix at read time — nothing to keep running, and the gateway is a
226
- one-tx repoint (`abx set-gateway`) rather than a value welded into the field. Only pattern 3 needs a
227
- resolver. Never tell a creator IPFS is blocked on "a public URL" or "a server always online".
324
+ **IPFS/Arweave is NOT a server.** Pattern 2 commits the bare CID/txid on-chain and the renderer joins it
325
+ to the collection's gateway prefix at read time — nothing to keep running, and the gateway is a one-tx
326
+ repoint (`abx set-gateway`), not a value welded into the field. Only pattern 3 needs a resolver. Never
327
+ tell a creator IPFS is blocked on "a public URL" or "a server always online".
228
328
 
229
329
  **The six decisions** — storage permanence · public host URL · identity · image placement · on-chain
230
330
  vs off-chain resolution · when to mint. Full detail, tradeoffs and failure modes:
@@ -233,14 +333,13 @@ vs off-chain resolution · when to mint. Full detail, tradeoffs and failure mode
233
333
  - **Never bake localhost** into an off-chain deploy — that token resolves for no one. The CLI refuses it; don't try to talk it round.
234
334
  - **Propose a real name/symbol and get an explicit yes** — on-chain identity is effectively permanent, and a generic folder name infers junk. The CLI refuses a real send that would bake its own placeholder (a `--dry-run` only warns — that is not permission).
235
335
  - **Store ≠ lock; lock last.** Deploy unlocked, confirm it resolves in production, *then* freeze (`lock-field` / `lock-uri`; a code drop also needs `lock-script` + `lock-dependencies`). A deliberate follow-up, never the first deploy.
236
- - **Say "locked metadata", never "immutable work".** Locks freeze what the contract *stores*, and the metadata locks don't reach every input. **An *ungoverned* PostParam has no lock at all** the owner's raw setter keeps writing it past every metadata lock, and the generator keeps injecting the new value as `tokenData`. (A *schema'd* param CAN be welded permanently: `set-schema … :lock=now` / `abx retire-param` freezes its **token-scope value** and its **schema** forever — see [operating.md](reference/operating.md). So "no lock" is about the ungoverned store, never about a governed key.) **One documented exception to that weld: a contract-scope DEFAULT can still be deleted.** `clearContractParam` deliberately sits outside the schema guard — it is the recovery path out of a value poisoned before the schema existed — so an owner can set a collection-wide default, attach and expire a schema on that key, sell tokens that inherit the default, and *then* clear it; every token with no override of its own changes. Clearing can only *remove* a fallback, never forge a value or bypass an auth rule, and token-scope values already written are untouched. If a creator is promising a frozen collection-wide value, the honest move is to write it **per token** (governed, welded) rather than leave it as an inherited default. A **`Registry` dependency's bytes are re-fetched from the registry on every read** — `lock-dependencies` pins which library a ref means, not what it returns. `abx verify`'s `chain-complete` is a claim about *where* bytes come from, not that they're frozen. A token that live-adapts to chain state is a legitimate thing to build; just don't sell a frozen one as frozen unless it is. **The one lock a BUYER asks about is `lock-param-hooks`** a code project's `--transfer` hook is a *veto* (its revert fails a transfer, and mints too), so until the hooks are frozen the owner holds a standing power over whether a collector can sell; freezing an empty set is how a creator proves they never will. **Every one of these locks freezes a POINTER, not behavior:** a locked hook, renderer or reader is still a *contract*, and a contract can be a proxy whose code is upgraded later — `paramHooksLocked()` would keep reading `true` while a re-pointed proxy starts reverting every transfer. The protocol doesn't try to detect proxies on chain (complex, incomplete); it defines locks as pointer locks and discloses it. **So: for any permanence claim, the hook/renderer must itself be an immutable deployment** — say "these exact three addresses can never change" and check what's behind them, never "the behavior is frozen". Depth → [decisions.md](reference/decisions.md) · [code-projects.md hooks](reference/code-projects.md#live-data-the-augment-hook--the-hook-is-the-setting) · https://abx.docs.artblocks.io/protocol/owner-powers/
336
+ - **Say "locked metadata", never "immutable work".** Locks freeze what the contract *stores* — and every one freezes a **pointer or a value, never behavior** (a locked renderer can still be a proxy that is upgraded later). An **ungoverned PostParam has no lock at all**; a schema'd one can be welded. The lock a **buyer** actually asks about is `lock-param-hooks`, because a `--transfer` hook is a **veto** over whether they can ever sell. A live-adapting token is a fine thing to build just never sell a frozen one as frozen unless it is. Rules, exceptions, and what to check before any permanence claim → [decisions.md → Locks](reference/decisions.md#locks--what-actually-freezes-and-what-a-buyer-is-really-asking).
237
337
  - **Tunnels (ngrok/cloudflared) are preview-only** — never bake one on-chain.
238
338
 
239
339
  ## Confirm before sending
240
340
 
241
- Run `abx deploy --dry-run` for real values (pin the salt it prints — [below](#deploy-strategy--when-to-mint) — with a second `--dry-run --salt <it>` to see the real address), present **this exact shape** — one row per on-chain value — then wait for go-ahead. **Mirror the dry-run's values; don't compose your own.**
242
-
243
- **No signing key in `.env` yet? `--dry-run` still needs a deployer address — pass `--for 0x<the creator's wallet>`.** The address is a pure function of (factory, salt, deployer), so a preview can't compute it from nothing; it signs nothing, so no key is involved. Ask the creator for their wallet address once, up front — it's also what the real wallet-lane deploy takes (`--sign --for 0x…`).
341
+ `abx deploy --dry-run` for real values (pin the salt it prints with a second `--dry-run --salt …`).
342
+ **No key in `.env`? `--dry-run` still needs a deployer — pass `--for 0x<the creator's wallet>`.**
244
343
 
245
344
  ```
246
345
  Deploy config — confirm before I send (everything below is written on-chain):
@@ -262,17 +361,23 @@ Deploy config — confirm before I send (everything below is written on-chain):
262
361
  ```
263
362
 
264
363
  Rules:
265
- - **The readout is a contract: what's shown is *exactly* what deploys.** Every value = a flag you pass. Not going on-chain → the row says `none`, never an invented placeholder.
266
- - **Every line must be *verified*, never aspirational — a value that depends on an external contract that must already exist** (`--attributes-renderer`, `--minter`, a `0x…` `--dep`, a gateway) **may appear as committed only once the dry-run confirms it resolves.** Never bake in a guessed/placeholder address; if it's unverified, show it `⚠ requires <X> — not yet deployed/verified` or leave the surface out. (The real-session trap: presenting "Traits computed on-chain via `--attributes-renderer 0x…`" off a *guessed* address — `deploy-code` now refuses an address with no code, so trust the dry-run over the assumption.)
267
- - **Flag every inferred/defaulted value** with `⚠` + where it came from (name/symbol from filename, royalty default, owner = signing key).
268
- - **The description is the one people forget** — never deploy an auto-written or empty description quietly. State what's written and whether it's on-chain (`--description-onchain`) or off. For a real piece, ask for the creator's words.
269
- - **Traits are the creator's** (the OpenSea `attributes` array) — ask (`--traits "Background=Blue"` or `--attributes file.json`); never invent traits, never put protocol facts there. Off-chain by default; `--traits-onchain` to commit them.
270
- - **Show real values** the real name, description, address + pinned salt, chunk/tx count from the dry-run.
271
- - Adapt rows to the config (off-chain shows storage backend + host URL instead of the renderer; `--no-mint` shows a deferred mint) — but always one row per written value, always the ⚠ flags, always an explicit confirm.
364
+ - **The readout is a contract: what's shown is *exactly* what deploys.** Every value is a flag you pass;
365
+ anything not going on-chain says so.
366
+ - **Every line is verified, never aspirational.** A value depending on a contract that must already exist
367
+ (`--attributes-renderer`, a minter) is only claimed once checked.
368
+ - **Flag every inferred or defaulted value** with `⚠` and where it came from.
369
+ - **The description is the one people forget** never deploy an auto-written or empty one quietly.
370
+ - **Traits are the creator's** ask; never invent them.
371
+ - **Show real values**: real name, description, address + pinned salt, chunk/tx counts from the dry run.
372
+ - Adapt rows to the config (off-chain shows backend + host URL instead of the renderer; `--no-mint` shows
373
+ a deferred mint) — but always show what is written on-chain.
272
374
 
273
375
  ## Deploy strategy — when to mint
274
376
 
275
- The deploy address is **deterministic** — a pure function of `(factory, salt)`, knowable before signing (`abx predict`). So you can warm the resolver at that exact address first → the moment a marketplace sees the mint it fetches live metadata, not a cached blank. Minting is optional at deploy (`--no-mint`); there's a one-shot `abx mint`.
377
+ The deploy address is **deterministic** — a pure function of `(factory, salt)`, knowable before signing
378
+ (`abx predict`). So you can warm a resolver at that exact address first, and the moment a marketplace
379
+ sees the mint it fetches live metadata rather than a cached blank. Minting at deploy is optional
380
+ (`--no-mint`); `abx mint` is the one-shot.
276
381
 
277
382
  | Path | When | Flow |
278
383
  |---|---|---|
@@ -280,115 +385,110 @@ The deploy address is **deterministic** — a pure function of `(factory, salt)`
280
385
  | **Careful** *(real launch, off-chain resolver)* | metadata live the instant it's listable | `deploy --no-mint` → `serve` (warm) → verify → `mint` → `refresh` |
281
386
  | **Primary sale** | token issued at point of sale | `deploy --no-mint` → settle off-chain → `mint --to <buyer>` → `refresh` |
282
387
 
283
- - **Fully on-chain ⇒ mint at deploy; do NOT add `--no-mint`.** The careful path warms an *off-chain resolver*; a fully on-chain token has none (the renderer resolves the instant the contract exists). Deferring buys nothing and adds a tx. Defer an on-chain mint only for a genuine primary sale.
284
- - **`abx predict [--salt 0x..] [--for 0x..]`** pre-computes the address. No `--salt` reserves a fresh, front-run-proof salt to the deployer; pass `--salt` for a vanity/known address. The salt's leading 20 bytes are an access guard: zero ⇒ anyone may deploy; non-zero ⇒ only that signer.
285
- - **`--dry-run` without `--salt` prints NO address** — enforced, not a trap to remember: without a pinned salt there's nothing real to quote, so it prints only the freshly-reserved salt plus a ready-to-paste `--salt …` re-run (or `abx predict --salt … --for …`). Pin that salt and re-run to see the real, reproducible address before you present anything.
286
- - **`abx refresh <addr>`** asks marketplaces to re-index. **Both lanes emit ERC-4906** on metadata/URI changes so 4906-aware marketplaces self-refresh, and `refresh` is the fallback (+ genesis mint). **An edition additionally has ERC-1155's native `URI` event, which is NOT emitted on a contract-wide re-point** (no range form exists) so after `set-token-uri`/`set-renderer` on an edition also run `abx ping-uri <addr> --token-ids <ids>` for consumers that honor only that event. With `OPENSEA_API_KEY` it calls OpenSea directly; else it prints the links to click.
287
- - **There is no "add it to a marketplace" step — don't offer one.** Marketplaces discover the collection from chain (the mint, plus ERC-4906 on either lane); `abx refresh` — and `abx ping-uri` on an edition — is the only nudge. Suggesting a manual listing implies work that doesn't exist. **The ABX App Store is different** — that registry is an explicit opt-in (`abx submit-app`), not marketplace discovery. Offer it after deploy; never fold it into deploy. See [List in the App Store](#list-in-the-abx-app-store-optional).
288
- - A `--no-mint` deploy reconstructs + serves normally (dashboard shows #0 "not yet minted"; metadata/image already resolve).
289
- - **Etherscan source is already verified** — your deploys are EIP-1167 clones Etherscan auto-recognizes as proxies of the verified implementation.
290
-
291
- ## List in the ABX App Store (optional)
292
-
293
- **Not part of deploy.** After the collection is live, ask: *want this listed in the ABX App Store?* If no, stop. If yes, this is a second, explicit opt-in — minting a token in the store registry — not marketplace discovery.
388
+ - **Fully on-chain ⇒ mint at deploy; do NOT add `--no-mint`.** The careful path warms an *off-chain
389
+ resolver*; a fully on-chain token has none the renderer resolves the instant the contract exists, so
390
+ deferring buys nothing and costs a tx. Defer an on-chain mint only for a genuine primary sale.
391
+ - **There is no "add it to a marketplace" stepdon't offer one.** Marketplaces discover the collection
392
+ from chain (the mint, plus ERC-4906 on either lane); `abx refresh` — and `abx ping-uri` on an edition —
393
+ is the only nudge. The App Store is a separate opt-in, never folded into deploy.
294
394
 
295
- 1. **Draft store copy with the creator.** `--name` / `--summary` / `--description` here are what someone can *do*, not the collection's NFT metadata. Do not silently copy deploy `--name` / `--description`. Confirm category, stage, launch URL, and icon mark/tone.
296
- 2. **You run the command** (same signing lane as the deploy). `--dry-run` first, then send:
297
-
298
- ```bash
299
- abx submit-app <collection> \
300
- --name "…" --summary "…" --description "…" \
301
- --category Create --stage Prototype \
302
- --url https://… \
303
- --sign
304
- ```
395
+ `abx predict` and salt guards, the `--dry-run`-without-`--salt` rule, ERC-4906 vs the edition's native
396
+ `URI` event, and why Etherscan already shows verified source
397
+ [operating.md → Deploy strategy](reference/operating.md#deploy-strategy--the-mechanics).
305
398
 
306
- 3. They sign once to mint the listing, then a few times to write metadata (the chain caps how much fits in one tx). If mint already succeeded, re-running skips the mint and only writes params.
307
- 4. Humans can instead use the store's `/submit` page; same txs. Don't hand them a list of `configure-param` commands.
399
+ ## List in the ABX App Store (optional)
308
400
 
309
- The wallet must **own the collection** at mint (the gate checks `isAbxClone` + `owner()`). After that, control follows the **entry token**.
401
+ **Not part of deploy, ever.** After the collection is live, ask once: *want this listed in the ABX App
402
+ Store?* If no, stop. If yes it is a second, explicit opt-in — minting a token in the store registry —
403
+ and **not** marketplace discovery. Draft the store copy *with* the creator (`--name`/`--summary`/
404
+ `--description` describe what someone can **do**, not the NFT's metadata — never silently reuse the
405
+ deploy's). You run `abx submit-app`, `--dry-run` first. The wallet must **own the collection** at mint.
406
+ Full command, the multi-tx write, and the humans-use-`/submit` alternative →
407
+ [operating.md → App Store](reference/operating.md#listing-in-the-abx-app-store--abx-submit-app).
310
408
 
311
409
  ## What a token carries — files beyond the image (the data plane)
312
410
 
313
- A token is **not "just a picture."** It anchors **named, typed files** ("artifacts"), and the served metadata JSON carries an **`artifacts`** list — the *complete* set of the token's files, each `{key, mimeType, uri}`. The `image` / `animation_url` are just reserved members of that same set; alongside them a token can carry a hi-res master, a certificate, source files, a README — any number of named files. (Background: [data plane](https://abx.docs.artblocks.io/protocol/data-plane/).)
314
-
315
- ```json
316
- "artifacts": [
317
- { "key": "image", "mimeType": "image/svg+xml", "uri": "…/image" },
318
- { "key": "print", "mimeType": "image/tiff", "uri": "ipfs://Qm…/master.tiff" },
319
- { "key": "readme", "mimeType": "text/markdown; charset=utf-8", "uri": "ar://…/README.md" }
320
- ]
321
- ```
322
-
323
- - **Attach a file:** `abx attach <addr> <key> <ipfs://… | ar://… | https://…>` — `<key>` is any name you choose (`print`, `certificate`, `stems`, `readme`) and becomes the manifest entry's key. The representation is **auto-detected** from the URI scheme; the `mimeType` is declared from the file **extension** (`…/master.tiff` → `image/tiff`), so point the URI at the file itself. Tiny bytes with no external host can go **on-chain** with `--file <path>`. Token scope by default; `--collection` for a collection-wide file. Any signing lane; `--dry-run` previews. It's one file per call, run **after deploy**.
324
- - **Don't have a URL yet? Upload first.** `attach` takes a locator you already host. `abx storage upload <path> --backend arweave|ipfs` uploads one file and prints a locator that **keeps the filename** (so the declared type survives) plus the ready-to-run `attach` line (Arweave = pay-once permanent; the same backends `deploy` uses; `--dry-run` to preview without uploading). Full flow: `abx storage upload master.tiff --backend arweave` → copy the printed locator → `abx attach <addr> print <that-locator>`.
325
- - **`artifacts` is COMPUTED, never a field you set.** The resolver/renderer assembles the list from your fields — setting a field literally named `artifacts` is refused. You attach one file per key; the manifest builds itself.
326
- - **The complete listing is a resolver surface.** A resolver serves the full `artifacts` list at `/t/<chainId>/<addr>/<id>`, and `/data/<key>` fetches each file. The bare on-chain `tokenURI` enumerates **reserved fields only** (the EVM can't enumerate arbitrary FIELD keys) — so a project that must surface extra files to consumers today runs a resolver (attached files are still stored on-chain + keccak-anchored regardless).
327
- - **PostParams are the exception — they need no resolver, and no metadata JSON either.** The params store enumerates its own keys on-chain (`paramSchemaKeys` · `contractParamKeys` · `tokenParamKeys`, then `tokenParam`/`contractParam` for each value), so **any** RPC reads a token's whole configuration directly from the contract — that IS the canonical surface, not a projection of one. (`abx state` for the declared schemas, `abx tokens` for the set values; a code project's program also receives them as `tokenData`. They are deliberately *not* copied into `tokenURI`, which stays small — traits meant for marketplaces belong in `attributes`.) The line to give a creator: *attachments always need a resolver; params never do.*
328
- - **Effect outputs are artifacts too.** A code project's effect runner publishes `render/image`, `render/traits`, and any extra declared output (e.g. a hi-res `render/print`) into the same manifest automatically, at the current settled state — files appear as tokens are minted and params change (see [code-projects](reference/code-projects.md)).
329
- - **Not the same as a Series.** `deploy-series` makes **N separate tokens, one file each**. The data plane is how **one** token holds several named files. Depth (representations, verify, reserved keys, on-chain-vs-resolver) [operating.md](reference/operating.md#attaching-files--the-data-plane).
330
- - **Set expectations honestly (say it up front).** No mainstream marketplace (OpenSea/Blur) shows a "files" tab **today** — they render `image`/`animation_url` only. Attached files are a durable, cryptographically-anchored part of the token *now*, read by **data-plane-aware tools and any resolver**; broad marketplace display is future adoption. So a creator verifies an attach with **`abx tokenuri <addr> --fetch`** — the served document carries the `artifacts` manifest — not by refreshing OpenSea (which won't show it).
411
+ A token is **not "just a picture."** It anchors **named, typed files**, and the served JSON carries an
412
+ **`artifacts`** list — the *complete* set, each `{key, mimeType, uri}`. `image`/`animation_url` are
413
+ reserved members of that same set; alongside them a token can carry a hi-res master, a certificate,
414
+ source files, a README.
415
+
416
+ - **Attach:** `abx attach <addr> <key> <ipfs://… | ar://… | https://…>` — `<key>` is any name you pick.
417
+ Representation is auto-detected from the scheme; `mimeType` comes from the file **extension**, so
418
+ point the URI at the file itself. Tiny bytes with no host can go on-chain with `--file`. One file per
419
+ call, after deploy.
420
+ - **No URL yet?** `abx storage upload <path> --backend arweave|ipfs` prints the locator `attach` wants
421
+ (and the ready-to-run `attach` line). Hand `attach` the `ipfs://`/`ar://` form, never a gateway URL
422
+ an `https://` locator is stored as an opaque `url` with the host welded in.
423
+ - **`artifacts` is COMPUTED, never a field you set** the manifest builds itself from your fields.
424
+ - **Say the honest limit up front:** no mainstream marketplace shows a files tab **today**. Verify an
425
+ attach with `abx tokenuri <addr> --fetch`, not by refreshing OpenSea.
426
+
427
+ Resolver-vs-on-chain reach, effect outputs, reserved keys, and the params exception
428
+ [operating.md Attaching files](reference/operating.md#attaching-files--the-data-plane).
331
429
 
332
430
  ## After launch — tell the creator (durability + owner care)
333
431
 
334
- Once it's live, cover these in plain language; don't wait to be asked.
432
+ Cover these in plain language once it's live; don't wait to be asked.
335
433
 
336
- - **Durability depends on the backend.** Arweave = **pay once, kept for centuries** (a storage endowment funds ~200 years; resolves as long as the network + any gateway are up) — nothing to renew. IPFS/Pinata = **the creator must keep it pinned** — the image serves through their gateway, and if pinning lapses the bytes can disappear. `cloud`/`fs` = they maintain them. Say which one this drop uses and what it implies. For a real drop, lead toward Arweave (or start elsewhere and re-host later — bytes are keccak-anchored, so re-upload to the new backend + re-point with `set-field`, verified by `abx verify`). A slow or dead **gateway** is not this problem: that's `abx set-gateway`, one tx, no re-upload.
337
- - **Moving backend or gateway later is an owner-signed on-chain edit — but they are two different sizes.** Changing the **gateway** for ipfs/arweave content is one tx that moves every token and needs no re-upload (`abx set-gateway <addr> --ipfs <prefix>`), because the field holds the CID and the gateway is a separate collection setting — it works even on locked fields. Changing the **backend** is a real migration: re-upload, then re-point (`set-field`/`migrate`), portable because the on-chain keccak anchors the bytes. Either way it is a tx from the **owner** wallet, so that wallet must stay secure and reachable.
338
- - **Owner-wallet hygiene (suggest as follow-ons).** The owner wallet controls mint, royalties, URIs, and ownership itself. Recommend: a hardware/dedicated wallet over a throwaway hot key; for anything valuable, hand ownership to a multisig (`abx set-admin <addr> --to <safe>`); and **back up `.abx-self-host/arweave-key.json`** — it holds any prepaid Turbo credits, lose it and they're stranded.
434
+ - **Durability is the backend's, and they differ.** Arweave = pay once, kept for centuries, nothing to
435
+ renew. IPFS = **only as durable as the pin** if pinning lapses the bytes can go. `cloud`/`fs` = they
436
+ maintain it. Say which one this drop uses and what it implies.
437
+ - **Gateway ≠ backend, and the sizes differ.** A slow or dead gateway for ipfs/arweave content is **one
438
+ tx** (`abx set-gateway`), no re-upload, and it works on locked fields. Changing the **backend** is a
439
+ real migration: re-upload, then re-point. Both are owner-signed.
440
+ - **Owner-wallet hygiene** (offer as follow-ons): the owner wallet controls mint, royalties, URIs and
441
+ ownership itself — recommend a hardware/dedicated wallet over a hot key, a multisig
442
+ (`abx set-admin`) for anything valuable, and backing up `.abx-self-host/arweave-key.json` if Arweave
443
+ credits live there.
339
444
 
340
445
  ## Setup + environment
341
446
 
342
- `abx` needs **Node ≥ 22.5** at runtime (the projection store uses built-in SQLite).
447
+ `abx` needs **Node ≥ 22.5**. Resolve the binary **local first, then global** — `./node_modules/.bin/abx`
448
+ (preferred; invoke as `npx abx …`), else a global `abx`, else install **`@artblocks/abx-cli`** (never
449
+ `@artblocks/abx-sdk`, which ships no binary). **Never probe with a bare `npx abx`** — that name on npm is
450
+ an unrelated squatted package, and `--no-install` does not make it safe. Ask before installing globally;
451
+ keep one invocation for the whole session. In the abx source repo: `pnpm abx <cmd>`.
343
452
 
344
- ### Find `abx` before you install it local first, then global
345
-
346
- **Resolve in this order and use the first hit.** Don't jump to a global install; a project-local CLI is pinned in the creator's `package.json` (reproducible, and what `abx skill install` version-locks against), so it wins whenever it exists:
347
-
348
- ```bash
349
- ./node_modules/.bin/abx version # 1. project-local — PREFER this (invoke as `npx abx <cmd>` once you know it's there)
350
- abx version # 2. a global install already on PATH
351
- ```
453
+ `.env` is **secrets only** — `ABX_DEPLOYER_PK` (hot lane only; prefer `--sign` and no key at all),
454
+ `ABX_RPC_URLS`, `ABX_PUBLIC_BASE_URL`, backend secrets, and the one client credential grammar
455
+ `ABX_REMOTE_<NAME>_URL` + `ABX_REMOTE_<NAME>_TOKEN` (a node you run is the name `self`).
352
456
 
353
- - **Probe the local binary by PATH, never with `npx abx`.** The bare `abx` name on npm is an **unrelated squatted package**, so a plain `npx abx` with nothing local *downloads that*. And `--no-install` does **not** make the probe safe or trustworthy: it only skips a *fresh download*, and npm will still happily run any `abx` binary sitting in the npx cache from an earlier run — which can be a stale version (a real sweep saw `--no-install` report a months-old build as the project's CLI) or, if a plain `npx abx` was ever run on that machine, the squatted package itself. `./node_modules/.bin/abx` is unambiguous: it exists in THIS project or it doesn't.
354
- - In the **abx source repo** (contributor), neither applies: use `pnpm abx <cmd>`, which runs the live `tsx` source.
457
+ Install matrix, the npx-cache trap in full, every env var, RPC range caps and pruned log history
458
+ [reference/setup.md](reference/setup.md).
355
459
 
356
- **Nothing found install.** The package is **`@artblocks/abx-cli`** (not `@artblocks/abx-sdk` — that's the library, and installing it gets you no `abx` binary; a real session lost a cycle to exactly that mistake):
460
+ **If indexing is slow, or a hosted resolver won't serve, ASK IT: `abx status <addr> [--remote <name>]`**
461
+ reports the lifecycle (`queued`→`backfilling`→`live`, plus `stale`/`failed` with a machine-readable
462
+ cause), so "still catching up" and "broken" stop looking alike. **Check the scan floor (is it scanning
463
+ from block 0?) before blaming the RPC tier** — that mis-diagnosis is a known trap.
357
464
 
358
- | Situation | Install | Then invoke as |
359
- |---|---|---|
360
- | The creator has a project dir (a `package.json`) — **default** | `npm install --save-dev @artblocks/abx-cli` | `npx abx …` |
361
- | No project, or they asked for a machine-wide tool | `npm install -g @artblocks/abx-cli` | `abx …` |
362
-
363
- Ask before installing **globally** — it's a machine-wide change to their PATH, and the per-project install is the reversible one. A project install needs no permission beyond the usual.
364
-
365
- Whichever you land on, **keep using that same invocation for every command in the session** (`npx abx …` vs `abx …`) — don't mix them, or you'll silently drive two different CLI versions. Then run `abx doctor` (preflight: signing key/wallet, RPC, canonical factory, storage).
366
-
367
- `.env` (in the creator's project dir) = **secrets only**:
368
- - **Signing:** `ABX_DEPLOYER_PK` (the only name read — older `SEPOLIA_FUNDED_PK`/`SEPOLIA_WALLET_PK` are retired, and the CLI's error names them if it finds neither) is needed ONLY for hot/unattended signing. If the creator owns a wallet, prefer **`--sign`** — no key in `.env`. `doctor`'s missing-key ✗ is **not fatal** on the `--sign` path.
369
- - `ABX_RPC_URLS`, `ABX_PUBLIC_BASE_URL` (remote-resolver custody only), optional `OPENSEA_API_KEY`, plus any backend secret.
370
- - **One client-side credential grammar: `ABX_REMOTE_<NAME>_URL` + `ABX_REMOTE_<NAME>_TOKEN`** (same normalization as `ABX_RPC_URLS_<CHAIN>`) — a node you run is just the name **`self`** (`deploy-resolver` generates `ABX_REMOTE_SELF_TOKEN` locally; bare `--remote` defaults to it), a managed provider is its own name. `ABX_RESOLVER_ADMIN_TOKEN` is the **resolver's own server-side config** (never read client-side) — a stale copy of it in `.env` with no `ABX_REMOTE_SELF_TOKEN` gets a pointed error naming both vars, and `abx doctor` flags it too. Only `_URL`/`_TOKEN` are read — `ABX_REMOTE_<NAME>_KEY` is ignored (the CLI flags a near-miss name).
371
-
372
- <sub>Working from the abx **source repo** (contributor)? `pnpm install`, then `pnpm abx <cmd>` or `pnpm sandbox`. Every command below is identical.</sub>
465
+ ## Reference files
373
466
 
374
- **Indexing reads the event log via `eth_getLogs` from the contract's deploy block** the CLI records it at deploy and forwards it to a resolver on `add` (discovering it on-chain for a contract it didn't deploy here). So a normal deploy→index scans a small, recent window and is fast on **any** RPC, and re-index is incremental (resumes from the last block). It **auto-chunks**, so a range cap never yields *wrong* state but don't wave a cap away: a **from-genesis** or long-span reconstruction, or a resolver serving many code projects under load, is slow and rate-limit-prone on a getLogs-**range-capped** endpoint. `abx doctor` rates each endpoint and flags a capped one — or one whose **log history is pruned**, which answers an old scan with an empty success rather than an error — treat either as a real infra signal, and give a resolver you'll run under load a range-generous **archive** RPC. **If indexing is slow, or a hosted resolver won't serve, ASK IT: `abx status <addr> [--remote <name>]`** reports the lifecycle (`queued`→`backfilling`→`live`, plus `stale`/`failed` with a machine-readable cause) — so "still catching up" and "broken" stop looking alike. Then **check the scan floor (is it scanning from block 0?) before the RPC tier** — that mis-diagnosis is a known trap. RPC deep-dive + troubleshooting → [reference/setup.md](reference/setup.md).
467
+ Load one when the decision tree above sends you there — each is the depth behind a routing line here.
375
468
 
376
- ## Reference files
469
+ | File | Holds |
470
+ |---|---|
471
+ | [decisions.md](reference/decisions.md) | the six launch decisions in depth · the READ ceiling (measured) · `--copies` custody · **locks: what freezes and what doesn't** |
472
+ | [code-projects.md](reference/code-projects.md) | the `abx.js` runtime contract · studio loop · PostParam catalog · seeds · in-chain Solidity lane · render ops · what to keep running |
473
+ | [operating.md](reference/operating.md) | owner ops · credit + license · **attaching files (data plane)** · selling via the shared minter · mint page · App Store · deploy mechanics · migrating hosting |
474
+ | [hosting.md](reference/hosting.md) | storage backends + Turbo playbook · gateways and their floors · managed providers + named remotes · `deploy-resolver`/`deploy-effects` |
475
+ | [setup.md](reference/setup.md) | install + `.env` (start here first time) · RPC selection, failover, range caps |
476
+ | [troubleshooting.md](reference/troubleshooting.md) | "my NFT looks wrong" — placeholders, stale listings, reverting `tokenURI`, baked localhost, not-registered |
477
+ | [creator-token.md](reference/creator-token.md) | ERC-721C royalty enforcement — **only when the creator raises it**; plain ERC-721 is the default |
377
478
 
378
- - **Public docs — the human-facing companion** at **https://abx.docs.artblocks.io** (quickstart, guides, the CLI/SDK reference, the protocol model). This skill is YOUR operating manual and stays authoritative for how to drive the CLI; the docs site is what you **link the creator to** for background/onboarding, and a place you can read if you want the protocol rationale behind a command. Don't send the creator commands to run (you run them) — send them the docs to *read*.
379
- - **Configuring a real launch — the six decisions in depth** (storage backends + who pays, public host URL + managed vs self-hosted, identity/credit/license, image placement + inline-vs-reader thresholds, on-chain vs off-chain resolution, store-vs-lock) → **[reference/decisions.md](reference/decisions.md)**
380
- - **Code projects — operating depth** (what to keep running, the resume loop, verify-it-resolves, render ops, `--onchain-uri`/`--image-base`/traits internals, arweave delay, `deploy-code` flags, mint timing/pause/supply) → **[reference/code-projects.md](reference/code-projects.md)**
381
- - **Operating an existing project** (owner ops, **creator credit + license fields**, **attaching files / the data plane**, selling via the shared minter, `abx mint-page`, moving hosting, resolver→resolver `migrate`) → **[reference/operating.md](reference/operating.md)**
382
- - **Royalty enforcement / ERC-721C / "make OpenSea honor my royalties" — ONLY when the creator raises it themselves** (plain ERC-721 is the default + recommendation; never offer enforcement unprompted) → **[reference/creator-token.md](reference/creator-token.md)**
383
- - **Hosting infrastructure** (storage backends, Turbo lanes + failure playbook, **managed providers + named remotes + the service descriptor**, `deploy-resolver`, `deploy-effects`, local-vs-remote stores, token API routes, Docker) → **[reference/hosting.md](reference/hosting.md)**
384
- - **Environment detail** (RPC selection + failover, multi-chain, troubleshooting) → **[reference/setup.md](reference/setup.md)**
385
- - **Troubleshooting — "my NFT looks wrong"** (gray placeholder, stale-on-marketplace, tokenURI reverts, localhost baked, "not registered", **registered-but-serving-nothing / indexing status**) — diagnose before acting → **[reference/troubleshooting.md](reference/troubleshooting.md)**
479
+ Human-facing companion: **https://abx.docs.artblocks.io** send creators there, work from this skill.
386
480
 
387
481
  ## Guarantees
388
482
 
389
- - **Reconstruction from chain alone** — deterministic, idempotent; delete the projection, replay yields identical state.
390
- - **Trust = the factory** — a clone is canonical only when the ownerless factory's `isAbxClone` confirms it (the `AbxDeployed` beacon is discovery, and spoofable). The **shared minters' events are discovery too, not proof**: both minters are deliberately neutral public utilities that will price a sale for any contract of the right shape, so a hostile target can make the canonical minter emit `Purchase` with no NFT issued. Anyone consuming those events authenticates the *token* — `isAbxClone`, the token's own `minter()`, and its `Transfer`/`TransferSingle` — never the minter's log.
391
- - **Content is verifiable** the on-chain keccak256 lets anyone re-hash the served bytes (the `verify` route), zero trust in the node.
392
- - **Keys stay with their owner** — the wallet lane signs in the user's own wallet; the CLI never sees the key. Runtime data lives in `./.abx-self-host/`, which the CLI makes **self-ignoring** (it writes a `.gitignore` inside on first use) — that directory can hold the managed Arweave key, a signing-capable credential holding prepaid upload credits, so it must never be committed. If a creator's repo predates that, check it isn't already staged.
393
-
394
- Protocol model + full docs **https://abx.docs.artblocks.io** ([protocol](https://abx.docs.artblocks.io/protocol/), [using ABX](https://abx.docs.artblocks.io/using-abx/), [CLI/SDK reference](https://abx.docs.artblocks.io/reference/)).
483
+ - **Reconstruction from chain alone** — deterministic and idempotent; delete the projection, replay
484
+ yields identical state.
485
+ - **Trust = the factory.** A clone is canonical only when the ownerless factory's `isAbxClone` confirms
486
+ it. The `AbxDeployed` beacon is discovery and spoofable, and **the shared minters' events are discovery
487
+ too**: both minters price a sale for any contract of the right shape, so a hostile target can make the
488
+ canonical minter emit `Purchase` with no NFT issued. Authenticate the **token** `isAbxClone`, its own
489
+ `minter()`, its `Transfer`/`TransferSingle` — never the minter's log.
490
+ - **Content is verifiable** — the on-chain keccak256 lets anyone re-hash the served bytes; zero trust in
491
+ the node.
492
+ - **Keys stay with their owner** — the wallet lane signs in the user's own wallet; the CLI never sees the
493
+ key. Runtime data lives in `./.abx-self-host/`, which the CLI makes self-ignoring (it writes a
494
+ `.gitignore` inside on first use) because that directory can hold a signing-capable Arweave key.