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

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 (65) hide show
  1. package/CHANGELOG.md +322 -0
  2. package/dist/commands/deploy.d.ts +27 -4
  3. package/dist/commands/deploy.d.ts.map +1 -1
  4. package/dist/commands/deploy.js +220 -70
  5. package/dist/commands/deploy.js.map +1 -1
  6. package/dist/commands/project.d.ts.map +1 -1
  7. package/dist/commands/project.js +15 -0
  8. package/dist/commands/project.js.map +1 -1
  9. package/dist/commands/scaffold.d.ts.map +1 -1
  10. package/dist/commands/scaffold.js +27 -4
  11. package/dist/commands/scaffold.js.map +1 -1
  12. package/dist/commands/storage.d.ts +14 -0
  13. package/dist/commands/storage.d.ts.map +1 -1
  14. package/dist/commands/storage.js +31 -4
  15. package/dist/commands/storage.js.map +1 -1
  16. package/dist/config.d.ts +13 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +25 -1
  19. package/dist/config.js.map +1 -1
  20. package/dist/conformance.d.ts.map +1 -1
  21. package/dist/conformance.js +20 -1
  22. package/dist/conformance.js.map +1 -1
  23. package/dist/flag-allowlists.d.ts.map +1 -1
  24. package/dist/flag-allowlists.js +4 -0
  25. package/dist/flag-allowlists.js.map +1 -1
  26. package/dist/kind.d.ts +8 -0
  27. package/dist/kind.d.ts.map +1 -1
  28. package/dist/kind.js +17 -0
  29. package/dist/kind.js.map +1 -1
  30. package/dist/main.js +38 -8
  31. package/dist/main.js.map +1 -1
  32. package/dist/output.d.ts.map +1 -1
  33. package/dist/output.js +59 -2
  34. package/dist/output.js.map +1 -1
  35. package/dist/ownerops.d.ts +51 -0
  36. package/dist/ownerops.d.ts.map +1 -1
  37. package/dist/ownerops.js +152 -5
  38. package/dist/ownerops.js.map +1 -1
  39. package/dist/provision.d.ts.map +1 -1
  40. package/dist/provision.js +6 -1
  41. package/dist/provision.js.map +1 -1
  42. package/dist/remote.d.ts +20 -0
  43. package/dist/remote.d.ts.map +1 -1
  44. package/dist/remote.js +38 -0
  45. package/dist/remote.js.map +1 -1
  46. package/dist/riskgate.d.ts +19 -0
  47. package/dist/riskgate.d.ts.map +1 -1
  48. package/dist/riskgate.js +31 -1
  49. package/dist/riskgate.js.map +1 -1
  50. package/dist/schema.js +4 -4
  51. package/dist/schema.js.map +1 -1
  52. package/dist/script-chunks.d.ts +8 -0
  53. package/dist/script-chunks.d.ts.map +1 -0
  54. package/dist/script-chunks.js +35 -0
  55. package/dist/script-chunks.js.map +1 -0
  56. package/dist/signer.d.ts.map +1 -1
  57. package/dist/signer.js +4 -1
  58. package/dist/signer.js.map +1 -1
  59. package/package.json +6 -6
  60. package/skill/SKILL.md +18 -8
  61. package/skill/reference/code-projects.md +3 -3
  62. package/skill/reference/decisions.md +3 -3
  63. package/skill/reference/hosting.md +15 -4
  64. package/skill/reference/operating.md +27 -1
  65. package/skill/reference/setup.md +2 -1
@@ -17,7 +17,7 @@ Master call is **custody × mutability**:
17
17
  1. **Fully on-chain** (`--onchain-image`) — bytes + JSON on-chain. Small content only, and the binding limit is READ gas, not storage cost (below).
18
18
  2. **Image off-chain, JSON on-chain, no server** (`--onchain-uri --backend arweave|ipfs|cloud`) — the sweet spot for static content. Arweave/IPFS (permanent, content-addressed) or your S3/CDN (`--backend cloud --public-base <url>`; centralized, mutable). Many files → one `url-template`.
19
19
  3. **Remote resolver** (`--public-base-url` + a node) — for mutable/dynamic metadata; **self-hosted** (`abx deploy-resolver`, you run it) or a **managed provider** (an API key, they run it). Same interface — swap with one re-point.
20
- 4. **Inline SVG on-chain** — self-contained vector content 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).
20
+ 4. **Inline SVG on-chain** — self-contained vector content inlined into `tokenURI`. For a **tiny 1/1** (under ~0.5 KB — a one-line SVG) that's `abx deploy … --onchain-uri`; **at a few KB and up, reach for `--onchain-image --compress fastlz` instead** (cheaper to write past ~0.5 KB — see *Inline vs reader* below, and note the 2.4 KB example SVG lands on that side). Same flag for a **Series** of tiny SVGs: `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).
21
21
 
22
22
  **All three deploy commands can now do pattern 2.** `deploy` (1/1), `deploy-series` and `deploy-code`
23
23
  each upload to the chosen backend under `--onchain-uri` and bake the resulting public URL into the
@@ -58,7 +58,7 @@ Get decisions 1–2 right before deploy (image commitment + resolver URL are wri
58
58
 
59
59
  **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.
60
60
  - `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.
61
- - **`arweave` is nearly as easy as `fs` for small content** — 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 does NOT fall back to `fs`** — it fails: `cloud` refuses up front naming the missing values, and `ipfs` without `PINATA_JWT` resolves to **kubo mode against a local node** (`http://127.0.0.1:5001`), which fails at upload time if you aren't running one. Check with `abx storage show` (it prints the resolved backend *and* mode) before a real upload.
61
+ - **`arweave` is nearly as easy as `fs` for small content** — Turbo default: **under 100 KB free, no setup** (a managed `.abx-self-host/arweave-key.json` minted the first time a command needs it — an upload, or a `storage balance`/`topup`; back it up with `abx storage backup-key`). Choose per command with `--backend` (stateless, no config file). **A backend missing its secret does NOT fall back to `fs`** — it fails: `cloud` refuses up front naming the missing values, and `ipfs` without `PINATA_JWT` resolves to **kubo mode against a local node** (`http://127.0.0.1:5001`), which fails at upload time if you aren't running one. Check with `abx storage show` (it prints the resolved backend *and* mode) before a real upload.
62
62
  - **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).
63
63
 
64
64
  **2. Public host URL — and who runs the resolver** (**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.
@@ -72,7 +72,7 @@ Get decisions 1–2 right before deploy (image commitment + resolver URL are wri
72
72
  - **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 on all three deploy commands the CLI *refuses* a real send that would bake its own placeholder identity (`--name`/`--symbol` missing) — because on-chain identity is effectively permanent. **In `--dry-run` the same check only warns** (so a preview still runs before you have the creator's title); don't read that warning as "the CLI allows it" — the real deploy stops. 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.
73
73
 
74
74
  **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.
75
- - *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.
75
+ - *Off-chain:* the served `image` is an **HTTPS gateway 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. Which prefix gets used depends on the field's representation, and the two cases differ: a **keccak-custody** field is served from whatever gateway the resolver is configured with (no tx to change it), while an **`ipfs`/`arweave`** field holds the bare CID/txid and takes its prefix from the collection's on-chain preference — floors `https://ipfs.io/ipfs/` and `https://arweave.net/`, changed with **one tx** (`abx set-gateway`), never a re-upload.
76
76
  - *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]`.
77
77
 
78
78
  **Inline vs reader — default to the reader for real work.** `--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 work (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).
@@ -13,7 +13,18 @@ Storage is **stateless** — there's no config file. Choose a backend **per comm
13
13
  | **`ipfs`** | Decentralized (CID) | kubo: `--backend ipfs --mode kubo --api-url http://127.0.0.1:5001 --gateway http://127.0.0.1:8080` · pinata: `--backend ipfs --mode pinata --gateway https://<you>.mypinata.cloud` | `PINATA_JWT` (pinata mode) |
14
14
  | **`arweave`** | Pay-once permanent | `--backend arweave` (Turbo default; or `ABX_STORAGE_BACKEND=arweave`) | the Turbo key (auto-managed) |
15
15
 
16
- All backends verify via the on-chain keccak commitment wherever the bytes live. **The served `image` is the backend's gateway HTTPS URL** → IPFS/Arweave custody needs a *public* gateway (Arweave's `https://arweave.net` is the public default). A `127.0.0.1` gateway is preview-only (the CLI warns); the shared `gateway.pinata.cloud` is rate-limited — use a **dedicated** gateway for anything real.
16
+ All backends verify via the on-chain keccak commitment wherever the bytes live. **What a marketplace fetches is always an HTTPS gateway URL** → IPFS/Arweave custody needs a *public* gateway. The two public **floors**, used whenever a project states no preference, are:
17
+
18
+ | Scheme | Floor (used when the project sets nothing) |
19
+ |---|---|
20
+ | `ipfs` | `https://ipfs.io/ipfs/` |
21
+ | `arweave` | `https://arweave.net/` |
22
+
23
+ Do not guess these and do not substitute the *upload* gateway for them — a project that uploaded through Pinata still **serves** from `ipfs.io` unless it said otherwise, and telling a creator otherwise gives them a URL their token does not use. A `127.0.0.1` gateway is preview-only (the CLI warns); the shared `gateway.pinata.cloud` is rate-limited — use a **dedicated** gateway for anything real, and set it with `--ipfs-gateway` (deploy) or `abx set-gateway` (after).
24
+
25
+ **A resolver adds nothing marketplace-facing to a project that already resolves on-chain — say so before you set one up.** The docs below explain when a resolver *helps* (mutable metadata, code drops needing rendering, off-chain traits); the inverse is just as load-bearing and was missing. Check first: `abx state <addr>` printing `renderer on-chain (0x…)` means `tokenURI` answers from the chain, so wallets and marketplaces read **that** document and never touch a resolver. Registering with one is still worth doing for **indexing, the live view, and managed rendering** — but it will not change a single thing a collector sees, and a creator told to "put it on a hosted service so it shows up properly" needs to hear that plainly rather than be walked through a setup that changes nothing. (`abx add --remote` now says this too.) Conversely, a project whose renderer is **off-chain** genuinely depends on a resolver, and that is the case the rest of this page is about.
26
+
27
+ **Which gateway, and who can change it.** For a `keccak256`-custody field the resolver builds that URL from the backend's own gateway. For an `ipfs`/`arweave` field — what `--onchain-uri --backend ipfs|arweave` writes — the chain holds only the CID/txid, and the prefix comes from the collection's `abx_gateway_ipfs` / `abx_gateway_arweave` setting (public defaults when unset). Set it at deploy with `--ipfs-gateway` / `--arweave-gateway` (plain `--gateway` seeds the one matching your backend), and change it any time with **`abx set-gateway <addr> --ipfs <prefix> --arweave <prefix>`** — one tx, every token, no re-upload, and it works on fields that are already locked. `--gateway` remains the UPLOAD/probe gateway; these are the SERVING one, and they are the same value in almost every project.
17
28
 
18
29
  **Code projects (directory mode) need a gateway that serves HTML.** A `deploy-code --code-dir` build's live view 302-redirects to `<gateway>/ipfs/<cid>/index.html` — and the **shared Pinata public gateway blocks HTML** (`ERR_ID:00023`, "HTML content cannot be served through the pinata public gateway"). So a code directory drop requires a **dedicated** Pinata gateway (paid) or **Arweave** (`https://arweave.net` serves HTML). Static image directories (`deploy-series`) are unaffected — only the executable HTML build hits this.
19
30
 
@@ -28,7 +39,7 @@ All backends verify via the on-chain keccak commitment wherever the bytes live.
28
39
  `arweave` defaults to the **Turbo** provider (ArDrive). For small content it needs **no setup and costs nothing** — no wallet dashboard, no JWT (don't send anyone to `app.arweave.org`). How it works:
29
40
 
30
41
  - **Identity is a lane (`--storage-signer`) — Turbo is multi-chain, so credits attach to whichever identity signs the upload.** Three choices, mirroring the tx signing lanes:
31
- - **`arweave`** *(default)* — a CLI-managed Arweave key auto-generated at `.abx-self-host/arweave-key.json` on first upload (address printed). Reused across every upload/project; it signs *and* holds credits. Zero-config. Bring-your-own via `ARWEAVE_JWK` (inline) / `ABX_ARWEAVE_KEY_FILE` (path). **Back it up:** `abx storage backup-key --out <path>` (copies the file — the key is never printed to the terminal/log).
42
+ - **`arweave`** *(default)* — a CLI-managed Arweave key auto-generated at `.abx-self-host/arweave-key.json` the first time a command actually needs it — an upload, or a `storage balance` / `storage topup` (so a creator can fund the identity *before* uploading). Reads that never need a signer (`doctor`, `--dry-run`, `storage show`) do not create one. The address is printed with a back-it-up notice on creation. Reused across every upload/project; it signs *and* holds credits. Zero-config. Bring-your-own via `ARWEAVE_JWK` (inline) / `ABX_ARWEAVE_KEY_FILE` (path). **Back it up:** `abx storage backup-key --out <path>` (copies the file — the key is never printed to the terminal/log).
32
43
  - **`--storage-signer eth`** — reuse the **`.env` EVM signing key** (`ABX_DEPLOYER_PK`) as the Turbo identity. Its ETH address holds the credits, so credits funded on that wallet's Turbo balance are spendable. Hot lane (key in `.env`).
33
44
  - **`--storage-signer eth` + `--sign`** — the **browser wallet** signs each upload data-item via `personal_sign` (no gas, no funds move; the upload is paid from the wallet's Turbo credits), in the same sign session as the deploy tx. The key never leaves the wallet. *(New; the mechanism matches arbundles' `InjectedEthereumSigner` and is unit-tested at the CLI↔page contract, but the live MetaMask↔Turbo path wants a manual smoke test. If a live upload misbehaves, fall back to `--storage-signer eth` with a key, or fund the managed key.)*
34
45
  - **Under 100 KB → free**, permanent, zero setup. The CLI prints this from the file size (in `--dry-run` too).
@@ -45,7 +56,7 @@ A failed upload is the #1 place an agent goes off the rails: it invents a cause
45
56
  - **Check BOTH balances before concluding "needs funding."** `abx storage balance --backend arweave` prints the managed key's balance **and** the deployer wallet's Turbo balance (Turbo exposes balance-by-address publicly — no key needed). The managed key showing 0 does NOT mean "top up": if the **wallet** has credits, use them (`--storage-signer eth`, or `+ --sign` for a browser wallet). The pre-upload funds guard does this check for you and recommends the wallet lane when it applies — checking only the managed key's 0 and pushing a top-up is the classic wrong turn.
46
57
  - **Don't reflexively push a $5 top-up or "switch to IPFS."** Both throw away what the creator chose (their funded wallet credits; permanence). Only top up if *neither* the managed key nor the wallet can cover the bytes — and **back up the managed key first** (`abx storage backup-key --out <path>`), since it will hold the credits you buy. Only suggest IPFS if they ask or Arweave is truly unavailable.
47
58
  - **A blind retry re-signs the same bytes → same dedup reply.** Retry only after you've identified and fixed the actual cause.
48
- - **Gateway swappable, integrity independent.** The on-chain keccak256 is the anchor; `arweave.net` is the default gateway (`--gateway` to override). A gateway issue is a re-point, never a lost asset.
59
+ - **Gateway swappable, integrity independent.** The on-chain keccak256 (or the CID itself) is the anchor; `arweave.net` is the default gateway (`--gateway` at upload, `abx set-gateway` for serving). A gateway issue is a re-point, never a lost asset.
49
60
  - **Provider swappable.** `--provider http-bundler --upload-url <bundler>` uses a dep-free token-authed endpoint you run (secret `ARWEAVE_UPLOAD_TOKEN`); funding then means "fund the wallet directly" (only Turbo tracks credits). Default to Turbo unless asked.
50
61
 
51
62
  ### Directory-base — image off-chain, JSON on-chain, no server (the medium/large static default)
@@ -71,7 +82,7 @@ The other way to have a resolver: be a **customer** of a hosted provider instead
71
82
 
72
83
  ## Self-hosted resolver — `abx deploy-resolver`
73
84
 
74
- For the large/mutable default, when the creator runs the node themselves (the alternative to a managed provider above — same interface, they own the cloud account). `abx deploy-resolver --provider <fly|render|vps> --domain <meta.you.xyz>` scaffolds the artifact and prints the exact next steps, the DNS record, and the bake reminder. Providers: **fly.io** / **render** (Docker PaaS, free tier, custom domain) and a **VPS** (compose + Caddy auto-TLS). The host is **read-only** — serves + accepts admin index-control, no signing key on it (writes are signed locally), so a compromised host can at worst serve wrong bytes (the keccak catches it). Prefer **a domain you control** (move = DNS, not a tx) — but without one the scaffold now bakes the **real platform hostname** (`<app>.fly.dev` / `<app>.onrender.com`) so the resolver works out of the box (add a custom domain later). It NO LONGER bakes a dead `<app>.example` placeholder, and the resolver **refuses to serve** an `.example`/placeholder base (or a localhost base in a hosted image, `ABX_HOSTED=1`) — a loud fail beats silently serving dead image/animation links. The one sub-decision is *which provider* (ask + recommend); you scaffold, the human owns the cloud account + domain.
85
+ For the large/mutable default, when the creator runs the node themselves (the alternative to a managed provider above — same interface, they own the cloud account). `abx deploy-resolver --provider <fly|render|vps> --domain <meta.you.xyz>` scaffolds the artifact and prints the exact next steps, the DNS record, and the bake reminder. Providers: **fly.io** / **render** (Docker PaaS, custom domain) and a **VPS** (compose + Caddy auto-TLS). **Do not quote their plans or promise a free tier** — those move, and a stale claim in here becomes a stale claim to a creator; point at the provider's own pricing page and let them read it. The host is **read-only** — serves + accepts admin index-control, no signing key on it (writes are signed locally), so a compromised host can at worst serve wrong bytes (the keccak catches it). Prefer **a domain you control** (move = DNS, not a tx) — but without one the scaffold now bakes the **real platform hostname** (`<app>.fly.dev` / `<app>.onrender.com`) so the resolver works out of the box (add a custom domain later). It NO LONGER bakes a dead `<app>.example` placeholder, and the resolver **refuses to serve** an `.example`/placeholder base (or a localhost base in a hosted image, `ABX_HOSTED=1`) — a loud fail beats silently serving dead image/animation links. The one sub-decision is *which provider* (ask + recommend); you scaffold, the human owns the cloud account + domain.
75
86
 
76
87
  **The artifact is fully self-contained.** `deploy-resolver` writes `deploy/<provider>/` with its OWN `Dockerfile` + `.dockerignore` + config (a production image installs the published CLI: `npm i -g --no-optional @artblocks/abx-cli`). **Run every next step from that dir.** You never supply, copy, or hand-edit a Dockerfile. **If a step seems to need a file from elsewhere (`../Dockerfile`, a `packages/` dir, the repo), STOP — that's a scaffold bug, not something to work around.** Report it; don't MacGyver it. (Local from-source/contributor dev sets `ABX_RESOLVER_SOURCE=1` → a build-from-source artifact instead, still self-contained; you don't set this.)
77
88
 
@@ -24,8 +24,12 @@ After launch the owner operates the project. Each command builds a tx, signs it
24
24
  | `abx set-royalty <addr> --bps <0-10000> [--receiver 0x..]` | change the royalty (receiver defaults to current) | the rate, and whether the payee changes |
25
25
  | `abx attach <addr> <key> <ipfs://…\|ar://…\|https://…>` | attach a named file to a token → the `artifacts` manifest (the data plane) | the key (how it appears); locator vs `--file` on-chain |
26
26
  | `abx set-field <addr> --field <name> (--text "…" \| --value 0x..) [--representation <r>] [--collection]` | set an on-chain metadata field (token or collection scope) — the low-level primitive | which field, where it lives, on-chain vs off |
27
+ | `abx set-gateway <addr> [--ipfs <prefix>] [--arweave <prefix>]` | repoint where this collection's `ipfs`/`arweave` fields are SERVED from (`none` clears → public default) | the prefix (must be the whole thing, e.g. `https://you.mypinata.cloud/ipfs/`) |
27
28
  | `abx lock-field <addr> --field <name> [--collection]` | **freeze a field forever** (all its representations) | confirm — irreversible |
28
29
  | `abx lock-script <addr>` | **freeze the on-chain program (script chunks) forever** — the lock that freezes a code project's *work* (`setScriptChunk`/`removeLastScriptChunk` revert after) | confirm — irreversible; SeriesCode/EditionCode only |
30
+
31
+ **There is no `abx replace-script`.** Until `lock-script`, the contract still accepts `setScriptChunk`, but the supported CLI does not expose post-deploy rewrite (only `deploy-code --resume` for an *incomplete* setup). To change a live program, deploy a new contract. Don't hand-roll ABI writes.
32
+
29
33
  | `abx lock-dependencies <addr>` | **freeze the dependency set forever** (library list + registry pointer) | confirm — irreversible |
30
34
  | `abx lock-param-hooks <addr>` | **freeze the three param hooks forever** (configure/augment/transfer) — the lock a *buyer* cares about: the transfer hook is a veto over transfers **and** mints, so until this is sent the owner can arm one | confirm — irreversible; SeriesCode/EditionCode only. Freezing an EMPTY set is the proof "no transfer veto can ever be armed"; a hook already set keeps its veto. No carve-out: hooks are owner-only forever and a freeze holds against everyone, including on an **ownerless** project — unlike the 721C validator, don't conflate them ([why](code-projects.md#live-data-the-augment-hook--the-hook-is-the-setting)) |
31
35
  | `abx set-seed-source <addr> <0x..\|canonical\|none>` | re-point a **code project's** mint-seed source — the creator's own `IAbxSeedSource` (commit-reveal / VRF), the shared `canonical` one, or `none`. Probed first: it must answer `seed(uint256,address)` with 32 bytes or the command refuses | **future mints only** (assigned seeds are settled) — on a part-sold drop the collection then spans two sources; pause and say so. Code projects only |
@@ -64,8 +68,10 @@ A token anchors **named, typed files**; the served JSON's **`artifacts`** array
64
68
  - **`mimeType` is declared from the URL extension** (`…/master.tiff` → `image/tiff`, `…/coa.pdf` → `application/pdf`). No extension → `application/octet-stream` (a warning fires). **Point the URI at the file itself with its real extension** — the on-chain field has no MIME slot; the extension is the declaration.
65
69
  - **`--file <path>`** stores tiny bytes ON-CHAIN (SSTORE2) instead — for a small file with no external host (≈200 gas/byte, so locators are the norm for anything non-trivial).
66
70
  - scope: `--token <id>` (default 0) or `--collection`; any lane; `--dry-run` previews the tx.
71
+ **Gateway ≠ field.** An `ipfs`/`arweave` field stores the bare CID/txid — identity — and the HTTPS prefix is a separate collection-wide setting. So "the gateway is slow / my Pinata plan lapsed / I bought a dedicated gateway" is **`abx set-gateway`**: one tx, moves every token, no re-upload, and it works on fields that are already **locked**. Do NOT reach for `set-field` here — it refuses the two gateway keys by name and points at `set-gateway`, because a token-scope write, a wrong representation, or a prefix missing its trailing path would each fail silently. Reserve `set-field` for the case where the CONTENT moved (a new CID, a new backend).
72
+
67
73
  - **`artifacts` and `abx_provenance` are COMPUTED** — the resolver/renderer assembles them from your fields. `set-field`/`attach` refuse them: you never set the manifest, you attach the files it lists.
68
- - **Verify** — a resolver serves the complete listing: `curl <resolver>/t/<chainId>/<addr>/<id>` shows the `artifacts` array, and `/t/<chainId>/<addr>/<id>/data/<key>` fetches (or 302-redirects to) each file. The attached bytes are stored on-chain + keccak-anchored either way.
74
+ - **Verify** — `abx tokenuri <addr> --fetch` follows the token's own URI and shows the served document, `artifacts` array included; that is the check after an attach, not a hand-built URL. (The route each file is fetched from is `/t/<chainId>/<addr>/<id>/data/<key>` on the resolver, which 302-redirects to the locator — worth knowing, not worth typing.) The attached bytes are stored on-chain + keccak-anchored either way.
69
75
  - **On-chain vs resolver reach.** The complete listing (arbitrary attached keys) is a **resolver** surface — the EVM can't enumerate an open set of FIELD keys, so the bare on-chain `tokenURI` (on-chain renderer) emits **reserved fields only**. Attaching is durable + anchored regardless, but to *surface* extra files to consumers today, the project is served by a resolver. **PostParams are the exception**: the params store enumerates its own keys on-chain (`paramSchemaKeys` · `contractParamKeys` · `tokenParamKeys`, then `tokenParam`/`contractParam`), so anyone with an RPC reads the complete configuration **directly from the contract** — no resolver, no indexer, and no metadata document in the middle. That store is the canonical surface, which is why params are *not* also projected into `tokenURI`; read them with `abx state` (schemas) and `abx tokens` (values). Say it to a creator as one line — *attachments always need a resolver; params never do.*
70
76
 
71
77
  ### Series-only owner ops
@@ -126,6 +132,26 @@ Two independent grants — *configured* (on the minter) and *assigned* (on the t
126
132
 
127
133
  > **Re-pricing a live sale fails the buys already in flight — by design.** Every purchase carries the terms it was quoted (payment token + a max price) and reverts `SaleTermsChanged` if the sale moved; that's what keeps a re-`configure` from reaching a buyer's ERC-20 allowance. So a mid-mint price change isn't silent: some in-flight buys revert and those buyers just retry (the CLI and the scaffolded mint page re-read the terms every time). Want a clean cutover? `abx pause <token>` → `minter configure …` → `abx unpause <token>`.
128
134
 
135
+ **Reserves and airdrops — the two recipes creators actually ask for.** Both are plain `mint` calls;
136
+ neither needs a minter. Do not invent a flag for them.
137
+
138
+ ```bash
139
+ abx mint <token> --to 0xALICE # airdrop: one call per recipient, N recipients ⇒ N calls
140
+ abx mint <token> --to 0xBOB
141
+ abx mint <token> --count 5 # reserve: 5 tokens to yourself (Series mints in order)
142
+ ```
143
+
144
+ The reserve decision is a **choice between two options — present both, don't pick silently**:
145
+
146
+ 1. **Pre-mint the reserve** (`abx mint --count k` before `unpause`) — the k pieces are yours, off the
147
+ market, provably held. The sale then sells whatever is left. Simplest to explain to a buyer.
148
+ 2. **Under-allocate the minter** (`--allocation <supply − k>`) — nothing is minted up front, and the
149
+ last k stay unminted until you decide. Keeps your options open; costs nothing until you use it.
150
+
151
+ They compose (pre-mint some, under-allocate the rest), and both bind against `maxInvocations` — the
152
+ tighter of allocation and supply always wins. An airdrop after the sale opens is fine too: owner
153
+ minting is not gated by `paused` (pausing blocks *minter* sales, not the owner).
154
+
129
155
  **On an edition, only the minter calls need `--token-id`** — `minter configure/show/buy --token-id <n>` (a different sale per work, on `AbxFixedPriceMinter1155`); `set-minter`/`set-primary-payee`/`pause`/`unpause` stay exactly as above, contract-wide (one minter, one payee, one pause gate for the whole id space). `minter buy --token-id <n> --quantity <n>` buys that many copies in one purchase, paying `price × quantity`.
130
156
 
131
157
  ### A mint website for buyers — `abx mint-page`
@@ -11,7 +11,7 @@ When `abx doctor` flags a tight range, or a reconstruction would be large/slow
11
11
  2. **Present clear choices with current specifics** — (a) point `ABX_RPC_URLS` at a higher-range endpoint *(recommended for anything large)*, with the exact signup/URL steps you found; or (b) stay on the current RPC and accept the wait.
12
12
  3. The CLI **stops a large job with an estimate** instead of grinding through thousands of requests; a better RPC is the real fix. To chunk through anyway, re-run with `--yes` (or raise `ABX_GETLOGS_MAX_CHUNKS`); `ABX_GETLOGS_RANGE` tunes the window.
13
13
 
14
- **One RPC var, network-scoped.** `ABX_RPC_URLS` is a comma/space-separated list of endpoints **for the active `ABX_CHAIN`** (there is no singular `ABX_RPC_URL`). The client **fails over** across them at request time — a request one endpoint rejects (a too-wide `eth_getLogs`) is retried on the next — and `abx doctor` **probes every endpoint** and reports a per-endpoint verdict (✓ wide range + archive · ⚠ usable but range-capped · ✗ unusable, with the reason, e.g. *archive refused* / *wrong network*), then names the best for reconstruction. So list a couple, let `doctor` pick, and order the list best-first. For **multiple chains**, set `ABX_RPC_URLS_<CHAIN>` (e.g. `ABX_RPC_URLS_BASE_SEPOLIA`, `ABX_RPC_URLS_SEPOLIA` — the default `ABX_CHAIN` is `base-sepolia`) — a per-chain list that overrides the bare var, never mixed, so one network's endpoints can't leak into another. The var *names* a network but the URL could point anywhere, so before any write the toolkit **verifies `eth_chainId` matches `ABX_CHAIN`** and hard-fails on a mismatch (reads stay unguarded/fast) — that's what makes "which network is this?" a checked fact, not a guess.
14
+ **One RPC var, network-scoped.** `ABX_RPC_URLS` is a comma/space-separated list of endpoints **for the active `ABX_CHAIN`** (there is no singular `ABX_RPC_URL`). The client **fails over** across them at request time — a request one endpoint **rejects** (a too-wide `eth_getLogs`) is retried on the next. Note the limit of that: failover triggers on an *error*, and an endpoint that has **pruned its log history** does not error it answers `eth_getLogs` with `[]` and HTTP 200, which is a success, so the pool keeps asking it. That is why `abx doctor` **probes every endpoint** and reports a per-endpoint verdict (✓ wide range + archive · ⚠ usable but range-capped · ✗ unusable, with the reason, e.g. *history pruned ~500000 blocks back* / *archive refused* / *wrong network*), then names the best for reconstruction — and why it warns when a **reachable** endpoint can't serve history, since ordering is what decides who answers first. So list a couple, let `doctor` pick, and order the list best-first. For **multiple chains**, set `ABX_RPC_URLS_<CHAIN>` (e.g. `ABX_RPC_URLS_BASE_SEPOLIA`, `ABX_RPC_URLS_SEPOLIA` — the default `ABX_CHAIN` is `base-sepolia`) — a per-chain list that overrides the bare var, never mixed, so one network's endpoints can't leak into another. The var *names* a network but the URL could point anywhere, so before any write the toolkit **verifies `eth_chainId` matches `ABX_CHAIN`** and hard-fails on a mismatch (reads stay unguarded/fast) — that's what makes "which network is this?" a checked fact, not a guess.
15
15
 
16
16
  **Named remotes follow the same env-name normalization**: `--remote my-provider` reads `ABX_REMOTE_MY_PROVIDER_URL` / `ABX_REMOTE_MY_PROVIDER_TOKEN` (uppercase, non-alphanumerics collapse to `_`). Inspect what's configured with `abx remote` ([hosting.md → Managed providers](hosting.md#managed-providers--a-resolver-someone-else-runs---remote-name)).
17
17
 
@@ -23,6 +23,7 @@ When `abx doctor` flags a tight range, or a reconstruction would be large/slow
23
23
  - **a write reverts with no clear reason (`--onchain-image` / staging)** → don't thrash by retrying variations. Get the actual reason: re-run the same call read-only with `cast call <addr> "<sig>" <args> --rpc-url <one of your ABX_RPC_URLS>`, which surfaces the revert string. The classic culprit is a stale chunk store (deployed before a contract upgrade) — the toolkit now detects this and auto-deploys a current store with a yellow notice, so you shouldn't hit the silent revert, but verify the resolved chunk-store/factory addresses are live (`cast code <addr>`; `abx doctor` / `abx status` print the factory) if anything looks off. **Never silently switch the representation you told the user you'd use** (e.g. fall back from `reader` to `inline`) — if the path changes, say so and why.
24
24
  - **storage 'cloud' missing accessKeyId/…** → set `ABX_S3_*` in `.env`. **'ipfs' unreachable** → Kubo not running or `PINATA_JWT` missing.
25
25
  - **re-index slow, or a "scan too large" stop** → **first check the scan floor: is it indexing from block 0 instead of the deploy block?** That's the usual cause of a "huge" scan (an `add` that lost the deploy block). The floor is `flags['from-block'] → the stored deploy block → on-chain discovery`; if none resolve, the CLI refuses rather than sweeping genesis. Only once the floor is correct does the RPC's `eth_getLogs` range matter: the toolkit chunks automatically, but a genuinely large job stops with an estimate rather than grinding. Real fix then: research a current higher-range free endpoint and set `ABX_RPC_URLS` (`abx doctor` reports your range); or re-run with `--yes` to chunk through. **archive-range getLogs refused** → use an archive-capable provider.
26
+ - **the scan finished but reconstructed 0 events** (`registered …, but with NO reconstructed state`) → for a project you JUST deployed this is transient (the logs haven't propagated; `abx index <addr> --full` in a minute). For a project that is **not new**, suspect **log retention**: several free endpoints keep only days of history and return an empty success past it, which is indistinguishable from "this contract emitted nothing". The CLI now re-runs the scan against **each configured endpoint on its own** before giving up, and names the one that served the logs — put that one FIRST in `ABX_RPC_URLS_<CHAIN>`. `abx doctor` flags a pruned endpoint up front.
26
27
  - **`serve`/resolver returns `{"error":"unknown project"}`** → the resolver you hit doesn't have that contract in *its own* projection store **yet**. Two distinct situations — diagnose which, and NEVER default to "RPC limit":
27
28
  - **A remote resolver you just registered** (`abx add … --remote`): it may still be **backfilling** — hit `GET /` (or `abx remote <name|url>`, which lists the projects the token sees) to see if it's appearing. If it's slow or stuck, the cause is almost always a **from-genesis scan (from-block=0)**, not the RPC tier — a fixed `abx add` forwards the deploy block, so re-run it and confirm the floor. (Fixed in-toolkit: a first remote add now forwards/derives the deploy block and refuses a genesis default.)
28
29
  - **A local `abx serve`**: a **store/port** problem. Usual causes, in order: (1) a **stale/duplicate `abx serve` from an old session** holds the port and serves a *different* store — hit `GET /` and see what it lists; (2) you're serving a different store directory than the deploy indexed into; (3) the contract was never registered there. Fix the server/port/registration.