@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.
- package/CHANGELOG.md +1762 -0
- package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +17 -0
- package/assets/renderer-scaffold/test/MyRenderer.t.sol +51 -2
- package/dist/commands/deploy.d.ts +206 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +4004 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/project.d.ts +241 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +1044 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/reads.d.ts +64 -0
- package/dist/commands/reads.d.ts.map +1 -0
- package/dist/commands/reads.js +667 -0
- package/dist/commands/reads.js.map +1 -0
- package/dist/commands/scaffold.d.ts +71 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/scaffold.js +544 -0
- package/dist/commands/scaffold.js.map +1 -0
- package/dist/commands/service.d.ts +41 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +618 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/storage.d.ts +37 -0
- package/dist/commands/storage.d.ts.map +1 -0
- package/dist/commands/storage.js +335 -0
- package/dist/commands/storage.js.map +1 -0
- package/dist/config.d.ts +91 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +244 -11
- package/dist/config.js.map +1 -1
- package/dist/conformance.d.ts +31 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +371 -0
- package/dist/conformance.js.map +1 -0
- package/dist/deps.d.ts +6 -39
- package/dist/deps.d.ts.map +1 -1
- package/dist/deps.js +4 -68
- package/dist/deps.js.map +1 -1
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -0
- package/dist/flags.d.ts +41 -0
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +94 -1
- package/dist/flags.js.map +1 -1
- package/dist/jsonout.d.ts +37 -0
- package/dist/jsonout.d.ts.map +1 -0
- package/dist/jsonout.js +68 -0
- package/dist/jsonout.js.map +1 -0
- package/dist/kind.d.ts +48 -0
- package/dist/kind.d.ts.map +1 -0
- package/dist/kind.js +69 -0
- package/dist/kind.js.map +1 -0
- package/dist/main.js +509 -4708
- package/dist/main.js.map +1 -1
- package/dist/mintpage.d.ts +17 -2
- package/dist/mintpage.d.ts.map +1 -1
- package/dist/mintpage.js +214 -52
- package/dist/mintpage.js.map +1 -1
- package/dist/output.d.ts +148 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +634 -0
- package/dist/output.js.map +1 -0
- package/dist/ownerops.d.ts +132 -44
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +1067 -327
- package/dist/ownerops.js.map +1 -1
- package/dist/preview.d.ts +81 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +535 -0
- package/dist/preview.js.map +1 -0
- package/dist/prompt.d.ts +17 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +19 -0
- package/dist/prompt.js.map +1 -0
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +10 -7
- package/dist/provision.js.map +1 -1
- package/dist/remote.d.ts +134 -53
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +352 -46
- package/dist/remote.js.map +1 -1
- package/dist/riskgate.d.ts +29 -0
- package/dist/riskgate.d.ts.map +1 -0
- package/dist/riskgate.js +91 -0
- package/dist/riskgate.js.map +1 -0
- package/dist/scaffold.d.ts +10 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +52 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/schema.d.ts +18 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +74 -23
- package/dist/schema.js.map +1 -1
- package/dist/served.d.ts +30 -0
- package/dist/served.d.ts.map +1 -0
- package/dist/served.js +112 -0
- package/dist/served.js.map +1 -0
- package/dist/signer.d.ts +13 -0
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +80 -14
- package/dist/signer.js.map +1 -1
- package/dist/update-check.d.ts +58 -4
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +92 -11
- package/dist/update-check.js.map +1 -1
- package/package.json +10 -6
- package/skill/SKILL.md +104 -60
- package/skill/reference/code-projects.md +87 -13
- package/skill/reference/creator-token.md +86 -0
- package/skill/reference/decisions.md +61 -0
- package/skill/reference/hosting.md +48 -15
- package/skill/reference/operating.md +29 -8
- package/skill/reference/setup.md +10 -3
- package/skill/reference/troubleshooting.md +31 -4
- package/dist/inspect.d.ts +0 -48
- package/dist/inspect.d.ts.map +0 -1
- package/dist/inspect.js +0 -184
- package/dist/inspect.js.map +0 -1
- package/dist/migrate.d.ts +0 -65
- package/dist/migrate.d.ts.map +0 -1
- package/dist/migrate.js +0 -180
- package/dist/migrate.js.map +0 -1
- package/dist/onchain-uri.d.ts +0 -97
- package/dist/onchain-uri.d.ts.map +0 -1
- package/dist/onchain-uri.js +0 -243
- package/dist/onchain-uri.js.map +0 -1
- package/dist/upload.d.ts +0 -28
- package/dist/upload.d.ts.map +0 -1
- package/dist/upload.js +0 -41
- 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,
|
|
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.
|
|
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
|
|
18
|
-
- **
|
|
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
|
|
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 =
|
|
23
|
-
- **
|
|
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`),
|
|
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 |
|
|
48
|
-
| Host URL |
|
|
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` +
|
|
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` (
|
|
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`).
|
|
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.
|
|
166
|
-
3. **
|
|
167
|
-
4. **Inline SVG on-chain** — self-contained vector art
|
|
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
|
-
**
|
|
185
|
-
-
|
|
186
|
-
|
|
204
|
+
**IPFS/Arweave is NOT a server.** Pattern 2 bakes the pinning service's public gateway URL into
|
|
205
|
+
on-chain JSON — nothing 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
|
-
**
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
- **
|
|
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 deploy — that 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
|
-
-
|
|
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** (
|
|
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
|
|
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`
|
|
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:**
|
|
286
|
-
- `ABX_RPC_URLS`, `ABX_PUBLIC_BASE_URL` (
|
|
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
|
|
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
|
-
- **
|
|
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
|
-
-
|
|
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).
|
|
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
|
|
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
|
-
- **
|
|
102
|
-
- **Provisioning
|
|
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
|
|
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
|
|
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).
|
|
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
|
|
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
|
|
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
|
|