@artblocks/abx-cli 0.1.0-alpha.25 → 0.1.0-alpha.27

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 (47) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/dist/commands/deploy.d.ts +12 -4
  3. package/dist/commands/deploy.d.ts.map +1 -1
  4. package/dist/commands/deploy.js +99 -24
  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 +48 -2
  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 +36 -9
  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/main.js +24 -8
  27. package/dist/main.js.map +1 -1
  28. package/dist/ownerops.d.ts +19 -1
  29. package/dist/ownerops.d.ts.map +1 -1
  30. package/dist/ownerops.js +33 -15
  31. package/dist/ownerops.js.map +1 -1
  32. package/dist/provision.d.ts.map +1 -1
  33. package/dist/provision.js +6 -1
  34. package/dist/provision.js.map +1 -1
  35. package/dist/remote.d.ts +20 -0
  36. package/dist/remote.d.ts.map +1 -1
  37. package/dist/remote.js +38 -0
  38. package/dist/remote.js.map +1 -1
  39. package/dist/signer.d.ts.map +1 -1
  40. package/dist/signer.js +4 -1
  41. package/dist/signer.js.map +1 -1
  42. package/package.json +6 -6
  43. package/skill/SKILL.md +272 -171
  44. package/skill/reference/decisions.md +45 -4
  45. package/skill/reference/hosting.md +12 -3
  46. package/skill/reference/operating.md +55 -2
  47. package/skill/reference/setup.md +42 -1
@@ -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).
@@ -129,5 +129,46 @@ creator (and to their buyers) is "the metadata is frozen"; only a project whose
129
129
  that adapts to live chain state is a real thing to build — but it has to be said out loud either way.
130
130
  Background: https://abx.docs.artblocks.io/protocol/owner-powers/
131
131
 
132
- **6. When to mint** → [Deploy strategy](#deploy-strategy--when-to-mint).
133
-
132
+ **6. When to mint** → [Deploy strategy](../SKILL.md#deploy-strategy--when-to-mint).
133
+
134
+ ## Locks — what actually freezes, and what a buyer is really asking
135
+
136
+ Every lock in this toolkit freezes a **pointer or a stored value**, never *behavior*. Say "locked
137
+ metadata", never "immutable work" — the difference is the whole of this section, and a creator who
138
+ promises the second while shipping the first has mis-sold their collection.
139
+
140
+ **An *ungoverned* PostParam has no lock at all.** The owner's raw setter keeps writing it past every
141
+ metadata lock, and the generator keeps injecting the new value as `tokenData`. A *schema'd* param can
142
+ be welded permanently (`set-schema … :lock=now`, `abx retire-param`), which freezes that key's
143
+ **token-scope value** and its **schema** forever. So "no lock" is a statement about the ungoverned
144
+ store, never about a governed key.
145
+
146
+ **The documented exception to a weld: a contract-scope DEFAULT can still be deleted.**
147
+ `clearContractParam` deliberately sits outside the schema guard — it is the recovery path out of a
148
+ value poisoned before the schema existed. So an owner can set a collection-wide default, attach and
149
+ expire a schema on that key, sell tokens that inherit the default, and *then* clear it; every token
150
+ with no override of its own changes. Clearing can only *remove* a fallback — never forge a value,
151
+ never bypass an auth rule, never touch a token-scope value already written. **If a creator is
152
+ promising a frozen collection-wide value, write it per token** (governed, welded) rather than leaving
153
+ it as an inherited default.
154
+
155
+ **A `Registry` dependency's bytes are re-fetched on every read.** `lock-dependencies` pins which
156
+ library a ref *means*, not what it returns. `abx verify`'s `chain-complete` is a claim about where
157
+ bytes come from, not that they are frozen.
158
+
159
+ **The one lock a BUYER asks about is `lock-param-hooks`.** A code project's `--transfer` hook is a
160
+ **veto** — its revert fails a transfer, and mints too — so until the hooks are frozen the owner holds
161
+ a standing power over whether a collector can ever sell. Freezing an empty set is how a creator proves
162
+ they never will.
163
+
164
+ **Every lock freezes a POINTER, not behavior.** A locked hook, renderer or reader is still a
165
+ *contract*, and a contract can be a proxy whose code is upgraded later: `paramHooksLocked()` keeps
166
+ reading `true` while a re-pointed proxy starts reverting every transfer. The protocol does not try to
167
+ detect proxies on chain (complex, incomplete) — it defines locks as pointer locks and discloses it. So
168
+ **for any permanence claim, the hook/renderer must itself be an immutable deployment**: say "these
169
+ exact three addresses can never change" and check what is behind them, never "the behavior is frozen".
170
+
171
+ A token that live-adapts to chain state is a legitimate thing to build. Just don't sell a frozen one as
172
+ frozen unless it is. See also
173
+ [code-projects.md → hooks](code-projects.md#live-data-the-augment-hook--the-hook-is-the-setting) and
174
+ https://abx.docs.artblocks.io/protocol/owner-powers/
@@ -13,7 +13,16 @@ 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 an HTTPS gateway 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.
17
26
 
18
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.
19
28
 
@@ -30,7 +39,7 @@ All backends verify via the on-chain keccak commitment wherever the bytes live.
30
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:
31
40
 
32
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:
33
- - **`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).
34
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`).
35
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.)*
36
45
  - **Under 100 KB → free**, permanent, zero setup. The CLI prints this from the file size (in `--dry-run` too).
@@ -73,7 +82,7 @@ The other way to have a resolver: be a **customer** of a hosted provider instead
73
82
 
74
83
  ## Self-hosted resolver — `abx deploy-resolver`
75
84
 
76
- 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.
77
86
 
78
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.)
79
88
 
@@ -41,7 +41,7 @@ After launch the owner operates the project. Each command builds a tx, signs it
41
41
 
42
42
  **Even with every lock engaged, don't tell a creator the OUTPUT is immutable.** A lock freezes what this contract *stores*, and the metadata locks reach fields and the URI config — not the param store. **An *ungoverned* param has no lock**, and the renderer serves it into `tokenURI` — so an owner can still change the served metadata and the render. (A *schema'd* param is different: it can be welded permanently — see "Welding a PARAM" below — but a welded value can still be *overridden at read time* by an `--augment` hook until `lock-param-hooks` freezes the hook set.) And a `name@version` **`--dep` resolves live from the dependency registry** on every read: `lock-dependencies` pins which library the ref means, not the bytes the registry hands back. `abx verify`'s `chain-complete` is about *where* bytes come from, not whether they change. Say **"the metadata is locked"**; the stronger claim needs on-chain `--dep 0x…` refs plus locked fields. **And a lock a buyer will ask about that isn't in the metadata set at all: `lock-param-hooks`** — a code project's transfer hook is a *veto* (its revert fails a transfer, and a mint), so an unfrozen hook set is a standing power over whether a collector can sell ([hooks](code-projects.md#live-data-the-augment-hook--the-hook-is-the-setting)). Read the other way it's a capability — a piece that adapts to live chain state is a real design, not a defect. Full disclosure list (what an owner can and cannot do, and the read for each) → https://abx.docs.artblocks.io/protocol/owner-powers/
43
43
 
44
- **Welding a PARAM is a different verb — and it IS a real weld.** Fields and [PostParams](code-projects.md#postparams) are separate namespaces that can share a name, and `lock-field` freezes the *field*. To make a param permanent, lock its schema: `abx set-schema <addr> --schema <key>:<Type>:<Auth>:lock=now` (or `abx retire-param <addr> <key>`, the one-arg form). Once that deadline is past, three things hold **forever**, all enforced on-chain:
44
+ **Welding a PARAM is a different verb — and it IS a real weld.** Fields and [PostParams](code-projects.md#postparam-schema--the-type--auth-catalog) are separate namespaces that can share a name, and `lock-field` freezes the *field*. To make a param permanent, lock its schema: `abx set-schema <addr> --schema <key>:<Type>:<Auth>:lock=now` (or `abx retire-param <addr> <key>`, the one-arg form). Once that deadline is past, three things hold **forever**, all enforced on-chain:
45
45
 
46
46
  - every `configure-param` on the key reverts `ParamLockExpired`;
47
47
  - **the SCHEMA freezes too** — `set-schema` on the key also reverts `ParamLockExpired`, so its Type, Auth, bounds and `Select` option table can never be edited. (That matters concretely: without it, a locked `Select` param's options could be swapped afterwards and a collector's "Ember" would re-render as "Frost" with no param write at all.)
@@ -70,8 +70,12 @@ A token anchors **named, typed files**; the served JSON's **`artifacts`** array
70
70
  - scope: `--token <id>` (default 0) or `--collection`; any lane; `--dry-run` previews the tx.
71
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
72
 
73
+ - **The complete listing is a resolver surface; PostParams are not.** 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 cannot enumerate arbitrary FIELD keys — so a project that must surface extra files to consumers today runs a resolver (attached files are stored + keccak-anchored regardless). The params store, by contrast, enumerates its own keys on chain, so any RPC reads a token's whole configuration straight from the contract. One line for a creator: *attachments always need a resolver; params never do.*
74
+ - **Effect outputs are artifacts too.** A code project's runner publishes `render/image`, `render/traits`, and any extra declared output into the same manifest automatically, at the current settled state — files appear as tokens mint and params change.
75
+ - **Set expectations honestly, up front.** No mainstream marketplace (OpenSea/Blur) shows a "files" tab **today** — they render `image`/`animation_url` only. Attached files are a durable, anchored part of the token *now*, read by data-plane-aware tools and any resolver; broad marketplace display is future adoption. So verify an attach with `abx tokenuri <addr> --fetch`, never by refreshing OpenSea.
76
+ - **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.
73
77
  - **`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.
74
- - **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.
78
+ - **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.
75
79
  - **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.*
76
80
 
77
81
  ### Series-only owner ops
@@ -132,6 +136,26 @@ Two independent grants — *configured* (on the minter) and *assigned* (on the t
132
136
 
133
137
  > **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>`.
134
138
 
139
+ **Reserves and airdrops — the two recipes creators actually ask for.** Both are plain `mint` calls;
140
+ neither needs a minter. Do not invent a flag for them.
141
+
142
+ ```bash
143
+ abx mint <token> --to 0xALICE # airdrop: one call per recipient, N recipients ⇒ N calls
144
+ abx mint <token> --to 0xBOB
145
+ abx mint <token> --count 5 # reserve: 5 tokens to yourself (Series mints in order)
146
+ ```
147
+
148
+ The reserve decision is a **choice between two options — present both, don't pick silently**:
149
+
150
+ 1. **Pre-mint the reserve** (`abx mint --count k` before `unpause`) — the k pieces are yours, off the
151
+ market, provably held. The sale then sells whatever is left. Simplest to explain to a buyer.
152
+ 2. **Under-allocate the minter** (`--allocation <supply − k>`) — nothing is minted up front, and the
153
+ last k stay unminted until you decide. Keeps your options open; costs nothing until you use it.
154
+
155
+ They compose (pre-mint some, under-allocate the rest), and both bind against `maxInvocations` — the
156
+ tighter of allocation and supply always wins. An airdrop after the sale opens is fine too: owner
157
+ minting is not gated by `paused` (pausing blocks *minter* sales, not the owner).
158
+
135
159
  **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`.
136
160
 
137
161
  ### A mint website for buyers — `abx mint-page`
@@ -164,3 +188,32 @@ New resolver now serves byte-identical metadata, but traffic still hits the old
164
188
  ### Source-only ("node-custody") images — `migrate` handles these automatically
165
189
 
166
190
  An image committed by hash but never pinned to a durable backend serves from the source's own `/image` route — no portable URL, so copying it points the new host back at the dying old one. `migrate` instead fetches the bytes, re-hashes them against the on-chain commitment (trustless), re-pins them to **your** durable backend, and bridges the new locator. The one case it can't fix: a migrating machine with no durable backend (only `fs`/`cloud`) — it bridges nothing source-bound and tells you to re-run with `--backend ipfs|arweave`. So configure a durable backend before migrating a project that used local-disk custody.
191
+
192
+ ## Listing in the ABX App Store — `abx submit-app`
193
+
194
+ **Not part of deploy.** After the collection is live, ask: *want this listed in the ABX App Store?* If no, stop. If yes, this is a second, explicit opt-in — minting a token in the store registry — not marketplace discovery.
195
+
196
+ 1. **Draft store copy with the creator.** `--name` / `--summary` / `--description` here are what someone can *do*, not the collection's NFT metadata. Do not silently copy deploy `--name` / `--description`. Confirm category, stage, launch URL, and icon mark/tone.
197
+ 2. **You run the command** (same signing lane as the deploy). `--dry-run` first, then send:
198
+
199
+ ```bash
200
+ abx submit-app <collection> \
201
+ --name "…" --summary "…" --description "…" \
202
+ --category Create --stage Prototype \
203
+ --url https://… \
204
+ --sign
205
+ ```
206
+
207
+ 3. They sign once to mint the listing, then a few times to write metadata (the chain caps how much fits in one tx). If mint already succeeded, re-running skips the mint and only writes params.
208
+ 4. Humans can instead use the store's `/submit` page; same txs. Don't hand them a list of `configure-param` commands.
209
+
210
+ The wallet must **own the collection** at mint (the gate checks `isAbxClone` + `owner()`). After that, control follows the **entry token**.
211
+
212
+ ## Deploy strategy — the mechanics
213
+
214
+ - **`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.
215
+ - **`--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.
216
+ - **`abx refresh <addr>`** asks marketplaces to re-index. **Both lanes emit ERC-4906** on metadata/URI changes so 4906-aware marketplaces self-refresh, and `refresh` is the fallback (+ genesis mint). **An edition additionally has ERC-1155's native `URI` event, which is NOT emitted on a contract-wide re-point** (no range form exists) — so after `set-token-uri`/`set-renderer` on an edition also run `abx ping-uri <addr> --token-ids <ids>` for consumers that honor only that event. With `OPENSEA_API_KEY` it calls OpenSea directly; else it prints the links to click.
217
+ - **There is no "add it to a marketplace" step — don't offer one.** Marketplaces discover the collection from chain (the mint, plus ERC-4906 on either lane); `abx refresh` — and `abx ping-uri` on an edition — is the only nudge. Suggesting a manual listing implies work that doesn't exist. **The ABX App Store is different** — that registry is an explicit opt-in (`abx submit-app`), not marketplace discovery. Offer it after deploy; never fold it into deploy. See [List in the App Store](../SKILL.md#list-in-the-abx-app-store-optional).
218
+ - A `--no-mint` deploy reconstructs + serves normally (dashboard shows #0 "not yet minted"; metadata/image already resolve).
219
+ - **Etherscan source is already verified** — your deploys are EIP-1167 clones Etherscan auto-recognizes as proxies of the verified implementation.
@@ -1,7 +1,48 @@
1
- # Environment detail — RPC & troubleshooting
1
+ # Setupinstall `abx`, configure `.env`, then RPC & troubleshooting
2
2
 
3
3
  [← back to SKILL.md](../SKILL.md)
4
4
 
5
+ **First time? Start at [Finding, installing and configuring `abx`](#finding-installing-and-configuring-abx)** —
6
+ that section is the required setup. The RPC and troubleshooting material below it is reference you reach
7
+ for when indexing is slow or a resolver will not serve, not something a first launch needs.
8
+
9
+ ## Finding, installing and configuring `abx`
10
+
11
+ `abx` needs **Node ≥ 22.5** at runtime (the projection store uses built-in SQLite).
12
+
13
+ ### Find `abx` before you install it — local first, then global
14
+
15
+ **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:
16
+
17
+ ```bash
18
+ ./node_modules/.bin/abx version # 1. project-local — PREFER this (invoke as `npx abx <cmd>` once you know it's there)
19
+ abx version # 2. a global install already on PATH
20
+ ```
21
+
22
+ - **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.
23
+ - In the **abx source repo** (contributor), neither applies: use `pnpm abx <cmd>`, which runs the live `tsx` source.
24
+
25
+ **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):
26
+
27
+ | Situation | Install | Then invoke as |
28
+ |---|---|---|
29
+ | The creator has a project dir (a `package.json`) — **default** | `npm install --save-dev @artblocks/abx-cli` | `npx abx …` |
30
+ | No project, or they asked for a machine-wide tool | `npm install -g @artblocks/abx-cli` | `abx …` |
31
+
32
+ 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.
33
+
34
+ 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).
35
+
36
+ `.env` (in the creator's project dir) = **secrets only**:
37
+ - **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.
38
+ - `ABX_RPC_URLS`, `ABX_PUBLIC_BASE_URL` (remote-resolver custody only), optional `OPENSEA_API_KEY`, plus any backend secret.
39
+ - **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).
40
+
41
+ <sub>Working from the abx **source repo** (contributor)? `pnpm install`, then `pnpm abx <cmd>` or `pnpm sandbox`. Every command below is identical.</sub>
42
+
43
+ **Indexing reads the event log via `eth_getLogs` from the contract's deploy block**, which the CLI records at deploy and forwards to a resolver on `add` — so a normal deploy→index scans a small recent window, is fast on any RPC, and re-indexes incrementally. **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 blaming the RPC tier** — that mis-diagnosis is a known trap. Range caps, pruned log history, and which endpoint to give a resolver under load are covered below.
44
+
45
+
5
46
  ## Choosing an RPC (drives re-index speed)
6
47
 
7
48
  Reconstruction 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`, and discovers it on-chain (a getCode binary search) for a contract it didn't deploy here. So a normal deploy→index scans a *small, recent* window and is fast on **any** RPC. Providers cap the getLogs block range very differently **and these caps change** — treat any number as illustrative, re-verify before you quote it. *(Illustrative, Jun 2026: Alchemy free ≈10 blocks/call, Infura/Chainstack free ≈2000, PublicNode/dRPC vary; no-key public endpoints are the lowest-friction to try.)* The toolkit **auto-chunks**, so any RPC yields correct state — but a range cap is **not** cost-free to shrug off: a **from-genesis** or long-span reconstruction, or a resolver serving many code projects under load, is slow and rate-limit-prone on a capped endpoint. `abx doctor` rates each endpoint and flags a capped one; treat that flag as a real infra signal, not noise. **First diagnosis when indexing is slow or a hosted resolver won't serve: is it scanning from block 0? Check the scan floor before the RPC tier.**