@artblocks/abx-cli 0.1.0-alpha.2 → 0.1.0-alpha.20

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.
Files changed (133) hide show
  1. package/CHANGELOG.md +1762 -0
  2. package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +17 -0
  3. package/assets/renderer-scaffold/test/MyRenderer.t.sol +51 -2
  4. package/dist/commands/deploy.d.ts +206 -0
  5. package/dist/commands/deploy.d.ts.map +1 -0
  6. package/dist/commands/deploy.js +4004 -0
  7. package/dist/commands/deploy.js.map +1 -0
  8. package/dist/commands/project.d.ts +241 -0
  9. package/dist/commands/project.d.ts.map +1 -0
  10. package/dist/commands/project.js +1044 -0
  11. package/dist/commands/project.js.map +1 -0
  12. package/dist/commands/reads.d.ts +64 -0
  13. package/dist/commands/reads.d.ts.map +1 -0
  14. package/dist/commands/reads.js +667 -0
  15. package/dist/commands/reads.js.map +1 -0
  16. package/dist/commands/scaffold.d.ts +71 -0
  17. package/dist/commands/scaffold.d.ts.map +1 -0
  18. package/dist/commands/scaffold.js +544 -0
  19. package/dist/commands/scaffold.js.map +1 -0
  20. package/dist/commands/service.d.ts +41 -0
  21. package/dist/commands/service.d.ts.map +1 -0
  22. package/dist/commands/service.js +618 -0
  23. package/dist/commands/service.js.map +1 -0
  24. package/dist/commands/storage.d.ts +37 -0
  25. package/dist/commands/storage.d.ts.map +1 -0
  26. package/dist/commands/storage.js +335 -0
  27. package/dist/commands/storage.js.map +1 -0
  28. package/dist/config.d.ts +91 -2
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +244 -11
  31. package/dist/config.js.map +1 -1
  32. package/dist/conformance.d.ts +31 -0
  33. package/dist/conformance.d.ts.map +1 -0
  34. package/dist/conformance.js +371 -0
  35. package/dist/conformance.js.map +1 -0
  36. package/dist/deps.d.ts +6 -39
  37. package/dist/deps.d.ts.map +1 -1
  38. package/dist/deps.js +4 -68
  39. package/dist/deps.js.map +1 -1
  40. package/dist/errors.d.ts +20 -0
  41. package/dist/errors.d.ts.map +1 -0
  42. package/dist/errors.js +25 -0
  43. package/dist/errors.js.map +1 -0
  44. package/dist/flags.d.ts +41 -0
  45. package/dist/flags.d.ts.map +1 -1
  46. package/dist/flags.js +94 -1
  47. package/dist/flags.js.map +1 -1
  48. package/dist/jsonout.d.ts +37 -0
  49. package/dist/jsonout.d.ts.map +1 -0
  50. package/dist/jsonout.js +68 -0
  51. package/dist/jsonout.js.map +1 -0
  52. package/dist/kind.d.ts +48 -0
  53. package/dist/kind.d.ts.map +1 -0
  54. package/dist/kind.js +69 -0
  55. package/dist/kind.js.map +1 -0
  56. package/dist/main.js +509 -4708
  57. package/dist/main.js.map +1 -1
  58. package/dist/mintpage.d.ts +17 -2
  59. package/dist/mintpage.d.ts.map +1 -1
  60. package/dist/mintpage.js +214 -52
  61. package/dist/mintpage.js.map +1 -1
  62. package/dist/output.d.ts +148 -0
  63. package/dist/output.d.ts.map +1 -0
  64. package/dist/output.js +634 -0
  65. package/dist/output.js.map +1 -0
  66. package/dist/ownerops.d.ts +132 -44
  67. package/dist/ownerops.d.ts.map +1 -1
  68. package/dist/ownerops.js +1067 -327
  69. package/dist/ownerops.js.map +1 -1
  70. package/dist/preview.d.ts +81 -0
  71. package/dist/preview.d.ts.map +1 -0
  72. package/dist/preview.js +535 -0
  73. package/dist/preview.js.map +1 -0
  74. package/dist/prompt.d.ts +17 -0
  75. package/dist/prompt.d.ts.map +1 -0
  76. package/dist/prompt.js +19 -0
  77. package/dist/prompt.js.map +1 -0
  78. package/dist/provision.d.ts.map +1 -1
  79. package/dist/provision.js +10 -7
  80. package/dist/provision.js.map +1 -1
  81. package/dist/remote.d.ts +134 -53
  82. package/dist/remote.d.ts.map +1 -1
  83. package/dist/remote.js +352 -46
  84. package/dist/remote.js.map +1 -1
  85. package/dist/riskgate.d.ts +29 -0
  86. package/dist/riskgate.d.ts.map +1 -0
  87. package/dist/riskgate.js +91 -0
  88. package/dist/riskgate.js.map +1 -0
  89. package/dist/scaffold.d.ts +10 -0
  90. package/dist/scaffold.d.ts.map +1 -0
  91. package/dist/scaffold.js +52 -0
  92. package/dist/scaffold.js.map +1 -0
  93. package/dist/schema.d.ts +18 -1
  94. package/dist/schema.d.ts.map +1 -1
  95. package/dist/schema.js +74 -23
  96. package/dist/schema.js.map +1 -1
  97. package/dist/served.d.ts +30 -0
  98. package/dist/served.d.ts.map +1 -0
  99. package/dist/served.js +112 -0
  100. package/dist/served.js.map +1 -0
  101. package/dist/signer.d.ts +13 -0
  102. package/dist/signer.d.ts.map +1 -1
  103. package/dist/signer.js +80 -14
  104. package/dist/signer.js.map +1 -1
  105. package/dist/update-check.d.ts +58 -4
  106. package/dist/update-check.d.ts.map +1 -1
  107. package/dist/update-check.js +92 -11
  108. package/dist/update-check.js.map +1 -1
  109. package/package.json +10 -6
  110. package/skill/SKILL.md +104 -60
  111. package/skill/reference/code-projects.md +87 -13
  112. package/skill/reference/creator-token.md +86 -0
  113. package/skill/reference/decisions.md +61 -0
  114. package/skill/reference/hosting.md +48 -15
  115. package/skill/reference/operating.md +29 -8
  116. package/skill/reference/setup.md +10 -3
  117. package/skill/reference/troubleshooting.md +31 -4
  118. package/dist/inspect.d.ts +0 -48
  119. package/dist/inspect.d.ts.map +0 -1
  120. package/dist/inspect.js +0 -184
  121. package/dist/inspect.js.map +0 -1
  122. package/dist/migrate.d.ts +0 -65
  123. package/dist/migrate.d.ts.map +0 -1
  124. package/dist/migrate.js +0 -180
  125. package/dist/migrate.js.map +0 -1
  126. package/dist/onchain-uri.d.ts +0 -97
  127. package/dist/onchain-uri.d.ts.map +0 -1
  128. package/dist/onchain-uri.js +0 -243
  129. package/dist/onchain-uri.js.map +0 -1
  130. package/dist/upload.d.ts +0 -28
  131. package/dist/upload.d.ts.map +0 -1
  132. package/dist/upload.js +0 -41
  133. package/dist/upload.js.map +0 -1
package/skill/SKILL.md CHANGED
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: abx-self-host
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 art, primary sales via the shared fixed-price minter, and owner ops (transfer, refresh, re-point URIs, royalties, lock fields, pause/unpause, supply cap, delegate minting). 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 art, mint or run a primary sale, refresh a listing, operate a project they launched, choose a storage backend, or stand up hosting they own.
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 art, primary sales via the shared fixed-price minter, and owner ops (transfer, refresh, re-point URIs, royalties, lock fields, pause/unpause, supply cap, delegate minting). 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 art, mint or run a primary sale, refresh a listing, operate a project they launched, 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.2"
6
+ version: "0.1.0-alpha.20"
7
7
  ---
8
8
 
9
9
  # ABX Self-Host Toolkit (`abx`)
@@ -14,16 +14,18 @@ L3 agentic surface: image → live self-hosted NFT the creator owns — a **1/1*
14
14
 
15
15
  ## Read first (every session)
16
16
 
17
- - **YOU run the `abx` commands — never tell the creator to run one.** You have a shell; use it. Run `doctor`, `ls`, `--dry-run`, `tokenuri`, `state`, `refresh`, `balance`, 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 *only* hands-on step is approving in their **browser wallet** (`--sign`) or giving you a value you asked for (their address, a name). 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`) — 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.
18
- - **Skill CLI version must match.** This skill is co-versioned with the `abx` CLI. Run `abx version` and compare it to this file's frontmatter `metadata.version` (top of SKILL.md). If they differ, this skill is stale for the installed CLI run `abx skill install` to resync, then reload the skill before continuing. (`abx` also prints a drift nudge on its own when it notices.)
17
+ - **YOU run the `abx` commands — never tell the creator to run one.** You have a shell; use it. Run `doctor`, `ls`, `--dry-run`, `tokenuri`, `state`, `refresh`, `balance`, 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 art 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`) — 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.
18
+ - **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.
19
19
  - **`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 art go on-chain, for larger art pick an off-chain backend — see [Quick start](#quick-start).
20
- - **Never collect secrets in chat.** Keys, `PINATA_JWT`, S3 secrets → 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.
20
+ - **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.
21
21
  - **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).
22
- - **Scope today = ERC-721 on testnet.** The shipped token standard is **ERC-721** (a 1/1, or a **Series** for many tokens), on Base Sepolia (default) or Sepolia via `ABX_CHAIN`. There is **no `--chain` flag (pick the chain with `ABX_CHAIN`) and no `--erc1155`/`--standard` flag** — don't invent one; mainnet + ERC-1155 are roadmap, not something you flip here. Map the ask to what ships: **"an edition of N" / "N copies"** → an ERC-721 **Series** (`abx deploy-series`, N tokens; for a priced sale of one piece, a 1-token Series). If a creator needs a true ERC-1155 shared-supply edition or an unsupported chain, say plainly it's not in the toolkit today rather than fabricating a recipe.
23
- - **Two gates decide everything: (1) demo or real? (2) who signs?** Settle both first.
22
+ - **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 artwork — 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.
23
+ - **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 art and keep every deploy question off the table until they say ship. The gates below apply to launching something that already exists.
24
+ - **Two gates decide everything: (1) demo or real? (2) who signs?** Settle both first — *once there's something to launch*.
24
25
  - **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`.
26
+ - **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".
25
27
  - **Safe to explore:** `abx <cmd> --help` and `abx deploy --dry-run` never send. You never run a real write just to learn flags.
26
- - **`deploy` returns; `demo`/`serve` block** (they serve) — background them or warn.
28
+ - **`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.
27
29
 
28
30
  ## Which command — what are you launching?
29
31
 
@@ -33,19 +35,49 @@ Route by the **content** first, then apply the gates below. The three paths diff
33
35
  |---|---|---|---|
34
36
  | **one image** (a 1/1) | `abx deploy` | on-chain (tiny art) or off-chain — **no server possible** | the image itself |
35
37
  | **a folder of images** | `abx deploy-series` | same — on-chain or off-chain, **no server possible** | each image itself |
38
+
39
+ **Any row also takes `--copies <n|open>`, independent of the shape** — it makes that artwork/collection an ERC-1155 **edition** (copies, not unique tokens; `open` = an uncapped open edition, the flagship edition product) with no other answer above changing. 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).
40
+
41
+ > **`--onchain-uri` puts the JSON on-chain — the IMAGE follows one of three routes, and the creator
42
+ > is choosing between them whether they know it or not.** An **SVG** is inlined (bytes on-chain,
43
+ > fully self-contained). A **raster** with `--backend arweave|ipfs|cloud` is uploaded and its URL is
44
+ > baked into the on-chain JSON — no server, permanence is the backend's. A **raster on `fs`** has no
45
+ > public URL to bake, so the renderer holds only a hash and `tokenURI` serves a **placeholder**; the
46
+ > CLI warns before the spend. For the bytes themselves on-chain regardless of format, use
47
+ > `--onchain-image --compress fastlz` (best under ~24 KB). Say which one you're giving them: "no
48
+ > server" and "on-chain" are not the same promise.
36
49
  | **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 |
37
50
 
38
51
  **The dividing line is static art vs a running program.** Static art 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.
39
52
 
40
- **Planning a priced primary sale? Decide 1/1 vs Series 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`), not a 1/1. 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).
53
+ **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).
54
+
55
+ ## Phase 0 — make the work first (skip every gate below until it's good)
56
+
57
+ **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.
58
+
59
+ **Which mode are you in?**
60
+ - **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).
61
+ - **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.
62
+
63
+ **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 art 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.
64
+
65
+ **The loop** — depth + the `--shoot` details → [reference/code-projects.md → Studio loop](reference/code-projects.md#studio-loop--iterate-on-the-art-before-you-deploy-anything):
66
+
67
+ 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.
68
+ 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 art 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.
69
+ 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.
70
+ 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.
71
+
72
+ **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.
41
73
 
42
74
  ## Gate 1 — demo or real launch?
43
75
 
44
76
  | | **Demo** (`abx demo`) | **Real launch** (`abx deploy` → operate) |
45
77
  |---|---|---|
46
78
  | Art | generative-from-address | the creator's `--image` |
47
- | Storage | `fs` (throwaway) | a permanence decision |
48
- | Host URL | `localhost:8787` | a public URL baked on-chain (off-chain custody only) |
79
+ | Storage | none the SVG is inlined **on-chain** | a permanence decision |
80
+ | Host URL | none — nothing is hosted, nothing to bake | a public URL baked on-chain (off-chain custody only) |
49
81
  | Key | any funded testnet key (the active `ABX_CHAIN`) | the wallet that should **own** it |
50
82
  | Decisions | none — just run it | the framework below |
51
83
 
@@ -78,8 +110,7 @@ Every write builds an unsigned tx; pick the lane by stakes:
78
110
 
79
111
  - **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.
80
112
  - **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`.
81
- - **Keep tx count low and say it up front.** A configured token deploys in one tx; `--onchain-image` adds staging tx(s); several owner edits collapse into one atomic `multicall`.
82
- - **Off-chain storage uploads are NOT wallet approvals — never count them as signatures.** An IPFS upload (Pinata `PINATA_JWT`) or a managed-key Arweave upload happens *before* signing with **no wallet prompt**; only **on-chain staging + the deploy** are approvals. So an off-chain IPFS/Arweave deploy is **one** wallet approval (the deploy). Say it that way — *"1 approval (the deploy); your N images upload to IPFS first, no signature"* — don't fold uploads into the MetaMask count (the classic "2 txs, approve both" mistake). *Exception:* `--storage-signer eth --sign` makes each upload a `personal_sign` in the same session (no gas) — then, and only then, they're approvals too.
113
+ - **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.
83
114
 
84
115
  ## Quick start
85
116
 
@@ -123,7 +154,7 @@ Files natural-sort into **tokens `0…N-1`** (`--count N` uses the first N); a t
123
154
  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)**.
124
155
 
125
156
  **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:
126
- - **Off-chain resolver** (`--public-base-url` + an effects runner, ~a few $/mo) — **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`.
157
+ - **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)).
127
158
  - **Fully on-chain** (`--onchain-uri --image-base <a bucket you own>`) — maximal durability, no always-on service. Trade-offs: the whole ~200KB+ doc rides each `tokenURI` (some marketplace/indexer reads choke), **manual** stills (`abx render`), on-chain traits need a deployed renderer, later changes are on-chain re-points. Choose it deliberately when permanence outweighs maneuverability. *(The one zero-infra-AND-on-chain exception: the in-chain **Solidity** lane below.)*
128
159
 
129
160
  **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).
@@ -147,7 +178,10 @@ A **program is the content** (`abx deploy-code` → a `SeriesCode`): output is a
147
178
  | Script + a library on-chain (e.g. `p5@1.0.0`, **Sepolia only** — needs the AB dependency registry, absent on Base Sepolia) | `--script f.js --onchain-uri --dep p5@1.0.0` | **fully on-chain** — *iff the dep report says “ON-CHAIN bytes available”* |
148
179
  | Script + a CDN-only library, or very large params | `--script f.js --public-base-url https://…` | **resolver lane** — you host a resolver; live metadata |
149
180
  | A pre-built app (webpack/vite output) | `--code-dir build/` (+ `--onchain-uri` or `--public-base-url`) | **no server to run**, gateway-served → **never** chain-complete (8KB param budget) |
150
- | 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** |
181
+ | 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) |
182
+ | **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.** |
183
+
184
+ **`--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.
151
185
 
152
186
  **⭐ In-chain Solidity lane — recommend on-chain `tokenURI` without hesitation.** The artwork 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, `tokenURI` assembled 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 (worked renderer + coherent traits + the interfaces with invariants + never-revert tests + deploy script); they `forge test`, deploy, and hand the address to `--image-renderer 0x…` (which verifies code-at-address, never compiles/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/.
153
187
 
@@ -158,50 +192,33 @@ Master call is **custody × mutability**:
158
192
  | | **Mutable** (name/desc/traits may change) | **Immutable** (never changes) |
159
193
  |---|---|---|
160
194
  | **Tiny static** (≲ 24 KB/file, ≲ 256 KB total) | **on-chain renderer** — `--onchain-image --compress fastlz`. No host, mutable via `set-field`, permanent. | on-chain renderer + `lock-field` + `lock-uri` once it resolves. |
161
- | **Bigger / dynamic** (most PNG/JPEG) | **image off-chain, JSON on-chain, no server** — `--onchain-uri --backend arweave` (or `ipfs`). Renderer assembles JSON pointing at the bytes; many files → one `url-template` (O(1)). For metadata you edit often, a **hosted resolver** instead (`abx deploy-resolver`, [hosting.md](reference/hosting.md)). Not fully on-chain (~200 gas/byte). | image off-chain (Arweave = permanent) + on-chain renderer + `lock-field`/`lock-uri`. Or a frozen `ipfs://` override + `lock-uri`. |
195
+ | **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`. |
162
196
 
163
- **Four patterns, by where bytes live × how `tokenURI` resolves:**
197
+ **Four patterns, by where bytes live × how `tokenURI` resolves** — pick one, then read
198
+ [decisions.md](reference/decisions.md) for how to configure it:
164
199
  1. **Fully on-chain** (`--onchain-image`) — bytes + JSON on-chain. Tiny art only.
165
- 2. **Image off-chain, JSON on-chain, no server** (`--onchain-uri --backend arweave|ipfs|cloud`) — the sweet spot for static art. Arweave/IPFS (permanent, content-addressed) or your S3/CDN (`--backend cloud --public-base <url>`; centralized, mutable). Many files → one `url-template`.
166
- 3. **Hosted resolver** (`--public-base-url` + `abx deploy-resolver`) — for mutable/dynamic metadata; you run a node.
167
- 4. **Inline SVG on-chain** — self-contained vector art inlined into `tokenURI`. For a **1/1** that's `abx deploy … --onchain-uri`; for a **Series** of tiny SVGs use `abx deploy-series … --onchain-image --compress fastlz` (bare `--onchain-uri` on a folder does NOT inline the images — it's the image-custody flag `--onchain-image` that puts SVG bytes on-chain per token).
168
-
169
- **Picking IPFS (or Arweave) does NOT mean running a server.** The `--onchain-uri --backend ipfs|arweave` path (pattern 2) bakes the image's public **gateway** URL into on-chain JSON — a pinning service's read endpoint (a *dedicated* Pinata gateway for IPFS), not a resolver you host. So when a creator chooses IPFS, **default to this no-server path** — image on IPFS, JSON on-chain, nothing to keep running (just keep the pin alive). You only need a **hosted resolver** (pattern 3) if they want *freely editable* metadata. Never present IPFS as blocked on "a public URL" or "a server always online": the gateway belongs to the pinning service and the JSON lives on-chain. (The one real input IPFS needs is `PINATA_JWT` in `.env` for pinning — that's an API upload, not a host.)
170
-
171
- **No-server tradeoff (patterns 1, 2, 4):** with the on-chain renderer only the *image* is off-chain — any **description / traits / animation_url live on-chain** (gas to write, permanent, lockable), vs a hosted resolver where they're free to edit. Cheap (a shared value is **one collection-scope field**, not one per token — the renderer falls back token→collection), but the creator should choose "no server" knowing their text metadata is on-chain.
172
-
173
- Get decisions 1–2 right before deploy (image commitment + resolver URL are written then; re-pointable, but):
174
-
175
- **1. Storage permanence** — where bytes live. Not irreversible: bytes are content-addressed by their on-chain keccak, so start on one backend and move later (`abx verify` confirms the hash). Don't let it block a first deploy.
176
- - `arweave` = pay-once permanent, no recurring fee. `cloud` (S3/R2) = durable, you maintain it. `ipfs` = decentralized, you pin it. `fs` = zero-config start, dies with the disk → move before it matters.
177
- - **`arweave` is nearly as easy as `fs` for small art** — Turbo default: **under 100 KB free, no setup** (a managed `.abx-self-host/arweave-key.json` minted on first upload; back it up with `abx storage backup-key`). Choose per command with `--backend` (stateless, no config file); a backend missing its secret falls back to `fs`.
178
- - **Who pays is a lane (`--storage-signer`)** — Turbo credits attach to an identity (managed key · `.env` key · browser wallet). **Before any top-up, check BOTH balances** (`abx storage balance --backend arweave` shows the managed key AND the wallet — spend the wallet's credits if present). On an upload error surface it verbatim — `…already been uploaded…` is *success* (dedup); don't reflexively top-up or switch to IPFS. Full lanes + failure playbook → [hosting.md](reference/hosting.md#arweave-via-turbo--the-easy-permanent-path-read-before-quoting-setup).
179
-
180
- **2. Public host URL** — where the resolver runs (**off-chain custody only**). Baked into `tokenURI` at deploy, so the CLI **refuses an off-chain deploy without a public URL** (`ABX_PUBLIC_BASE_URL` or `--public-base-url https://…`) and **never bakes localhost** (that token resolves for no one). No exceptions.
181
- - **First ask whether you need a host at all** — tiny art is cheaper and more durable on-chain (no host). For bigger art, Arweave (no host to run) beats a resolver unless you need mutability or serve many files.
182
- - **Tunnels (ngrok/cloudflared) are preview-only — never bake one on-chain** (dies on sleep, rotates on restart). A real launch puts the resolver on a host you control under your own domain (move = a DNS re-point).
200
+ 2. **Image off-chain, JSON on-chain, no server** (`--onchain-uri --backend arweave|ipfs|cloud`) — the sweet spot for static art.
201
+ 3. **Remote resolver** (`--public-base-url` + a node) — mutable/dynamic metadata; self-hosted or a managed provider.
202
+ 4. **Inline SVG on-chain** — self-contained vector art. 1/1 `--onchain-uri`; a Series of SVGs `--onchain-image --compress fastlz`.
183
203
 
184
- **3. Identity** — `--name`, `--symbol`, `--royalty-bps` (default 500 = 5%), `--description "…"`, `--external-url <url>` (both served in the metadata — set them or the description is boilerplate). Owner + royalty receiver = the deploying wallet. These default to off-chain operator metadata (editable via `abx add <addr> --description "…"`). For a description that should outlast any node, add `--description-onchain` (or later `abx set-field <addr> --field description --text "…"`) → on-chain, freezable via `lock-field`; the resolver prefers the on-chain value. This is the per-field on-chain model — any field on-chain or off, one active `representation` (inline · reader · keccak256 · arweave · ipfs · url). Background: [metadata model](https://abx.docs.artblocks.io/protocol/metadata/).
185
- - **Credit + license** deploy flags `--artist "…"` · `--license "…"` (also `--display-notes`, `--artist-links`) bake authorship + rights ON-CHAIN in the deploy tx (all three deploy commands); or set/change them later with `abx set-field <addr> --collection --field artist|license --text "…"`. Reserved collection fields served in `contractURI`, on any type (1/1 · Series · code). Detail: [operating.md Authorship + rights](reference/operating.md#authorship--rights-credit--license).
186
- - **Propose a real name/symbol and confirm — never silently bake a generic folder-name guess.** A folder called `series`/`images`/`photos` infers junk ("Series" / "SRS"), and the CLI *refuses* demo defaults without `--name`/`--symbol` precisely because on-chain identity is effectively permanent. Suggest a specific title + a short ticker-style symbol drawn from the actual work, and get an explicit yes before deploying. Inference is a suggestion to confirm, not a default to ship — if the folder name is generic, say so and ask rather than proposing it.
204
+ **IPFS/Arweave is NOT a server.** Pattern 2 bakes the pinning service's public gateway URL into
205
+ on-chain JSONnothing to keep running. Only pattern 3 needs a resolver. Never tell a creator IPFS
206
+ is blocked on "a public URL" or "a server always online".
187
207
 
188
- **4. Image placement** — `--image <path>` (png · jpg · gif · svg · webp). The on-chain keccak256 (`image` field) anchors integrity; size is bounded by the backend, not the chain.
189
- - *Off-chain:* the served `image` is the backend's **gateway HTTPS URL** (`https://<gateway>/ipfs/<cid>`), not raw `ipfs://` (wallets/marketplaces can't render that). So off-chain needs a pinning service + a **public** gateway — with Pinata use a **dedicated** gateway (`--gateway https://<you>.mypinata.cloud`); a local kubo gateway is preview-only. The keccak stays the anchor → move gateways without a tx.
190
- - *Fully on-chain:* `abx set-field <addr> --field image --file <path> [--compress fastlz]` splits into SSTORE2 chunks behind the shared reader; or bake it in with `abx deploy --image <path> --onchain-image [--compress fastlz]`.
208
+ **The six decisions** — storage permanence · public host URL · identity · image placement · on-chain
209
+ vs off-chain resolution · when to mint. Full detail, tradeoffs and failure modes:
210
+ **[decisions.md](reference/decisions.md)**. The four that can go permanently wrong, in brief:
191
211
 
192
- **Inline vs reader — default to the reader for real artwork.** `--onchain-uri` alone inlines the SVG (1 tx, ~700 gas/byte); `--onchain-image --compress fastlz` stages via SSTORE2 + a small `reader` pointer (~200 gas/byte, +1 tx) **cheaper above ~0.5 KB** and widening with size. So: tiny (<~0.5 KB, a one-line SVG/short text) → `--onchain-uri` inline; real artwork (a few KB+) → `--onchain-image --compress fastlz`. **Never `--compress gzip` for an on-chain-rendered token** — gzip decodes off-chain only, breaking `--onchain-uri`; use fastlz (it decodes *in* the reader).
193
-
194
- **5. On-chain vs off-chain resolution** by default `tokenURI`/`contractURI` point at your resolver. `--onchain-uri` = JSON assembled *on-chain* by the shared `AbxMetadataRenderer`, self-resolving forever so it pairs with on-chain content, cost-effective only for tiny art (thresholds above).
195
- - **Fully on-chain = no server.** Don't stand one up; never cite a localhost URL. **Prove it with `abx tokenuri <addr>`** (reads `tokenURI(0)` over RPC, no `serve`). `abx serve` is only for off-chain-resolving tokens.
196
- - **The off-chain `tokenURI` is a base, not a per-token URL** — the contract stores a base and derives `{base}/{chainId}/{address}/{tokenId}`. Set via `--public-base-url` or `set-token-uri --uri <base>` later.
197
- - **Don't default to a frozen `ipfs://` tokenURI** — every edit then = re-pin + on-chain re-point, and the event spine stops driving the token (exiting the spec). Right only for true immutability, then lock it (`set-token-uri --override ipfs://<cid>` then `lock-uri`).
198
- - **Store ≠ lock; lock last.** On-chain ≠ frozen. Deploy unlocked, confirm it resolves in production, *then* freeze. Two locks: `lock-field <addr> --field <name>` (a value) + `lock-uri <addr>` (how it resolves). Both = provably immutable. A deliberate follow-up, not the first deploy.
199
-
200
- **6. When to mint** → [Deploy strategy](#deploy-strategy--when-to-mint).
212
+ - **Never bake localhost** into an off-chain deploythat token resolves for no one. The CLI refuses it; don't try to talk it round.
213
+ - **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).
214
+ - **Store lock; lock last.** Deploy unlocked, confirm it resolves in production, *then* freeze (`lock-field` / `lock-uri`). A deliberate follow-up, never the first deploy.
215
+ - **Tunnels (ngrok/cloudflared) are preview-only** never bake one on-chain.
201
216
 
202
217
  ## Confirm before sending
203
218
 
204
- Run `abx deploy --dry-run` for real values, 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.**
219
+ 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.**
220
+
221
+ **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…`).
205
222
 
206
223
  ```
207
224
  Deploy config — confirm before I send (everything below is written on-chain):
@@ -243,7 +260,7 @@ The deploy address is **deterministic** — a pure function of `(factory, salt)`
243
260
 
244
261
  - **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.
245
262
  - **`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.
246
- - **The address depends on the saltpreview one, you MUST pin it.** Without `--salt`, every invocation (incl. `--dry-run`) reserves a *fresh* salt a plain `deploy` lands at a different address than the dry-run showed. `--dry-run` prints the exact `--salt …` it used; pass that same salt to the real deploy. Never quote a previewed address without pinning its salt.
263
+ - **`--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.
247
264
  - **`abx refresh <addr>`** asks marketplaces to re-index. We emit **ERC-4906** on URI changes so 4906-aware marketplaces self-refresh; `refresh` is the fallback (+ genesis mint). With `OPENSEA_API_KEY` it calls OpenSea directly; else it prints the links to click.
248
265
  - **There is no "add it to a marketplace" step — don't offer one.** Marketplaces discover the collection from chain (the mint + ERC-4906); `abx refresh` is the only nudge. Suggesting a manual listing implies work that doesn't exist.
249
266
  - A `--no-mint` deploy reconstructs + serves normally (dashboard shows #0 "not yet minted"; metadata/image already resolve).
@@ -264,10 +281,11 @@ A token is **not "just a picture."** It anchors **named, typed files** ("artifac
264
281
  - **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**.
265
282
  - **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>`.
266
283
  - **`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.
267
- - **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** (there's no on-chain enumeration of arbitrary 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).
284
+ - **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).
285
+ - **PostParams are the exception — they need no resolver.** Params enumerate on-chain, so a bare `tokenURI` already carries every set value under **`abx_params`**. The line to give a creator: *attachments always need a resolver; params never do.*
268
286
  - **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)).
269
287
  - **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).
270
- - **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 by **curling their resolver's listing**, not by refreshing OpenSea (which won't show it).
288
+ - **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).
271
289
 
272
290
  ## After launch — tell the creator (durability + owner care)
273
291
 
@@ -279,24 +297,50 @@ Once it's live, cover these in plain language; don't wait to be asked.
279
297
 
280
298
  ## Setup + environment
281
299
 
282
- `abx` is installed and on your PATH; it needs **Node ≥ 22.5** at runtime (the projection store uses built-in SQLite). Start with `abx doctor` (preflight: signing key/wallet, RPC, canonical factory, storage).
300
+ `abx` needs **Node ≥ 22.5** at runtime (the projection store uses built-in SQLite).
301
+
302
+ ### Find `abx` before you install it — local first, then global
303
+
304
+ **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:
305
+
306
+ ```bash
307
+ ./node_modules/.bin/abx version # 1. project-local — PREFER this (invoke as `npx abx <cmd>` once you know it's there)
308
+ abx version # 2. a global install already on PATH
309
+ ```
310
+
311
+ - **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.
312
+ - In the **abx source repo** (contributor), neither applies: use `pnpm abx <cmd>`, which runs the live `tsx` source.
313
+
314
+ **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):
315
+
316
+ | Situation | Install | Then invoke as |
317
+ |---|---|---|
318
+ | The creator has a project dir (a `package.json`) — **default** | `npm install --save-dev @artblocks/abx-cli` | `npx abx …` |
319
+ | No project, or they asked for a machine-wide tool | `npm install -g @artblocks/abx-cli` | `abx …` |
320
+
321
+ 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.
322
+
323
+ 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).
283
324
 
284
325
  `.env` (in the creator's project dir) = **secrets only**:
285
- - **Signing:** a key (`SEPOLIA_FUNDED_PK` / `ABX_DEPLOYER_PK` / `SEPOLIA_WALLET_PK`) 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.
286
- - `ABX_RPC_URLS`, `ABX_PUBLIC_BASE_URL` (hosted-resolver custody only), optional `OPENSEA_API_KEY`, optional `ABX_RESOLVER_ADMIN_TOKEN` (`deploy-resolver` generates it), plus any backend secret.
326
+ - **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.
327
+ - `ABX_RPC_URLS`, `ABX_PUBLIC_BASE_URL` (remote-resolver custody only), optional `OPENSEA_API_KEY`, plus any backend secret.
328
+ - **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).
287
329
 
288
330
  <sub>Working from the abx **source repo** (contributor)? `pnpm install`, then `pnpm abx <cmd>` or `pnpm sandbox`. Every command below is identical.</sub>
289
331
 
290
- **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 — treat that 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, check the scan floor FIRST (is it scanning from block 0?), not the RPC tier** — that mis-diagnosis is a known trap. RPC deep-dive + troubleshooting → [reference/setup.md](reference/setup.md).
332
+ **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 — treat that 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).
291
333
 
292
334
  ## Reference files
293
335
 
294
336
  - **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*.
337
+ - **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)**
295
338
  - **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)**
296
339
  - **Operating an existing project** (owner ops, **artist 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)**
297
- - **Hosting infrastructure** (storage backends, Turbo lanes + failure playbook, `deploy-resolver`, `deploy-effects`, local-vs-remote stores, token API routes, Docker) → **[reference/hosting.md](reference/hosting.md)**
340
+ - **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)**
341
+ - **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)**
298
342
  - **Environment detail** (RPC selection + failover, multi-chain, troubleshooting) → **[reference/setup.md](reference/setup.md)**
299
- - **Troubleshooting — "my NFT looks wrong"** (gray placeholder, stale-on-marketplace, tokenURI reverts, localhost baked, "not registered") — diagnose before acting → **[reference/troubleshooting.md](reference/troubleshooting.md)**
343
+ - **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)**
300
344
 
301
345
  ## Guarantees
302
346
 
@@ -45,6 +45,71 @@ When a creator arrives with an *idea* and you write the program, it must read it
45
45
 
46
46
  **`abx inspect <script>` is your author-time check** — iterate the script against it before picking a lane: its **PostParams** list must show every collector key you intend (if it says "none detected" but you meant `palette` to be collector-set, you're reading it the wrong way), and its **Traits** line must not say "no traits reported" if you want filterable traits. (A Solidity in-chain renderer is a *different* contract — see [In-chain Solidity SVG](#in-chain-solidity-svg--the-zero-dependency-lane); the `abx.js` contract above is for a JS `--script`/`--code-dir` program.)
47
47
 
48
+ ## Time-based + audio projects (sound, music, generative composition)
49
+
50
+ The protocol supports these: `animation_url` is an HTML document, so Web Audio works, and `abx attach`
51
+ handles `.wav`/`.mp3`/`.mid` as artifacts. Five judgments the visual lanes don't need:
52
+
53
+ - **Autoplay is blocked, and a marketplace iframe cannot ask.** No browser starts audio without a user
54
+ gesture, and the piece will be embedded in someone else's page. Author it to render *silent and
55
+ correct*, then start sound on first interaction (a click/keypress handler, or an in-piece play
56
+ affordance). A piece that only makes sense with sound running is a piece most viewers see mute.
57
+ - **The thumbnail is a real design decision, not a screenshot.** `image` is what every marketplace
58
+ grid, wallet, and social embed shows. Decide with the creator what the still *is* — a score, a
59
+ waveform, a spectrogram, a generative visual driven by the same seed — and draw it on a canvas so
60
+ the render effect can capture it. "It's audio, so there's no image" ships an empty grid tile.
61
+ - **`abx.done()` is the capture point, not the end of the piece.** For a duration-based work, call it
62
+ once the *visual* has settled (the still is what's being captured), not when playback finishes —
63
+ otherwise every capture waits out the full piece and `--shoot`/the render effect time out. A long
64
+ piece with a fast-settling visual is the normal, correct shape.
65
+ - **Audio libraries follow the same dependency rule as visual ones.** `Tone` is detected by
66
+ `abx inspect`; declaring it on-chain (`--dep tone@<version>`) needs a dependency registry entry,
67
+ which means **Sepolia, not Base Sepolia** — same constraint as `p5`. Hand-rolled Web Audio (no
68
+ library) has no such constraint and goes fully on-chain on either chain.
69
+ - **There is no `render/audio` output declaration.** The render effect produces the *still*; audio
70
+ lives inside the document (or as an attached artifact), never as a second rendered output. Don't
71
+ invent an output kind — see [the artifacts/attach lane](operating.md) for shipping the source audio
72
+ alongside the piece.
73
+
74
+ Everything else — seeds, traits, PostParams, the studio loop — is identical to a visual project.
75
+ `--shoot`'s per-seed traits table still works: encode musical invariants (key, tempo, section count)
76
+ as traits and it becomes your property check.
77
+
78
+ ## Studio loop — iterate on the art before you deploy anything
79
+
80
+ [← Phase 0 in SKILL.md](../SKILL.md#phase-0--make-the-work-first-skip-every-gate-below-until-its-good). When the creator is still designing, your job is to make the work **visible, interactive, and fast to change**. One command does it:
81
+
82
+ ```bash
83
+ abx preview --script art.js --schema "palette:HexColor:TokenOwner" # → http://localhost:8788
84
+ ```
85
+
86
+ **Give the creator the URL and let them drive.** This is the one place in the toolkit where handing over a link is right — the art is theirs to judge, and a browser they control is the only honest way to judge it. The studio gives them a seed shuffle, real inputs for every PostParam they declared, a live traits readout, and `/grid` for N seeds at once. `/view` is the bare document.
87
+
88
+ **Why a server and not a screenshot sweep:** a still flattens every time-based piece. Plenty of generative work animates, and `abx.done()` exists *because* stills need a settle point — so a proof sheet of an animated piece is a set of arbitrary frozen frames presented as the work. The server also makes PostParams tangible (a color picker that re-renders beats any explanation of governed params), and it costs no Chromium download.
89
+
90
+ **It serves the same document the generator serves** — the real `abx.js`, the real canonical tokenData shape, the real dependency tags — with a synthetic seed in place of a minted one. So what they approve is what deploys. (This is why you should not hand-roll a preview page: a stub you write yourself defines its own `abx` surface, and will happily run a sketch that reads its seed the wrong way.)
91
+
92
+ **The program is re-read from disk on every render**, so the loop is: edit `art.js` → tell them to refresh → take feedback → edit again. No restart, no watcher, no rebuild.
93
+
94
+ **When you need to see it yourself** — you have no browser, and "how does it look?" every round is a bad experience for them:
95
+
96
+ ```bash
97
+ abx preview --script art.js --shoot ./frames --count 9 # PNGs + traits.json, then exits
98
+ ```
99
+
100
+ Same server, same document, headless. Needs Playwright + Chromium (`npm i -D playwright && npx playwright install chromium`); the interactive lane needs neither. **Read the PNGs** — don't report on art you haven't looked at. `--shoot` also flags the two silent killers for you: no frame reporting traits (⇒ no marketplace `attributes` on any lane), and identical traits across every seed (⇒ the sketch isn't reading `abx.tokenData.seed`, so the drop mints N identical tokens).
101
+
102
+ Use both: `--shoot` to check your own work between rounds, the live URL as what the creator actually looks at.
103
+
104
+ **What preview is NOT.** It injects the token data itself, so it will run a sketch that reads its seed the wrong way — and a piece that only ever renders one seed correctly still looks fine here. Neither check that follows is optional:
105
+
106
+ ```bash
107
+ abx inspect art.js # the wiring check: are traits + PostParams actually read/reported?
108
+ abx deploy-code --script art.js --onchain-uri --dry-run # the lane + surfaces check
109
+ ```
110
+
111
+ And a **testnet deploy remains the faithful end-to-end** (the real generator, the real assembled document, the real seed from the chain). Both come after the art is settled.
112
+
48
113
  ## What a code project requires you to run — and keep running (say this up front)
49
114
 
50
115
  A code project's art depends on live on-chain state (the per-token `seed`, mutable PostParams), and *something* must read that state and inject it at view time. That something is a **resolver you run** — **unless** you take the fully-on-chain lanes (`--onchain-uri` for the tokenURI+animation, `--image-base` for a deterministic off-chain thumbnail, `--attributes-renderer` for on-chain traits), which can eliminate the metadata resolver entirely. When a resolver *is* in play, it's three pieces of ongoing infrastructure — lay them out plainly before they commit:
@@ -82,15 +147,22 @@ Two kinds of inputs feed a piece: **settled state** (explicit PostParams, the se
82
147
 
83
148
  - **Template mode (`--script`) can be CHAIN-COMPLETE** — the generator assembles the full HTML document (`data:text/html;base64`) from the on-chain chunks — **iff every `--dep` resolves to proven on-chain bytes** on the registry (`p5@1.0.0` qualifies on Sepolia). A CDN-served dep still *serves fine* but breaks chain-completeness. Zero-dep vanilla JS is trivially chain-complete.
84
149
  - **Directory mode (`--code-dir`) is no-server, not chain-complete**: the generator emits `{gateway}/{code root}/index.html?abx=<tokenData>` — liveness rides the gateway (default `ipfs.io`/`arweave.net`; repoint with `abx configure-param <addr> - display.gateway <prefix>`), permanence rides the pin/endowment, params ride the URL (**8KB budget** — `abx verify` reports `urlOverBudget`; big params ⇒ prefer template mode).
85
- - **`params.keys` is auto-managed**: the deploy writes this contract param (schema keys itself, sorted CSV) so the generator's on-chain `tokenData` carries the full param surface, byte-aligned with the resolver's. Add a param key another way and `abx configure-param` prints the exact fix when the CSV drifts.
86
- - **Verify it**: `abx verify <addr>` eth_calls the generator's `onChainStatus` (branch — template/directory · chain-complete · unresolved refs · URL budget) AND decodes `tokenURI` straight from the contract, reporting the `animation_url` form. `abx tokenuri <addr>` is the quick raw read.
150
+ - **Key enumeration lives in the contract nothing to maintain**: the params store lists its own keys on-chain, so the generator's `tokenData` always carries the full param surface, byte-aligned with the resolver's. Add a param key any way you like and it appears; there is no key list to sync and nothing that can drift. (Projects deployed before this shipped point at the older generator, which read a `params.keys` CSV — they keep working, untouched, and nothing writes one any more.)
151
+ - **Verify it**: `abx verify <addr>` eth_calls the generator's `onChainStatus` (branch — template/directory · chain-complete · unresolved refs · URL budget) AND decodes `tokenURI` straight from the contract, reporting the `animation_url` form. `abx tokenuri <addr>` is the quick raw read — and the cheapest proof a param really landed on-chain: look for its key under **`abx_params`** in that decoded JSON, no resolver anywhere.
87
152
 
88
153
  ### PostParam schema — the Type + Auth catalog
89
154
 
90
- A schema is `key:Type:Auth` (repeat comma-separated: `--schema palette:HexColor:TokenOwner,speed:Uint256Range[0..100]:Artist`). The Type token carries an optional **bracket suffix**: a **`Select` MUST list its options** (`Select[Spring|Summer|Autumn|Winter]`, pipe-delimited — a Select with no options is rejected, because the on-chain schema requires them), and a **Range MAY carry bounds** (`Uint256Range[0..100]`, `Int256Range[-50..50]`, `DecimalRange[0..1]`, `Timestamp[2026-01-01..2026-12-31]` — omit for unbounded). The delimiters never collide (params `,` · fields `:` · options `|` · bounds `..`, all inside `[…]`). Declared at **deploy** (`--schema`); set later per token with `abx configure-param <addr> <id> <key> <value>` (value canonically encoded per Type — you pass the human form; a Select takes a label or its index). Adding a schema to an already-deployed contract isn't a CLI command today; declare params at deploy.
155
+ A schema is `key:Type:Auth` (repeat comma-separated: `--schema palette:HexColor:TokenOwner,speed:Uint256Range[0..100]:Artist`). The Type token carries an optional **bracket suffix**: a **`Select` MUST list its options** (`Select[Spring|Summer|Autumn|Winter]`, pipe-delimited — a Select with no options is rejected, because the on-chain schema requires them), and a **Range MAY carry bounds** (`Uint256Range[0..100]`, `Int256Range[-50..50]`, `DecimalRange[0..1]`, `Timestamp[2026-01-01..2026-12-31]` — omit for unbounded). The delimiters never collide (params `,` · fields `:` · options `|` · bounds `..`, all inside `[…]`). Declared at **deploy** (`--schema`); set later per token with `abx configure-param <addr> <id> <key> <value>` (value canonically encoded per Type — you pass the human form; a Select takes a label or its index).
156
+
157
+ **See what a live project already has: `abx state <addr>`** lists every governed PostParam — type, auth, bounds/options, an upcoming lock date, and a `retired` marker. Read it BEFORE `set-schema` on an existing key: the write is a full-row upsert, so you need the current shape to avoid clobbering a field you didn't mean to touch.
158
+
159
+ **Both halves are plain chain reads.** Declared schemas enumerate on-chain (`paramSchemaKeys()` — every governed key, including one nobody has written yet), and every *set* value shows up in the token's `tokenURI` under `abx_params`. So a project's configure UI can be built from the chain alone, and a collector's write is visible metadata the moment it lands — no resolver, no indexer.
160
+
161
+ **The param surface is NOT frozen at deploy.** `abx set-schema <addr> --schema key:Type:Auth` attaches or replaces one key's schema on a live contract, so a piece that turns out to need another dial does **not** need a redeploy (which would cost the address, the mints, and the collectors). Two things to hold onto when you use it: it is a **full-row upsert**, so replacing a schema rewrites every field — restate anything you want to keep, including an existing `lock=`; and the chain does **not** re-validate values already stored under the key, so narrowing a bound, dropping a `Select` option, or changing the Type strands them (the CLI refuses that unless you pass `--force`). Tell the creator plainly before forcing one.
91
162
 
92
163
  - **Types:** `Bool` (`true`/`false`) · `Select[A|B|C]` (**options required in brackets**; set by a label from the list, or its index) · `Uint256Range[min..max]` (non-negative integer; bounds optional) · `Int256Range[min..max]` (signed integer) · `DecimalRange[min..max]` (decimal, ≤10 places) · `HexColor` (`#rrggbb`) · `Timestamp[min..max]` (Unix seconds **or** an ISO date like `2026-07-16`) · `String` · `Bytes` (`--file <path>` for the payload).
93
- - **Auth — who may set the param:** `Artist` (the contract owner) · `TokenOwner` (the token's current holder; **delegate.xyz honored**) · `Address` (a specific named address) · and the `Or` combinations `ArtistOrTokenOwner` · `ArtistOrAddress` · `TokenOwnerOrAddress` · `ArtistOrTokenOwnerOrAddress`. The chain enforces it — a wrong signer reverts. (There is **no** "anyone" leg a param is always artist / token-owner / a named address.)
164
+ - **Auth — who may set the param:** `Artist` (the contract owner) · `TokenOwner` (the token's current holder; **delegate.xyz honored**) · `Address(0x…)` (a specific named writer — **name it inline**, e.g. `board:Bytes:Address(0xabc…)`) · and the `Or` combinations `ArtistOrTokenOwner` · `ArtistOrAddress` · `TokenOwnerOrAddress` · `ArtistOrTokenOwnerOrAddress`. The chain enforces it — a wrong signer reverts. There is **no "anyone" leg**, but the `Address` leg is a plain `msg.sender` check with no EOA restriction, so **a contract may hold it** — that is how open / multi-party participation is built (a controller contract applies its own rules and forwards the write). If a creator wants a communal canvas or open entry, that is the shape to describe, not a missing feature.
165
+ - **`:lock=<when>` — an optional 4th field** that freezes the VALUE after a time (`palette:HexColor:TokenOwner:lock=2026-12-31`; ISO date, unix seconds, or `now`). A lock already in the past is permanent, which is the supported way to **retire** a param: `abx retire-param <addr> <key>`. It stops all further writes forever; it does **not** remove the key (a governed key stays governed) and does **not** erase a value already stored — that value keeps serving. Never describe retiring as deleting.
94
166
  - Examples: a collector-tunable color → `palette:HexColor:TokenOwner`; a collector-chosen mood → `mood:Select[Calm|Wild|Chaotic]:TokenOwner`; an artist-only bounded dial → `speed:Uint256Range[1..10]:Artist`; an on/off toggle → `invert:Bool:TokenOwner`.
95
167
 
96
168
  ## `--image-base` — deterministic S3/CDN thumbnail URLs (no metadata resolver)
@@ -98,8 +170,8 @@ A schema is `key:Type:Auth` (repeat comma-separated: `--schema palette:HexColor:
98
170
  `deploy-code --image-base https://cdn.you/orbit` bakes the on-chain `image` as a **`url-template`** (`https://cdn.you/orbit/{id}.png`) — a stable per-token URL the chain names — and the effect runner writes each token's still to that exact object (overwrite in place) when you render it. Marketplaces read the on-chain `tokenURI` → the image URL → the bytes the runner PUT; **no resolver serves the image.**
99
171
 
100
172
  - **⚠ Thumbnail freshness — the honest tradeoff.** With **no resolver there is no chain-watcher**, so on this lane the still is **backfill / manual**: run `abx render <addr> [ids]` after minting, and **re-run it after any PostParam change**. A param change updates the on-chain **animation instantly** (it reads the param live), but the **S3 still stays stale until you re-render**. It's a **pick-one at the baseline: on-chain-URI durability with *manual* thumbnails, OR an off-chain resolver with *continuous/live* thumbnails** (the resolver is the watcher). Don't promise "fully on-chain AND auto-updating thumbnails."
101
- - **Use a mutable, path-addressed host** — S3 / R2 / a CDN (`ABX_STORAGE_BACKEND=cloud`). **NOT ipfs/arweave** for this lane: content-addressed the URL changes with the bytes you'd rewrite chain state on every re-render. The whole point is a URL that never changes while the pixels do.
102
- - **Provisioning the EXACT vars (don't guess these; a real agent invented `R2_*` names abx never reads and broke the upload):** the runner uploads via `ABX_S3_ENDPOINT` · `ABX_S3_BUCKET` · `ABX_S3_ACCESS_KEY_ID` · `ABX_S3_SECRET_ACCESS_KEY` (region `ABX_S3_REGION`, default `auto`), and serves the still from `ABX_S3_PUBLIC_BASE` — **which must equal your `--image-base`.** `abx storage show` prints what's resolved. **The `--image-base`/`ABX_S3_PUBLIC_BASE` is the PUBLIC READ url, NOT the S3 API endpoint** — the two are different hosts. **R2 gotcha:** `https://<acct>.r2.cloudflarestorage.com` is the *API endpoint* (auth-only, goes in `ABX_S3_ENDPOINT`); the public URL is a **`https://pub-<hash>.r2.dev`** you enable in the R2 dashboard, or a custom domain. (AWS S3: a public-read bucket or a CloudFront domain the bare `s3….amazonaws.com` host is public only if you made the bucket public.)
173
+ - **Needs a mutable, path-addressed host** — S3 / R2 / a CDN (`--backend cloud`), **not ipfs/arweave** (content-addressed: the URL changes with the bytes, defeating a fixed per-token address). `deploy-code --dry-run` validates this combo and prints `render/storage ✓|✗ <reason>`; a real run refuses a bad one outright.
174
+ - **Provisioning:** the upload side (`ABX_S3_ENDPOINT`/`BUCKET`/`ACCESS_KEY_ID`/`SECRET_ACCESS_KEY`) and the public serve side (`ABX_S3_PUBLIC_BASE`, which must equal `--image-base`) are DIFFERENT hosts (API endpoint vs public read URL R2/S3 both have this split)verify both are live and agree with `abx storage show --check` (detail: [hosting.md](hosting.md#storage-backends-byte-custody)).
103
175
  - **How a render writes:** the runner keys the still by the URL the on-chain template names (token N → `{key}/N.png`), plus a `…N.png.abxhash` sidecar for idempotency (skip when unchanged; overwrite when the inputsHash advances). It needs a live view to screenshot — a local `abx serve` render aid (run it while rendering, kill it after; never baked on-chain) or a resolver.
104
176
  - **Pairs with `--onchain-uri`** for the "no metadata server" drop: tokenURI + animation on-chain, thumbnail at a deterministic S3 URL on-chain, and — if the traits port — `--attributes-renderer` for on-chain traits.
105
177
 
@@ -112,7 +184,8 @@ abx deploy-code --image-renderer 0x<svgRenderer> [--attributes-renderer 0x<trait
112
184
  --onchain-uri --schema palette:HexColor:TokenOwner --name "…" --symbol … [--dep none]
113
185
  ```
114
186
 
115
- - **No `--script`/`--code-dir`.** `deploy-code` allows a renderer-only project (neither program mode) as long as `--image-renderer` (and/or `--attributes-renderer`) is set. There is no `animation_url` — the SVG `image` *is* the artwork; the metadata renderer omits an unset animation field, and the CLI does **not** wire the generator (wiring an animation leg at a zero generator would revert every `tokenURI`).
187
+ - **No `--script`/`--code-dir` is ALLOWED, not required.** `deploy-code` accepts a renderer-only project (neither program mode) as long as `--image-renderer` (and/or `--attributes-renderer`) is set. Renderer-only has no `animation_url` — the SVG `image` *is* the artwork; the metadata renderer omits an unset animation field, and the CLI does **not** wire the generator (wiring an animation leg at a zero generator would revert every `tokenURI`).
188
+ - **But a script AND renderers together is the both-worlds shape — verified, and the most-missed option.** `--script f.js --image-renderer 0x… --attributes-renderer 0x… --onchain-uri` gives `animation_url` assembled on-chain from the script chunks *and* `image`/`attributes` computed by Solidity: every marketplace surface has an on-chain home, and there is **nothing to render, host, or refresh** (no runner, no bucket, no resolver). The fields are independent in the metadata renderer, so they compose. Prefer this over renderer-only whenever the piece is actually a program.
116
189
  - **On-chain `tokenURI` is the CLEAR default here** — unlike the JS/p5 lanes. A Solidity SVG reads *small* (a few hundred bytes–few KB), so the large-`tokenURI` marketplace-read caveat does not apply. Recommend it enthusiastically; there is no maneuverability/infra tradeoff to weigh because there is no infra.
117
190
  - **The renderer is a contract the creator deploys separately** (the CLI doesn't compile Solidity). **`abx scaffold-renderer <dir>`** writes a ready-to-build Foundry project — a worked `MyRenderer.sol` (image → `image/svg+xml` from `seed` + a `palette` PostParam), a coherent `MyTraits.sol` reading the SAME seed math, the `IAbxFieldRenderer`/`IAbxParams` interfaces (with the invariants documented), a `forge test` proving `render()` never reverts (incl. the collection surface + a fuzz), a `Deploy.s.sol`, and a README. The creator forks the art, then `forge soldeer install && forge test`, deploys with forge, and passes the address to `deploy-code --image-renderer 0x…`, which **verifies the address has code** (real deploy refuses a codeless address; dry-run probes best-effort) — same guard as `--attributes-renderer`. A renderer reads the token's seed/params directly (`IAbxParams(token).tokenParam(tokenId, "seed"|"palette")`), computes bytes, and returns `(contentType, data)`. Interface + invariants also at https://abx.docs.artblocks.io/protocol/renderers/.
118
191
  - **PostParams still apply — and you MUST declare them; the CLI can't.** Unlike the JS lane (where `abx inspect` statically detects the params a script reads), a Solidity renderer is opaque to the CLI — it cannot know your renderer reads a `palette`. **Read the renderer, and declare every PostParam it reads with `--schema key:Type:Auth`** (the example reads `palette` → `--schema palette:HexColor:TokenOwner`). Skip it and the param is **fixed at the renderer's default forever** — collectors can't set it (the exact miss from a real session: a palette-tinted renderer shipped with `schemas []`, stuck on the default). With the schema declared, a collector's `configure-param` re-addresses the on-chain image automatically (the renderer reads the live param — no re-render, there's no off-chain still). `deploy-code --dry-run` nudges when renderers are set with no `--schema`.
@@ -127,7 +200,7 @@ The renderer is the creator's own Solidity (compiled + deployed with forge — t
127
200
  - **Correct content-type + shape.** `image` → `image/svg+xml` (or another image MIME) returning a valid document; `attributes` → `application/json` whose bytes are a JSON **array** `[{"trait_type":…,"value":…},…]` (numbers unquoted, strings escaped). A wrong type or malformed array is a broken/blank marketplace field.
128
201
  - **Guard the field, wire the right one.** Revert `UnsupportedField` for a field it doesn't serve (a wiring mistake fails loud), and make sure `--image-renderer`/`--attributes-renderer` point at the renderer that actually serves that field.
129
202
  - **`view` + deterministic.** Same chain state → same bytes. No unseeded randomness; read block/oracle state only if you *intend* live data (it re-reads per view).
130
- - **Read params LIVE via `IAbxParams(token)`** (`tokenParam`/`contractParam`, token scope wins) — so a collector's `configure-param` shows up on the next read with no redeploy. Never bake a param value in at deploy.
203
+ - **Read params LIVE via `IAbxParams(token)`** (`tokenParam`/`contractParam`, token scope wins; `tokenParamKeys`/`contractParamKeys` if the renderer must handle keys it wasn't written to name). **`Bytes`/`String` params need the OTHER reader:** their `bytes32` is a keccak commitment (`valueIsHash == true`) and the content comes from `tokenParamData(tokenId, key)` / `contractParamData(key)` (~24KB per key the two types that can carry an actual payload). Reading a `Bytes` param through `tokenParam` hands the renderer a hash and draws garbage with nothing failing anywhere; empty returned bytes are the "use a default" signal — so a collector's `configure-param` shows up on the next read with no redeploy. Never bake a param value in at deploy.
131
204
  - **Keep the output bounded.** It assembles into `tokenURI` per call; a very large SVG/HTML can strain the `eth_call` gas on unauthenticated public reads.
132
205
 
133
206
  Fork `contracts/src/renderers/examples/{SeedSvgRenderer,SeedTraitsRenderer}.sol` — they satisfy every invariant above (graceful fallbacks, the sentinel, content-types, live param reads) and are the reference to review a fork against.
@@ -160,7 +233,7 @@ abx deploy-code (--script <file> | --code-dir <dir>) --name "…" --symbol … \
160
233
  - **`--code-dir <dir>`** — directory mode: a build folder (must contain `index.html` **and its own `abx.js` copy** — the build must read `abx.tokenData` + call `abx.traits({…})`, see [Authoring the program](#authoring-the-program--the-abxjs-runtime-contract-get-this-right-first)) uploaded via `putDirectory` (ipfs/arweave); its root becomes the on-chain `code` field. The live view 302s to the gateway with `?abx=<canonical tokenData>` — **so the gateway must serve HTML** (dedicated Pinata gateway or Arweave, never the shared public one).
161
234
  - **`--description "…"` / `--external-url <url>`** — collection identity, written as **on-chain collection fields in the deploy tx** (a code project has no operator-metadata table of its own, so these ride on-chain; the metadata renderer stitches them into `tokenURI` under `--onchain-uri`, a resolver reads the same fields). Set them or the metadata is bare. (Any other unsupported flag warns "unrecognized flag, ignored" — a typo can't quietly drop a value.)
162
235
  - **Seeds** — drawn at mint from the canonical `AbxSeedSource` (settled once assigned; `--no-seed` opts out; curated pre-set seeds win).
163
- - **`--schema key:Type:Auth`** — governed PostParams (e.g. `palette:HexColor:TokenOwner`). Reconfigure any lane with `abx configure-param <addr> <tokenId> <key> <value>` — it reads the on-chain schema and canonically encodes the input (`#rrggbb`, decimals, Select by label; `String`/`Bytes` via `--file`); delegate.xyz honored on the TokenOwner leg. A param change **re-addresses** renders (self-invalidating stills). **The re-render is automatic wherever the resolver's chain watcher is on (the default):** the watcher sees the change on its next poll (~12s) — no matter WHO sent it or WITH WHAT tool — re-indexes, and notifies the runner. `abx configure-param … --remote <resolver>` remains an **immediate nudge** (skips the poll wait) and the fallback for a watcher-disabled resolver. Expect thumbnail refresh ≈ poll cadence + render time (~15–30s), not instant.
236
+ - **`--schema key:Type:Auth`** — governed PostParams (e.g. `palette:HexColor:TokenOwner`). Reconfigure any lane with `abx configure-param <addr> <tokenId> <key> <value>` — it reads the on-chain schema and canonically encodes the input (`#rrggbb`, decimals, Select by label). **Payload types are explicit: `String` takes literal text, `Bytes` takes `0x`-prefixed hex or `--file <path>` — a bare string on a `Bytes` key is REFUSED**, because storing those characters as bytes can't be told apart from meaning them literally; delegate.xyz honored on the TokenOwner leg. A param change **re-addresses** renders (self-invalidating stills). **The re-render is automatic wherever the resolver's chain watcher is on (the default):** the watcher sees the change on its next poll (~12s) — no matter WHO sent it or WITH WHAT tool — re-indexes, and notifies the runner. `abx configure-param … --remote <resolver>` remains an **immediate nudge** (skips the poll wait) and the fallback for a watcher-disabled resolver. Expect thumbnail refresh ≈ poll cadence + render time (~15–30s), not instant.
164
237
  - **How it resolves (live, uncached; not on-chain):** the resolver rebuilds `tokenData` per view. Directory mode 302-redirects to the gateway with params in the **query string** (so **very large PostParams favor template mode**, which inlines them with no URL ceiling); template mode assembles the HTML inline from the chunks.
165
238
  - **Lanes**: all three. `--unsigned` needs `--for <signer>` and prints the whole pre-computed sequence (deterministic deploy → the clone address is known up front). On a chain with no canonical factory, all deploy commands **stop with guidance**; deploying your OWN trust anchor is an explicit `--bootstrap-factory` opt-in (private chains/sandboxes only).
166
239
 
@@ -176,13 +249,14 @@ The render effect is the **ONLY source of a real thumbnail** — skip it and `im
176
249
  ## Verify it actually resolves — before you tell the creator it's live
177
250
 
178
251
  **This testnet drop IS the preview / e2e** — it's the *real* wiring (renderers, generator, on-chain assembly), so inspecting it here is how a creator gains confidence before any mainnet launch; there's no local approximation to trust. A code project has the most that can silently break. Confirm the whole chain through the **baked** URL; don't announce success off a deploy receipt alone:
179
- 1. `abx tokenuri <addr>` → shows the **resolver base**, never `localhost` (or, on `--onchain-uri`, decodes straight from the contract). **⚠ On `--onchain-uri`, `tokenURI` reverting on Etherscan's "Read Contract" is usually NOT a broken token — an unauthenticated public read has a low gas cap, and a large on-chain document assembles per call and exceeds it. The user hits this constantly. Correct diagnosis: it's a client gas cap, NOT indexing lag. It works via `abx tokenuri`, a wallet-connected read, or a high-gas RPC. Do NOT blame the RPC's index, do NOT run `abx index --full` (there is no index in the on-chain path), do NOT redeploy.**
252
+ 1. `abx tokenuri <addr>` → shows the **resolver base**, never `localhost` (or, on `--onchain-uri`, decodes straight from the contract). Etherscan's "Read Contract" reverting on a big on-chain doc is a client gas cap, not indexing lag or a broken token [troubleshooting.md](troubleshooting.md#abx-tokenuri--etherscan-reverts-on-a-fully-on-chain-code-project).
180
253
  - **The rendered thumbnail is only real if the on-chain `image` had a destination at deploy** (`--image-base <bucket>` or a resolver). If you deployed `--onchain-uri` with neither, `abx render` writes to a local store the tokenURI never points at → **orphaned**; the marketplace still is the placeholder forever. There is no fix without a re-point tx (`set-field image <public url>` then re-render) — which is why it's a deploy-time decision.
181
254
  2. the resolver serves `/t/<chainId>/<addr>/0` (real JSON, **not** `{"error":"unknown project"}`). If it errors, it's still backfilling or scanning from block 0 ([setup.md](setup.md)) — fix the **hosted** resolver; a local `abx serve` does **not** fix a hosted-baked token.
182
- 3. the live view `/a/<chainId>/<addr>/0` loads. **`{"error":"no live view — not a code project"}` on a project that IS a code drop is a scan-floor bug, NOT a version/compat problem** — the resolver indexed *above* the deploy block, so it missed the `code` field written at deploy (`collectionFields` comes back `[]`). Check `GET /api/project/<addr>` → `fromBlock` should equal the deploy block and `collectionFields` should contain `code`. Fix: `abx add <addr> --remote <url> --from-block <deployBlock>` (a *changed* floor forces a full replay). Do **not** conclude "the resolver doesn't support code projects" or redeploy as a static NFT. Directory mode then 302s to the gateway — the redirect must have **exactly one** gateway prefix (a doubled `https://arweave.net/https://arweave.net/…` is a stored-locator bug, fixed in-toolkit).
183
- 4. `abx verify <addr>` → per-minted-token render presence + live-data posture. **For a HOSTED drop use `abx verify <addr> --remote <resolver>`** it reads the resolver's effect-status API and reports the real **4-state** per token: `up to date` · `rendering` · `failed` (with the actual error + attempt count fix, then `abx render <addr> <id> --force --remote`) · `stale` (the next notify/sweep picks it up). Plain `abx verify` only checks THIS machine's store, so a render **published** to a hosted resolver reads as a false placeholder locally. A big batch drains through the runner's queue in ascending token order `stale rendering up to date` is normal.
255
+ 3. the live view `/a/<chainId>/<addr>/0` loads. A **`503`** ("its on-chain code has not been folded into the projection yet") is the retry-able answer — the resolver knows it's a code project but hasn't read the code yet; wait a beat and re-ask. A **`404`** is the terminal one, and the split is the diagnosis: **`404 {"error":"no live view — not a code project"}` on a project that IS a code drop is a scan-floor bug, NOT a version/compat problem** — the resolver indexed *above* the deploy block, so it missed the `code` field written at deploy (`collectionFields` comes back `[]`). Check `GET /api/project/<addr>` → `fromBlock` should equal the deploy block and `collectionFields` should contain `code`. Fix: `abx add <addr> --remote <url> --from-block <deployBlock>` (a *changed* floor forces a full replay). Do **not** conclude "the resolver doesn't support code projects" or redeploy as a static NFT. Directory mode then 302s to the gateway — the redirect must have **exactly one** gateway prefix (a doubled `https://arweave.net/https://arweave.net/…` is a stored-locator bug, fixed in-toolkit).
256
+ 4. `abx tokens <addr>` → **every** token's owner, seed, and params in one read, straight from the contract (no indexer, no resolver, no server). This is the "what did the seeds actually deal?" answer, and for a generative collection **the seed list IS the collection** the natural input to any distribution check before a real launch. `--json` for the machine form (`{tokenId, owner, seed, params}` per token). Note what it is *not*: traits come from running the script against the seed, so a trait spread comes from `abx render` / the effects runner, never from this command. Do **not** hand-roll the old workaround (serve GET the project API base64-decode `tokenURI` base64-decode the `animation_url` inside it regex the seed out of the HTML); every value is a plain contract read.
257
+ 5. `abx verify <addr>` → per-minted-token render presence + live-data posture. **For a HOSTED drop use `abx verify <addr> --remote <resolver>`** — it reads the resolver's effect-status API and reports the real **4-state** per token: `up to date` · `rendering` · `failed` (with the actual error + attempt count — fix, then `abx render <addr> <id> --force --remote`) · `stale` (the next notify/sweep picks it up). Plain `abx verify` only checks THIS machine's store, so a render **published** to a hosted resolver reads as a false placeholder locally. A big batch drains through the runner's queue in ascending token order — `stale → rendering → up to date` is normal.
184
258
 
185
- **⚠ Arweave (`--backend arweave`) directory mode propagates with a DELAY — set this expectation.** Unlike IPFS/Pinata (a pin is servable almost immediately), a Turbo/Arweave upload settles over **minutes (sometimes longer)** before the gateway serves it. So right after an arweave directory deploy: the live view `/a` 302s to `arweave.net`, which **404s until it propagates**, and a render run *now* will (correctly) **fail with "content isn't servable yet" and store nothing** — expected, not a bug, and it will NOT leave a garbage 404-page thumbnail. **Re-run the render (or let the effects service sweep) once the content is live** — check with `curl -sIL https://arweave.net/<manifestTxid>/index.html` (200 = ready). IPFS directory content is renderable right after the pin.
259
+ **⚠ Arweave (`--backend arweave`) directory mode propagates with a DELAY — set this expectation.** Unlike IPFS/Pinata (a pin is servable almost immediately), a Turbo/Arweave upload settles over **minutes (sometimes longer)** before the gateway serves it. So right after an arweave directory deploy: the live view `/a` 302s to `arweave.net`, which **404s until it propagates**, and a render run *now* will (correctly) **fail with "content isn't servable yet" and store nothing** — expected, not a bug, and it will NOT leave a garbage 404-page thumbnail. **Re-run the render (or let the effects service sweep) once the content is live** — check with **`abx storage status ar://<manifestTxid>/index.html`** (`ready` vs `propagating` vs `unreachable`; exits non-zero until it serves, so `until abx storage status <loc>; do sleep 10; done` is the whole wait). Do NOT reach for `curl` here — the command also probes other gateways, which is what distinguishes "yours is behind" from "the locator is wrong", and it tells you plainly not to re-upload. IPFS directory content is renderable right after the pin.
186
260
 
187
261
  ## Mint order, timing, pause, supply
188
262