@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Royalty enforcement — opt-in creator token (ERC-721C / ERC-1155C)
|
|
2
|
+
|
|
3
|
+
[← back to SKILL.md](../SKILL.md)
|
|
4
|
+
|
|
5
|
+
**Read this ONLY because the creator brought it up** (royalty enforcement, "make OpenSea pay my
|
|
6
|
+
royalties", ERC-721C/ERC-1155C, transfer restrictions, a transfer validator). If they didn't, this file
|
|
7
|
+
has no business in the conversation: **plain ERC-721 (or plain ERC-1155, for an edition) is the default
|
|
8
|
+
and the recommendation**, every deploy flow already does the right thing with zero `--721c` flags, and
|
|
9
|
+
offering enforcement unprompted reads as a nudge toward a restricted asset. Never present `--721c` as a
|
|
10
|
+
checklist item, a "should we also…", or a row in the deploy readout for a creator who never asked.
|
|
11
|
+
|
|
12
|
+
**Same flag, both standards.** `--721c` is the one flag for creator-token enrollment everywhere — on a
|
|
13
|
+
721 deploy (`deploy`/`deploy-series`/`deploy-code` with no `--copies`) it enrolls **ERC-721C**; on the
|
|
14
|
+
same commands with `--copies` (an edition) it enrolls **ERC-1155C** instead. Same validator registry,
|
|
15
|
+
same recommended validator, same permanence rule, same UX — only the ERC-165 discovery ids and the
|
|
16
|
+
per-transfer call differ, both invisible to a creator (below).
|
|
17
|
+
|
|
18
|
+
## The stance (how to frame it when asked)
|
|
19
|
+
|
|
20
|
+
- **Plain ERC-721 — or plain ERC-1155 for an edition — is the more sovereign asset (the default)**:
|
|
21
|
+
no third-party contract on the transfer path, tradeable everywhere, nothing anyone — creator
|
|
22
|
+
included — can later switch off. ABX royalties are already on-chain via ERC-2981
|
|
23
|
+
(`abx set-royalty`); marketplaces *choose* whether to honor them.
|
|
24
|
+
- **721C/1155C is enforcement for creators who specifically want it**: transfers are checked by a
|
|
25
|
+
validator contract, and sales that don't route through an authorized venue revert. It is a real
|
|
26
|
+
trade-off, decided at deploy, **permanently** — present it transparently, recommend it only when
|
|
27
|
+
the creator's stated goal is enforcement, and get an explicit yes before enrolling.
|
|
28
|
+
- **The honest market picture (2026) — say it, don't oversell**: enforcement via 721C effectively
|
|
29
|
+
means **OpenSea** plus venues on Limit Break's Payment Processor. OpenSea's own help docs say
|
|
30
|
+
enforcement limits sales to those venues; Magic Eden's EVM marketplace shut down March 2026, and
|
|
31
|
+
Blur never honored 721C. The trade: enforced royalties on OpenSea, in exchange for not trading on
|
|
32
|
+
venues the validator doesn't authorize.
|
|
33
|
+
|
|
34
|
+
## What enrolling does (the facts to relay)
|
|
35
|
+
|
|
36
|
+
- **Deploy-time, permanent, both directions.** A plain ABX token (721 or 1155) can **never** grow
|
|
37
|
+
transfer restrictions later (that's a feature — collectors of unenrolled tokens keep that promise
|
|
38
|
+
forever), and an enrolled one never sheds the standard. No retrofit exists; the only path to
|
|
39
|
+
enrollment for a live plain collection is a redeploy.
|
|
40
|
+
- **Suspend ≠ un-enroll.** The owner can re-point the validator or set it to zero (enforcement off,
|
|
41
|
+
token **stays** enrolled — the owner can re-enable any time). Collectors can see enrollment via
|
|
42
|
+
ERC-165, suspended or not.
|
|
43
|
+
- **Mints and burns are NEVER validated** — no validator policy can brick minting, the shared
|
|
44
|
+
fixed-price minter (721 or edition), or a delegated minter.
|
|
45
|
+
- **The recommended validator** (`--721c recommended`, or bare `--721c`) is OpenSea's
|
|
46
|
+
StrictAuthorizedTransferSecurityRegistry — the same registry, same address, for both standards.
|
|
47
|
+
Under it, owner-initiated wallet-to-wallet transfers pass; non-authorized operators revert. One
|
|
48
|
+
decision, done.
|
|
49
|
+
- **On an edition, validation is per `(id, amount)`, not per token.** ERC-1155C's validator call
|
|
50
|
+
carries the transferred amount and isn't a view (unlike 721C's), and a batch transfer loops one
|
|
51
|
+
validator call per id — invisible to a creator, but the reason a large batch transfer on an
|
|
52
|
+
enrolled edition costs more gas than an unenrolled one.
|
|
53
|
+
|
|
54
|
+
## Commands
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Enroll at deploy — all three deploy commands take the flag (absent = plain, forever):
|
|
58
|
+
abx deploy|deploy-series|deploy-code … --721c [recommended|0x<validator>]
|
|
59
|
+
# On an edition deploy (--copies), the SAME flag enrolls ERC-1155C instead — nothing else changes.
|
|
60
|
+
abx deploy --copies <n|open> … --721c [recommended|0x<validator>]
|
|
61
|
+
|
|
62
|
+
# Operate an ENROLLED collection (owner-only, any signing lane, --dry-run works):
|
|
63
|
+
abx set-transfer-validator <addr> 0x<validator> # re-point enforcement
|
|
64
|
+
abx set-transfer-validator <addr> none # suspend (stays enrolled)
|
|
65
|
+
abx set-transfer-validator <addr> recommended # the chain's recommended validator
|
|
66
|
+
|
|
67
|
+
abx state <addr> # shows a "721C validator" row ONLY when enrolled ("suspended" when zero);
|
|
68
|
+
# an enrolled EDITION shows the same row labeled "1155C validator" — same
|
|
69
|
+
# ERC-165 id + getter on both standards, so detection is identical
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Enrolling prints one plain statement of what enforcement means — relay it, don't embellish. An
|
|
73
|
+
un-enrolled deploy prints nothing about 721C/1155C.
|
|
74
|
+
|
|
75
|
+
## Refusals the CLI enforces (don't fight them; relay the reason)
|
|
76
|
+
|
|
77
|
+
- **`--721c none`/zero at deploy** — refused: a zero validator never enrolls, and "enrolled but
|
|
78
|
+
suspended" is not a deploy-time state. Plain (721 or 1155) is already the default; drop the flag.
|
|
79
|
+
- **`--721c recommended` on a chain with no known validator** — refused, naming the chains that have
|
|
80
|
+
one. Pass an explicit deployed validator address instead; never guess one.
|
|
81
|
+
- **A codeless validator address** — refused before any gas at deploy AND at re-point (the contract
|
|
82
|
+
would revert `InvalidTransferValidator()`); a mixed-case address must pass its EIP-55 checksum.
|
|
83
|
+
A real deploy also refuses to enroll blind when the RPC can't verify code (a dry-run defers it).
|
|
84
|
+
- **`set-transfer-validator` on a plain, unenrolled collection** — refused up front (721 or 1155):
|
|
85
|
+
enrollment is a deploy-time decision and this collection didn't enroll. Do not retry with different
|
|
86
|
+
flags; if the creator truly needs enforcement, that's a redeploy conversation.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Decisions — configuring a real launch
|
|
2
|
+
|
|
3
|
+
[← back to SKILL.md](../SKILL.md#decisions-real-launch)
|
|
4
|
+
|
|
5
|
+
The six decisions a real launch actually turns on. SKILL.md carries the router and the rules that
|
|
6
|
+
prevent an irreversible mistake; this is the depth — read it once you are configuring a real deploy,
|
|
7
|
+
not while you are still working out what the creator wants.
|
|
8
|
+
|
|
9
|
+
Master call is **custody × mutability**:
|
|
10
|
+
|
|
11
|
+
| | **Mutable** (name/desc/traits may change) | **Immutable** (never changes) |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| **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. |
|
|
14
|
+
| **Bigger / dynamic** (most PNG/JPEG) | **image off-chain, JSON on-chain, no server** — `--onchain-uri --backend arweave` (or `ipfs`). Renderer assembles JSON pointing at the bytes; many files → one `url-template` (O(1)). For metadata you edit often, a **resolver** instead — a managed provider or your own (`abx deploy-resolver`), [hosting.md](reference/hosting.md). Not fully on-chain (~200 gas/byte). | image off-chain (Arweave = permanent) + on-chain renderer + `lock-field`/`lock-uri`. Or a frozen `ipfs://` override + `lock-uri`. |
|
|
15
|
+
|
|
16
|
+
**Four patterns, by where bytes live × how `tokenURI` resolves:**
|
|
17
|
+
1. **Fully on-chain** (`--onchain-image`) — bytes + JSON on-chain. Tiny art only.
|
|
18
|
+
2. **Image off-chain, JSON on-chain, no server** (`--onchain-uri --backend arweave|ipfs|cloud`) — the sweet spot for static art. Arweave/IPFS (permanent, content-addressed) or your S3/CDN (`--backend cloud --public-base <url>`; centralized, mutable). Many files → one `url-template`.
|
|
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 art inlined into `tokenURI`. For a **1/1** that's `abx deploy … --onchain-uri`; for a **Series** of tiny SVGs use `abx deploy-series … --onchain-image --compress fastlz` (bare `--onchain-uri` on a folder does NOT inline the images — it's the image-custody flag `--onchain-image` that puts SVG bytes on-chain per token).
|
|
21
|
+
|
|
22
|
+
**All three deploy commands can now do pattern 2.** `deploy` (1/1), `deploy-series` and `deploy-code`
|
|
23
|
+
each upload to the chosen backend under `--onchain-uri` and bake the resulting public URL into the
|
|
24
|
+
on-chain JSON. (Until 2026-08-03 the 1/1 silently could not — it committed a hash and rendered a
|
|
25
|
+
placeholder. If you remember that limitation, it is gone.)
|
|
26
|
+
|
|
27
|
+
**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 **resolver** (pattern 3 — managed or self-hosted) 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.)
|
|
28
|
+
|
|
29
|
+
**No-server tradeoff (patterns 1, 2, 4):** with the on-chain renderer only the *image* is off-chain — any **description / traits / animation_url, and every configured PostParam (served as `abx_params`), 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.
|
|
30
|
+
|
|
31
|
+
Get decisions 1–2 right before deploy (image commitment + resolver URL are written then; re-pointable, but):
|
|
32
|
+
|
|
33
|
+
**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.
|
|
34
|
+
- `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.
|
|
35
|
+
- **`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 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.
|
|
36
|
+
- **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).
|
|
37
|
+
|
|
38
|
+
**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.
|
|
39
|
+
- **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.
|
|
40
|
+
- **A named remote is already configured (`ABX_REMOTE_<NAME>_URL` in `.env`)? Use it.** The creator already chose a provider — don't stand up new infrastructure beside it. **Run `abx remote <name>` FIRST, before registering anything**: it prints the provider's chain coverage + whether rendering is managed, and it *validates the key* (`401` = the token in `ABX_REMOTE_<NAME>_TOKEN` is stale/wrong → they replace the value in `.env`; `403` = the key is fine but not authorized for this contract/chain → provider-side scoping, don't touch the key). Then register: `abx add <addr> --remote <name>`. Testing a replacement key without editing `.env` first: `abx remote <name> --remote-token <new-key>`.
|
|
41
|
+
- **Otherwise, two equal ways to have a resolver, one config change apart.** A **managed provider** — one base URL + one API key, no cloud account, nothing to keep alive; often **managed rendering** too, so a code drop needs no effects runner (**lead with this when the creator doesn't already run infrastructure or doesn't want to** — [hosting.md → Managed providers](reference/hosting.md#managed-providers--a-resolver-someone-else-runs---remote-name)). Or **self-host** (`abx deploy-resolver`, [hosting.md](reference/hosting.md)) — the creator owns the node and the cloud account. Same interface, same commands; a project moves between them with one re-point + re-register. **No provider key in hand and none to get? Self-host is the fully-supported path today** — the provider market is only starting to form; never invent or recommend a provider that isn't in front of you.
|
|
42
|
+
- **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), or behind a provider.
|
|
43
|
+
|
|
44
|
+
**3. Identity** — `--name`, `--symbol`, `--royalty-bps` (default 500 = 5%), `--description "…"`, `--external-url <url>` (both served in the metadata — set them or the description is boilerplate). Owner + royalty receiver = the deploying wallet. These default to off-chain operator metadata (editable via `abx add <addr> --description "…"`). For a description that should outlast any node, add `--description-onchain` (or later `abx set-field <addr> --field description --text "…"`) → on-chain, freezable via `lock-field`; the resolver prefers the on-chain value. This is the per-field on-chain model — any field on-chain or off, one active `representation` (inline · reader · keccak256 · arweave · ipfs · url). Background: [metadata model](https://abx.docs.artblocks.io/protocol/metadata/).
|
|
45
|
+
- **Credit + license** — deploy flags `--artist "…"` · `--license "…"` (also `--display-notes`, `--artist-links`) bake authorship + rights ON-CHAIN in the deploy tx (all three deploy commands); or set/change them later with `abx set-field <addr> --collection --field artist|license --text "…"`. Reserved collection fields served in `contractURI`, on any type (1/1 · Series · code). Detail: [operating.md → Authorship + rights](reference/operating.md#authorship--rights-credit--license).
|
|
46
|
+
- **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.
|
|
47
|
+
|
|
48
|
+
**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.
|
|
49
|
+
- *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.
|
|
50
|
+
- *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]`.
|
|
51
|
+
|
|
52
|
+
**Inline vs reader — default to the reader for real artwork.** `--onchain-uri` alone inlines the SVG (1 tx, ~700 gas/byte); `--onchain-image --compress fastlz` stages via SSTORE2 + a small `reader` pointer (~200 gas/byte, +1 tx) — **cheaper above ~0.5 KB** and widening with size. So: tiny (<~0.5 KB, a one-line SVG/short text) → `--onchain-uri` inline; real artwork (a few KB+) → `--onchain-image --compress fastlz`. **Never `--compress gzip` for an on-chain-rendered token** — gzip decodes off-chain only, breaking `--onchain-uri`; use fastlz (it decodes *in* the reader).
|
|
53
|
+
|
|
54
|
+
**5. On-chain vs off-chain resolution** — by default `tokenURI`/`contractURI` point at your resolver. `--onchain-uri` = JSON assembled *on-chain* by the shared `AbxMetadataRenderer`, self-resolving forever — so it pairs with on-chain content, cost-effective only for tiny art (thresholds above).
|
|
55
|
+
- **Fully on-chain = no server.** Don't stand one up; never cite a localhost URL. **Prove it with `abx tokenuri <addr>`** (reads `tokenURI(0)` over RPC, no `serve`). `abx serve` is only for off-chain-resolving tokens.
|
|
56
|
+
- **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.
|
|
57
|
+
- **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`).
|
|
58
|
+
- **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.
|
|
59
|
+
|
|
60
|
+
**6. When to mint** → [Deploy strategy](#deploy-strategy--when-to-mint).
|
|
61
|
+
|
|
@@ -29,7 +29,7 @@ All backends verify via the on-chain keccak commitment wherever the bytes live.
|
|
|
29
29
|
|
|
30
30
|
- **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
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).
|
|
32
|
-
- **`--storage-signer eth`** — reuse the **`.env` EVM signing key** as the Turbo identity
|
|
32
|
+
- **`--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
33
|
- **`--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
34
|
- **Under 100 KB → free**, permanent, zero setup. The CLI prints this from the file size (in `--dry-run` too).
|
|
35
35
|
- **Over 100 KB → prepaid credits, one-time.** `abx storage balance` shows the funded address + credits; `abx storage topup --usd <n>` returns a Stripe checkout link. A **pre-upload balance guard** stops *before* the deploy if credits are short — printing the address + fund options — so a shortfall never fails mid-deploy after txs already landed. One-time payment, no recurring fee, nothing to re-pin (contrast `cloud`/`ipfs`). Credits are non-refundable.
|
|
@@ -54,19 +54,30 @@ A failed upload is the #1 place an agent goes off the rails: it invents a cause
|
|
|
54
54
|
|
|
55
55
|
- **Uniform extension → O(1) directory.** Files are renamed to the token-id convention (`0.png`, `1.png`, …), uploaded as one IPFS directory pin (Pinata) / Arweave path manifest (Turbo), and a single collection-scope `image` field gets the **`url-template`** representation — `<gatewayBase>/{id}.png`. The renderer substitutes each id → one field renders the whole collection, any size. No per-token on-chain data.
|
|
56
56
|
- **Mixed extensions → per-token `url`** fields (O(N)) — still no server. A uniform extension unlocks the single-template path; flag it if a folder is mixed.
|
|
57
|
-
- **`cloud` (S3/R2/CDN)** works the same but
|
|
57
|
+
- **`cloud` (S3/R2/CDN)** works the same, but the signed-API host (`--endpoint`/`ABX_S3_ENDPOINT`, where uploads go) and the public read host (`--public-base`/`ABX_S3_PUBLIC_BASE`, also what `deploy-code --image-base` takes) are **different hosts by design** — one auth-only, one public (R2's own API endpoint is never public; its public URL is a separate `pub-<hash>.r2.dev` or custom domain you enable). **`abx storage show --check` proves the two agree** — a real PUT through the API + an unsigned GET through the public base (`abx doctor` runs the same probe). Files upload under a content-derived prefix → `<publicBase>/<prefix>/{id}.png`. **Caveat:** S3 is centralized, mutable, not content-addressed (no CID/txid root). Good if the creator already runs a bucket/CDN; IPFS/Arweave are the trustless/permanent options.
|
|
58
58
|
- **Integrity** (IPFS/Arweave) comes from the content-addressed root (CID / manifest txid), not a per-token keccak. The gateway host is baked on-chain → moving gateways is a `set-field` (bytes stay put). Prefer a **dedicated** gateway.
|
|
59
59
|
- **`url-template`** is a first-class representation ([spec](../../../../specs/protocol/onchain-metadata.md)); set by hand with `abx set-field <addr> --collection --field image --representation url-template --text "<gateway>/ipfs/<cid>/{id}.png"` then `abx set-renderer <addr>`.
|
|
60
60
|
|
|
61
|
-
##
|
|
61
|
+
## Managed providers — a resolver someone else runs (`--remote <name>`)
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
The other way to have a resolver: be a **customer** of a hosted provider instead of running a node — one base URL + one API key, no cloud account, no Dockerfile, nothing to keep alive. Same interface, same commands as self-hosting ([spec](../../../../specs/self-host-toolkit/remote-services.md)); a project moves between a provider and your own node with one re-point + re-register ([operating.md → Moving your hosting](operating.md#moving-your-hosting--two-cases-dont-conflate)).
|
|
64
|
+
|
|
65
|
+
- **Named remotes.** Put the provider in `.env`: `ABX_REMOTE_<NAME>_URL=<base>` + `ABX_REMOTE_<NAME>_TOKEN=<api key>`. **`<NAME>` is YOUR label for the provider, not their legal name** — you pick it, you pass the same word to `--remote`. Normalization matches `ABX_RPC_URLS_<CHAIN>`: uppercase, runs of non-alphanumerics → `_`. So `--remote my-provider` reads `ABX_REMOTE_MY_PROVIDER_*`. For a multi-word brand, pick ONE short word and use it consistently — "Meridian Metadata" → `--remote meridian` + `ABX_REMOTE_MERIDIAN_URL`/`_TOKEN` (using `meridian metadata` would work too, as `ABX_REMOTE_MERIDIAN_METADATA_*`, but short is kinder to type). Then every remote command takes the name: `abx add <addr> --remote <name>`, ditto `index` / `forget` / `render` / `verify`, `abx migrate --from/--to <name>`, and owner-op nudges. Ad-hoc: `--remote <url> --remote-token <key>`. **Bare `--remote` stays the self-host default** (`ABX_PUBLIC_BASE_URL` + `ABX_REMOTE_SELF_TOKEN` — "self" is just another named remote) — nothing changes for a node you deployed. A named remote never falls back to `ABX_REMOTE_SELF_TOKEN` (or the resolver's own server-side `ABX_RESOLVER_ADMIN_TOKEN`), so neither secret can leak to a provider.
|
|
66
|
+
- **Ask up front whether they serve YOUR domain.** Whatever base URL is baked on-chain is what a marketplace fetches. If that's a domain the creator controls (pointed at the provider), leaving later is a DNS change — no tx, no gas. If it's the provider's own hostname, leaving costs an **owner-signed `set-token-uri`/`set-contract-uri` tx**. Same exit either way, different price; worth knowing before you bake it, not after.
|
|
67
|
+
- **Read the descriptor BEFORE registering — match the project to the provider.** `abx remote <name>` (or `curl <base>/.well-known/abx-service`, public, no key) prints what the service supports: **`chains`** must cover the project's chain (else registration is refused with `unsupported_chain`); **`render.attached`** means thumbnails/traits for code drops are rendered behind the provider — **skip `abx deploy-effects` entirely** (without it, renders are still yours: `abx render <addr> --remote <name>` or your own runner); **`auth.signupUrl`** is where a human gets a key.
|
|
68
|
+
- **The API key is the human's step — a membrane ask, never a chat paste.** Say: *"This provider covers your chain and manages rendering. You'll need an API key from `<signupUrl>` — put it in `.env` as `ABX_REMOTE_<NAME>_TOKEN` (never paste it in chat) and I'll do the rest."* Then verify with `abx remote <name>`: it lists the projects visible to the key. **401** = the key is missing/wrong (check the var, rotate at the provider). **403** = the key is valid but not authorized for this contract/chain — provider-side scoping, not a typo; don't retry-loop, check the provider dashboard.
|
|
69
|
+
- **The exit is guaranteed by the interface**, not by the provider's goodwill: registration is never load-bearing for resolution, and `abx migrate --from <provider> --to <anywhere>` reads only the provider's *public* endpoints — leaving is one config change plus (for a provider-hostname base) one on-chain re-point.
|
|
70
|
+
- **Honesty:** no default provider is baked into the CLI, the SDK, or this skill, and the provider market is only starting to form. A creator with a provider key (or a named remote already in `.env`) should use it; otherwise **self-hosting (next section) is the fully-supported path today** and what the rest of this file assumes.
|
|
71
|
+
|
|
72
|
+
## Self-hosted resolver — `abx deploy-resolver`
|
|
73
|
+
|
|
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.
|
|
64
75
|
|
|
65
76
|
**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.)
|
|
66
77
|
|
|
67
78
|
**Never inline a secret into a command.** The generated steps **source from `.env`** (`set -a; . ../../.env; set +a` then `fly secrets set ABX_RPC_URLS="$ABX_RPC_URLS"`) — keep that shape. Never substitute a literal secret into command text; it must never appear in output or shell history.
|
|
68
79
|
|
|
69
|
-
`deploy-resolver` also **generates `
|
|
80
|
+
`deploy-resolver` also **generates `ABX_REMOTE_SELF_TOKEN`** → your local `.env` (a secret — never printed, env-only; "self" is just the named remote for a node you run); the printed steps set that same value on the host under **its own** var name, `ABX_RESOLVER_ADMIN_TOKEN` (server-side config, unrelated to how any client authenticates). That token is what later lets `abx add <clone> --remote` tell the hosted node which contracts to index — the only new secret the hosted path adds, and it authorizes indexing control, not signing.
|
|
70
81
|
|
|
71
82
|
**Gather the decisions in one pass.** The hosted path has a few small choices — provider, custom domain (or the provider-hostname tradeoff), app name, plus identity. Collect them together, then show the full plan; don't drip one question per turn. Once the human says "you run it," run the steps — don't flip between "here are commands" and "I'll do it."
|
|
72
83
|
|
|
@@ -74,18 +85,37 @@ For the large/mutable default. `abx deploy-resolver --provider <fly|render|vps>
|
|
|
74
85
|
|
|
75
86
|
## Render runner — `abx deploy-effects`
|
|
76
87
|
|
|
77
|
-
A code project's marketplace still is rendered off-chain, so a **runner** (Playwright + Chromium — the resolver image stays browserless) must render each token's live view and hand the resolver the result. `abx deploy-effects --resolver-url <resolver>` scaffolds it for Fly (a self-contained `Dockerfile.effects` + `fly.toml` + vendored source), wired to the resolver and your storage home. **Decide where renders live up front** (`ABX_STORAGE_BACKEND`): **
|
|
88
|
+
A code project's marketplace still is rendered off-chain, so a **runner** (Playwright + Chromium — the resolver image stays browserless) must render each token's live view and hand the resolver the result. **A managed provider with `render.attached` in its descriptor does all of this for you — skip this section.** Self-hosting it: `abx deploy-effects --resolver-url <resolver>` scaffolds it for Fly (a self-contained `Dockerfile.effects` + `fly.toml` + vendored source), wired to the resolver and your storage home. **Decide where renders live up front** (`ABX_STORAGE_BACKEND`): **cloud (S3/R2 + a public base) · ipfs · arweave** — equal options, pick on cost/ops. **NOT** the default `fs` for a hosted setup, and this is now refused rather than warned: the runner HOLDS its render bytes and hands the resolver a URL, so a backend that can't name one has no lane (`abx deploy-effects` errors out, and the runner refuses to start). The runner **registers** each render with the resolver's control plane (`POST /v1/effect-artifacts`, gated by the same token — your local `ABX_REMOTE_SELF_TOKEN`, the resolver's own `ABX_RESOLVER_ADMIN_TOKEN` — index-control only, never signing), so it does NOT need to share the resolver's disk. What travels: a **locator** for the still (the resolver 302-redirects; it never proxies or fetches it), and the **content** for `traits` (≤64KB — traits stitch into the token JSON, so a pointer there could never work). It does not matter which scheme the URL uses: an `https://` gateway or bucket URL is a peer of `ipfs://`/`ar://`, since a lost render is a re-render. It only has to be fetchable by someone other than you — a loopback gateway or a presigned expiring URL is refused. Two topologies:
|
|
78
89
|
|
|
79
|
-
- **Locator bridge (default for a hosted drop):** runner on its own host + a public storage home; it
|
|
80
|
-
- **Co-located:** runner beside the resolver sharing one backend/volume (no admin token needed — both read/write the same store).
|
|
90
|
+
- **Locator bridge (default for a hosted drop):** runner on its own host + a public storage home; it registers locators, the resolver redirects. `deploy-effects` sets this up.
|
|
91
|
+
- **Co-located:** runner beside the resolver sharing one backend/volume (no admin token needed — both read/write the same store). This is the escape hatch when there's no public storage home: `fs` is fine here.
|
|
81
92
|
|
|
82
|
-
One-shot without a service: **`abx render <addr> --remote <resolver>`** renders locally and
|
|
93
|
+
One-shot without a service: **`abx render <addr> --remote <resolver>`** renders locally and registers with the hosted resolver — same requirement, so pass `--backend cloud|ipfs|arweave` (the default `fs` is refused before Chromium launches, naming the fix). Re-run any time: registration is idempotent and repairs a resolver that lost its rows without re-rendering. Optional immediacy: set `ABX_EFFECTS_URL` on the resolver so it pings the runner on re-index; the periodic sweep is the eventual floor regardless.
|
|
83
94
|
|
|
84
|
-
## Local vs remote — two SEPARATE projection stores (read before deploying to a
|
|
95
|
+
## Local vs remote — two SEPARATE projection stores (read before deploying to a remote resolver)
|
|
85
96
|
|
|
86
|
-
A project is served only if it's in **the resolver's own** store. `abx deploy`/`abx add` (no `--remote`) index into **this machine's** store; a
|
|
97
|
+
A project is served only if it's in **the resolver's own** store. `abx deploy`/`abx add` (no `--remote`) index into **this machine's** store; a remote resolver — self-hosted or a managed provider — is a **different store** and doesn't learn about a contract just because you deployed it. A local deploy pointing its `tokenURI` at a remote resolver returns `{"error":"unknown project"}` for everyone — "works on my `serve`, broken for the world." **Bridge it:**
|
|
87
98
|
- **Local resolver** (`abx serve` here): a local `deploy`/`add` already indexed it — done.
|
|
88
|
-
- **Remote resolver** (baked URL is
|
|
99
|
+
- **Remote resolver** (baked URL is remote): after deploy run **`abx add <clone> --remote [name|url]`** to register + index it on the node (bare `--remote` defaults to `ABX_PUBLIC_BASE_URL` + `ABX_REMOTE_SELF_TOKEN`; a named remote uses its own `ABX_REMOTE_<NAME>_TOKEN`). Also **bridges** what the node can't derive: the durable `ipfs://`/`ar://` locator and off-chain traits. Post-deploy nudge (after a deferred mint) is **`abx index <clone> --remote`** (idempotent). Remove with **`abx forget <clone> --remote`**. Every remote command prints `REMOTE → <url>`.
|
|
100
|
+
|
|
101
|
+
### Indexing status — `backfilling` is not a failure
|
|
102
|
+
|
|
103
|
+
A service may accept a registration and catch up **asynchronously** (a cold replay on a rate-limited RPC takes minutes). `abx add --remote` handles it: prints `registered — backfilling…`, polls, then prints the normal summary once the project is **`live`**. So a slow add is *slow*, not broken — don't re-run `add` to "retry" it (a re-POST never starts a second replay, but you'll just wait again). `--no-wait` returns immediately when you don't want to block.
|
|
104
|
+
|
|
105
|
+
**Check anytime — same five words locally and remotely:** `abx status <address>` (this node) · `abx status <address> --remote <name> [--watch]` (a service) · `abx remote <name>` (roll-up: *"3 live, 1 backfilling, 1 failed (rpc_rate_limited)"*).
|
|
106
|
+
|
|
107
|
+
**On a node YOU run**, two knobs shape this (both optional): `ABX_REGISTER_DEADLINE_MS` (default 8000) is how long a register may hold the HTTP request before answering "accepted, still catching up" and finishing in the background — the registration is durable either way, so this only decides who waits; and `ABX_STALE_LAG_BLOCKS` (default 5000) is how far behind chain head the watcher may fall before it reports its projects `stale`. A backfill interrupted by a restart is re-queued automatically on the next `abx serve` — you don't have to re-add it.
|
|
108
|
+
|
|
109
|
+
| status | what it means | what to do |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| `queued` / `backfilling` | accepted, catching up (registration is already durable) | **wait** — poll, don't re-add |
|
|
112
|
+
| `live` | caught up, tracking head | done |
|
|
113
|
+
| `stale` | was live, now lagging — **still serving** old state | usually the service's RPC; investigate if it sticks |
|
|
114
|
+
| `failed` | catch-up errored; retried with backoff | read the **class** (below); the registration survived |
|
|
115
|
+
|
|
116
|
+
`failed` names a cause: **`rpc_rate_limited`/`rpc_unavailable`** = the *service's* RPC, not your key and not your address — it retries, so wait or tell the operator; **`not_abx_contract`** = wrong address/chain (or a provider that validates clones refusing a non-clone); **`internal`** = the operator's logs have it. Report the class verbatim to the user rather than guessing a cause.
|
|
117
|
+
|
|
118
|
+
**A `live` project with 0 events is a red flag, not a success** — a real ABX clone always emits a spine, so 0 events means the service scanned the wrong chain or its RPC didn't serve the logs (check `abx remote <name>` covers your chain, and that the deploy block was forwarded). The CLI warns instead of printing ✓ here.
|
|
89
119
|
|
|
90
120
|
### Deploying to a hosted resolver — pre-warm flow (preferred)
|
|
91
121
|
|
|
@@ -117,12 +147,15 @@ Drop `--no-mint` (steps 5–6 collapse) for the express path. Add `--sign` to st
|
|
|
117
147
|
|
|
118
148
|
## Token API (the resolver)
|
|
119
149
|
|
|
150
|
+
⚠ **Read this list; never infer a route from another one — and prefer not needing it at all.** The contract commits its own metadata URL on-chain, so **`abx tokenuri <addr>`** and **`abx contracturi <addr>`** (ERC-7572 collection metadata: reads `contractURI()`, follows it, decodes the JSON) answer "where does this resolve, and what does it say" without you assembling a URL. Guessing has a specific trap: **`/t/<chainId>/<address>` is NOT collection metadata** — collection metadata is `/c/<chainId>/<address>`. A miss now names which of three things happened, in a machine `code`: **400 `invalid_request`** (your path shape — the body names the right template and may carry `didYouMean`) · **404 `unknown_route`** (this node serves no such path; body lists what it does serve) · **404 `not_registered`** (path fine, this node doesn't index that contract → `abx add <addr> --remote`) · **400 `unsupported_chain`** (body carries the `chains` it serves). None of those mean the service is down — don't report an outage off a URL you built.
|
|
151
|
+
|
|
120
152
|
Default `http://localhost:8787` (or `ABX_PUBLIC_BASE_URL`). Routes carry the **chainId** (the protocol path grammar) so one host can serve many chains:
|
|
121
|
-
- `GET /` — read-only index of served contracts · `GET /t/<chainId>/<address>/0` — ERC-721
|
|
153
|
+
- `GET /` — read-only index of served contracts · `GET /t/<chainId>/<address>/0` — token metadata (ERC-721 `tokenURI` on a 1/1/Series/code project, ERC-1155 `uri` on an edition — auto-detected, same route) · `…/0/image` — the image
|
|
122
154
|
- `GET /c/<chainId>/<address>` — ERC-7572 collection metadata · `GET /api/project/<address>` — full reconstructed state · `GET /d/<chainId>/<address>` — per-contract read-only dashboard (namespaced so one host serves many contracts). No public action buttons anywhere.
|
|
123
155
|
- For IPFS/Arweave the served `image` is the **gateway HTTPS URL** (`https://<gateway>/ipfs/<cid>` / `<gateway>/<txid>`), the form wallets/marketplaces render (raw `ipfs://` doesn't). The on-chain commitment is the **keccak256** (backend-neutral anchor, survives a gateway migration); the CID/txid is just the locator. The locator lives in the **deployer's** local index, so a **remote** resolver emits the gateway URL only once it's bridged (`abx add <clone> --remote`), else `image` falls back to the resolver's own `/…/image` route.
|
|
124
|
-
- `
|
|
125
|
-
- `POST /
|
|
156
|
+
- `GET /.well-known/abx-service` — the **service descriptor** (public): what the node supports — `interfaces`, `chains`, `auth` (incl. `signupUrl` for humans), `render.attached` (managed rendering). Read it before registering with any remote service.
|
|
157
|
+
- `POST /v1/projects` `{chainId, address, fromBlock?, factory?, description?, externalUrl?, attributes?, contentLocators?}` · `GET /v1/projects` (the projects visible to the token, each with `status`) · `DELETE /v1/projects/<chainId>/<address>` · `POST /v1/projects/<chainId>/<address>/reindex` · `GET /v1/projects/<chainId>/<address>/status` (`status` + `fromBlock`/`toBlock`/`headBlock` + `error.class`) — the **control plane** ([spec](../../../../specs/self-host-toolkit/remote-services.md)): register/forget which contracts the node indexes, bridge off-chain traits + locators; a remote `add`/`forget`. Bearer-gated (`Authorization: Bearer <token>`), 404 code `disabled` when no token is configured. Errors carry a machine `code` (`unauthorized` 401 · `forbidden` 403 · `unsupported_chain` · `not_registered`). Register answers **200** (caught up, with counts) or **202** (accepted, still catching up — poll status); the registration is durable either way. Indexing control only — never signing.
|
|
158
|
+
- `GET /api/project/<address>/verify` — bearer-gated too (it triggers outbound fetches). Run from the CLI: `abx verify <addr>` / `abx index <addr> --remote`.
|
|
126
159
|
|
|
127
160
|
`/api/project/<address>` key fields: `isCanonical` (factory-verified — note: `isCanonical`, **not** `canonical`), `owner`, `royalty`, `collectionFields[]`/`lockedCollectionFields[]`, `extensions[]`, `tokens[]` each `{minted, owner, tokenURI, fields[] ({field, representation, value}), lockedFields[]}`. The JSON also carries **`abx_provenance`** — per-field `source` + `status` (`on-chain` · `verified` · `mismatch` · `anchored` · `off-chain` · `n/a`), with an `anchor` for off-chain bytes that carry an on-chain hash (`off-chain` = plain operator value, benign; `anchored` = verifiable via `abx verify`). Confirm a deploy with **`abx verify <addr>`**, not curl.
|
|
128
161
|
|
|
@@ -16,9 +16,10 @@ After launch the owner operates the project. Each command builds a tx, signs it
|
|
|
16
16
|
| `abx set-contract-uri <addr> (--uri <base> \| --override <uri>)` | re-point the collection base, or pin a fixed locator | the new base / locator |
|
|
17
17
|
| `abx deploy-resolver --provider <fly\|render\|vps> [--domain <host>]` | scaffold a hosted read-only resolver (the default off-chain path); generates the admin token | which provider; the custom domain |
|
|
18
18
|
| `abx deploy-effects --resolver-url <resolver>` | scaffold the render runner (Playwright) beside a hosted resolver — renders code-token stills off-chain and publishes them | the storage home (`ABX_STORAGE_BACKEND`: ipfs/arweave/s3, not local `fs`) |
|
|
19
|
-
| `abx render <addr> [id…] [--remote <
|
|
20
|
-
| `abx
|
|
21
|
-
| `abx
|
|
19
|
+
| `abx render <addr> [id…] [--remote <name\|url>]` | render missing stills/traits now (repair lane); `--remote` publishes to a REMOTE resolver | none — idempotent; local captures need Playwright chromium |
|
|
20
|
+
| `abx remote [<name\|url>]` | inspect a remote service: named remotes in `.env`, or a target's descriptor (chains · managed rendering · signup URL) + the projects your token sees | none — read-only; the "is my provider key valid?" check |
|
|
21
|
+
| `abx add <addr> --remote <name\|url>` · `abx index <addr> --remote` · `abx forget <addr> --remote` | register / re-index (nudge) / deregister a contract on a REMOTE resolver (control plane) | which remote — a name (`ABX_REMOTE_<NAME>_URL/_TOKEN`, a managed provider's key) or bare `--remote` (self-host default: `ABX_PUBLIC_BASE_URL` + `ABX_REMOTE_SELF_TOKEN`); ad-hoc `--remote <url> --remote-token <t>` |
|
|
22
|
+
| `abx migrate <addr> --from <src name\|url> --to <dest name\|url>` | move a contract's **off-chain state** to a new resolver instance (see below) | confirm the cutover step (DNS vs base URI); re-pin any source-only images; only the DESTINATION needs a token |
|
|
22
23
|
| `abx set-royalty <addr> --bps <0-10000> [--receiver 0x..]` | change the royalty (receiver defaults to current) | the rate, and whether the payee changes |
|
|
23
24
|
| `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 |
|
|
24
25
|
| `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,12 +28,11 @@ After launch the owner operates the project. Each command builds a tx, signs it
|
|
|
27
28
|
|
|
28
29
|
**`lock-field` / `lock-uri` are permanent — always confirm with the human first.**
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
**Welding a PARAM is a different verb.** 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's value permanent, lock its schema: `abx set-schema <addr> --schema <key>:<Type>:<Auth>:lock=now` — after which every `configure-param` on that key reverts. (`lock-field` refuses a declared param key rather than letting you lock the wrong thing.)
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
### Authorship + rights (credit + license)
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
- **Later / to change**: `abx set-field <addr> --collection --field artist --text "Casey Reas"`.
|
|
35
|
+
The reserved **collection** keys are `artist`, `display_notes`, `artist_links`, `license` (a rights *identifier* like `CC0-1.0` / `CC-BY-NC-4.0`). `--artist`/`--license`/`--display-notes`/`--artist-links` on any deploy command are **sugar over `set-field`** — they pre-fill the same on-chain inline collection fields at deploy time (parity with `--description`/`--external-url`); change them later with `abx set-field <addr> --collection --field artist --text "Casey Reas"` directly.
|
|
36
36
|
|
|
37
37
|
They project into `contractURI` on both the resolver and the on-chain renderer; unset ⇒ omitted. Works on **any** token type (1/1 · Series · code) — reserved keys in the shared metadata store, not a code-only feature. Being *reserved*, they never become `artifacts` entries the way an arbitrary `attach` key would.
|
|
38
38
|
|
|
@@ -48,7 +48,7 @@ A token anchors **named, typed files**; the served JSON's **`artifacts`** array
|
|
|
48
48
|
- scope: `--token <id>` (default 0) or `--collection`; any lane; `--dry-run` previews the tx.
|
|
49
49
|
- **`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.
|
|
50
50
|
- **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.
|
|
51
|
-
- **On-chain vs resolver reach.** The complete listing (arbitrary attached keys) is a **resolver** surface — the EVM can't enumerate an open set of
|
|
51
|
+
- **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, so a bare `tokenURI` emits every set param under **`abx_params`** with no resolver in the picture. Say it to a creator as one line — *attachments always need a resolver; params never do.*
|
|
52
52
|
|
|
53
53
|
### Series-only owner ops
|
|
54
54
|
|
|
@@ -65,11 +65,28 @@ For a multi-token Series ([SKILL → Series](../SKILL.md#series-multi-token-drop
|
|
|
65
65
|
|
|
66
66
|
`set-field` / `lock-field` work per **token** for a Series — pass the token id (`--field image --file … ` targets that token's artwork); tokens share the collection scope with `--collection`.
|
|
67
67
|
|
|
68
|
+
### Edition owner ops
|
|
69
|
+
|
|
70
|
+
For an **ERC-1155 edition** (`OneOfOneEdition` / `EditionImage` / `EditionCode`, reached via `--copies` at deploy — [SKILL → which command](../SKILL.md#which-command--what-are-you-launching)). Everything above still applies per artwork (transfer, royalty, fields, locking); these are the edition-only twins, keyed by **id** instead of a single owner.
|
|
71
|
+
|
|
72
|
+
| Command | Does | Decision to surface |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `abx mint <addr> --token-id <n> --amount <n>` | mint **copies** of one id — `--token-id` defaults to `0` on a 1/1-edition (its only id), **required** on EditionImage/EditionCode (ids are caller-named artworks; see `abx tokens <addr>`) | which id, how many copies, who receives them |
|
|
75
|
+
| `abx transfer <addr> --amount <n> --from 0x.. --to 0x..` | move copies of an id between holders — `--from` is **required** (an id can have many concurrent holders, so there's no single on-chain "owner" to default from) | which holder is selling, how many copies |
|
|
76
|
+
| `abx set-max-supply <addr> --token-id <n> --cap <n>` | **lower** one id's ERC-1155 supply cap — the per-id twin of `set-max-invocations`; monotonic (never below that id's live supply) | confirm — **one-way**: `--cap open` is refused (once capped, an id can never re-open), so closing an id is final |
|
|
77
|
+
| `abx ping-uri <addr> --token-ids <csv\|range>` | permissionless: re-emit the native ERC-1155 `URI` event for the given ids, after a contract-wide re-point (`set-token-uri`/`set-renderer`) that only emitted its own config event, not a per-id one | none — run it when a marketplace/indexer only honors the native per-id event and missed the re-point |
|
|
78
|
+
| `abx minter configure <addr> --token-id <n> --price <eth> --allocation <n>` | price **one id** on the edition sibling minter (`AbxFixedPriceMinter1155`) — every id sells on its own terms, not one price for the whole contract | which id, price, allocation |
|
|
79
|
+
| `abx minter buy <addr> --token-id <n> --quantity <n>` | buy `n` copies of that id in one purchase — pays `price × quantity` | how many copies |
|
|
80
|
+
|
|
81
|
+
`--token-id` is required on every edition op above that names a specific artwork, except a 1/1-edition (defaults to `0`, its only id) — and refused on a 721 target either way. Passing `--count` to `mint`, or omitting `--from` on `transfer`, is refused on an edition, pointed at the flag to use instead.
|
|
82
|
+
|
|
68
83
|
### Selling — the shared fixed-price minter
|
|
69
84
|
|
|
70
85
|
Instead of writing a sale contract, use the canonical **`AbxFixedPriceMinter`**: one **ownerless, multi-tenant singleton** (shared like the factory/renderer) that sells *any* Series exposing the sequential mint primitive. Sale config is keyed by token address and **all authority defers to the token owner** — so the minter holds no admin, custodies nothing, and routes proceeds to the token's own `primaryPayee()` (read fresh each sale). Design detail: [Minter spine](../../../../specs/protocol/minter-spine.md).
|
|
71
86
|
|
|
72
87
|
> **The minter sells a Series, mint-on-purchase — not a 1/1 you already hold.** It mints *unminted* supply to a buyer, so it needs the Series interface (`minter`/`paused`/`primaryPayee`). A plain `abx deploy` **1/1 has none of that** — its only post-mint move is `abx transfer` (settle an off-chain sale). So there are two real paths for selling one piece: **(A)** mint the 1/1 to yourself, sell it off-platform (OpenSea/Blur), then `abx transfer --to <buyer>`; or **(B)** for a native fixed-price *primary* sale, deploy the single piece as a **1-token Series** (`abx deploy-series` on a one-file folder, `--no-mint`) and run the go-live sequence below with `--allocation 1`. Don't try to wire the minter to a plain 1/1 — `set-minter`/`unpause` will revert.
|
|
88
|
+
>
|
|
89
|
+
> **The edition minter is the one exception — it DOES sell a "1/1"-shaped artwork directly.** `AbxFixedPriceMinter1155` targets `IAbxEditionMint`, which `OneOfOneEdition` implements (id space fixed to `0`), so a priced open/limited edition of a single artwork needs no Series wrapper: `abx deploy --copies <n|open> --no-mint` ships the full sale stack on its own — run the same go-live sequence below with `--token-id 0`.
|
|
73
90
|
|
|
74
91
|
| Command | Does | Notes |
|
|
75
92
|
|---|---|---|
|
|
@@ -86,6 +103,8 @@ abx unpause <token> # open the sale (th
|
|
|
86
103
|
```
|
|
87
104
|
Two independent grants — *configured* (on the minter) and *assigned* (on the token) — so "not configured" and "not assigned" are distinct, diagnosable reverts; `abx minter show` flags whichever is missing. **One mint per `purchase()`**; a buyer who wants several sends several txs. Allocation and the token's `maxInvocations` both bind (tighter wins) — keep reserves by allocating less than the supply. Other mechanics (auctions, allowlists) are *different* minters, not knobs here.
|
|
88
105
|
|
|
106
|
+
**On an edition, only the minter calls need `--token-id`** — `minter configure/show/buy --token-id <n>` (a different sale per artwork, 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`.
|
|
107
|
+
|
|
89
108
|
### A mint website for buyers — `abx mint-page`
|
|
90
109
|
|
|
91
110
|
Once a sale is live, `abx mint-page <token>` scaffolds a **self-contained Next.js mint site** for the collection — a paginated gallery of minted tokens, connect-wallet, and a mint button — prefilled with the token, the shared minter, and the chain. It's the sale-side sibling of `abx deploy-resolver`: an artifact the creator **owns and customizes**, not a service we run.
|
|
@@ -96,6 +115,7 @@ Once a sale is live, `abx mint-page <token>` scaffolds a **self-contained Next.j
|
|
|
96
115
|
| Runs | `cd mint-page && npm install && npm run dev` (preview) → **Vercel** (`npm i -g vercel && vercel --prod`), setting the `NEXT_PUBLIC_*` vars in the dashboard |
|
|
97
116
|
|
|
98
117
|
- **Backend-free** — reads sale state + each token's image straight from chain (`tokenURI(id)`); mints via the buyer's injected wallet (viem, no API keys). On-chain collections need nothing else; off-chain ones rely on their `tokenURI` resolving.
|
|
118
|
+
- **Works against an edition too** — pointed at `OneOfOneEdition`/`EditionImage`/`EditionCode`, the page is a **token-id-and-quantity purchase card** instead of a gallery (no minted-tokens list in this v1 page). Refuses only a plain 1/1 (no minter lane) — deploy a one-token Series, or an edition of one artwork, instead.
|
|
99
119
|
- **A starting point** — plain React + one CSS file. Offer it as a release next-step, then help build a bespoke page per drop. The README covers multi-wallet (RainbowKit), ERC-20 (add an `approve` step), and the RPC/gallery notes. V1 handles **ETH** sales.
|
|
100
120
|
- **RPC safety** — the embedded read RPC is public/keyless (it ships to the browser). Never bake a secret-keyed endpoint into a `NEXT_PUBLIC_` var.
|
|
101
121
|
|
|
@@ -103,6 +123,7 @@ Once a sale is live, `abx mint-page <token>` scaffolds a **self-contained Next.j
|
|
|
103
123
|
|
|
104
124
|
- **Same resolver, new address** (moved the *node*, kept its projection store — e.g. a VPS restored from the same volume): `set-token-uri` + `set-contract-uri` to the new **base**, then `abx index`. With a custom domain, just re-point DNS — no tx.
|
|
105
125
|
- **A fresh resolver instance** (new host, empty store): run **`abx migrate <addr> --from <old-url> --to <new-url>`** first. The new resolver replays on-chain state itself; `migrate` bridges what it can't derive — off-chain `description`/`external_url`, off-chain traits, image **content locators** — by reading the old resolver's public API (they never talk directly; provenance makes the JSON self-describing). It verifies parity and **does not cut over**.
|
|
126
|
+
- **Leaving (or joining) a managed provider — the same two cases.** `abx migrate <addr> --from <provider name|url> --to <your-node-or-new-provider>` reads only the provider's **public** endpoints — the interface guarantees the exit needs zero provider cooperation, and no source credential. Then the standard cutover below (a provider base URL is the "provider endpoint" case: one on-chain re-point). Registration is never load-bearing for resolution, so a vanished provider costs availability, never state.
|
|
106
127
|
|
|
107
128
|
### The cutover, after a clean `migrate`
|
|
108
129
|
|
package/skill/reference/setup.md
CHANGED
|
@@ -13,18 +13,21 @@ When `abx doctor` flags a tight range, or a reconstruction would be large/slow
|
|
|
13
13
|
|
|
14
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.
|
|
15
15
|
|
|
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
|
+
|
|
16
18
|
**Re-index is incremental by default** — it resumes from the last-indexed block and only fetches new blocks, so routine re-indexing (after an owner op, or just refreshing) stays instant *regardless* of the range cap. Even the **first** reconstruction scans only from the **deploy block** (not genesis), so range/archive capability bites only on a genuinely long span — a forced full replay (`abx index --full`, a fresh projection, or the dashboard's "Re-index from chain" — the durability proof) of a contract deployed long ago. A "why is this scanning millions of blocks?" moment almost always means the scan floor is wrong (block 0), not that the RPC is inadequate.
|
|
17
19
|
|
|
18
20
|
## Troubleshooting
|
|
19
21
|
|
|
20
|
-
- **doctor: signing key** → **not fatal — a missing key is a fork, not a blocker.** A key in `.env` is only needed to sign *unattended* (hot lane). To sign in a browser wallet instead, run the write with `--sign` and no env key is needed. Offer both: add `
|
|
22
|
+
- **doctor: signing key** → **not fatal — a missing key is a fork, not a blocker.** A key in `.env` is only needed to sign *unattended* (hot lane). To sign in a browser wallet instead, run the write with `--sign` and no env key is needed. Offer both: add `ABX_DEPLOYER_PK` for autonomous signing, **or** use `--sign`. **deployer unfunded** → fund the printed address from a faucet for the active chain (Base Sepolia by default, else Sepolia).
|
|
21
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.
|
|
22
24
|
- **storage 'cloud' missing accessKeyId/…** → set `ABX_S3_*` in `.env`. **'ipfs' unreachable** → Kubo not running or `PINATA_JWT` missing.
|
|
23
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.
|
|
24
26
|
- **`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":
|
|
25
|
-
- **A
|
|
27
|
+
- **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.)
|
|
26
28
|
- **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.
|
|
27
|
-
- **`/a/…` returns `
|
|
29
|
+
- **`/a/…` returns `503` with "its on-chain code has not been folded into the projection yet"** → exactly what it says: the resolver knows this is a code project (from the deployed extensions) but hasn't read its code yet. **Retry** — this is normal right after a register. It only means trouble if it persists, and then it's the scan-floor bug below.
|
|
30
|
+
- **`/a/…` returns `404 {"error":"no live view — not a code project"}` on a project that IS a code drop** → the resolver indexed *above* the deploy block, so it never saw the deploy at all — a scan-floor bug, **not** a resolver version/compat gap (do NOT redeploy as a static NFT). The 404-vs-503 split is the diagnosis: 404 on a real code drop means the fold never saw this contract's deploy, which is the scan-floor signature. This is the *opposite* of the genesis bug: the floor is too **high**, not too low. It happened when a `--sign` code deploy spanned blocks (deploy at N, mint at N+2) and the mint block was recorded as the floor. Confirm: `GET /api/project/<addr>` → `collectionFields` is `[]` and `fromBlock` sits above the deploy block. Fix: `abx add <addr> --remote <url> --from-block <deployBlock>` — a *changed* floor forces a full replay that picks up the `code` field. Find the true deploy block with `abx add <addr>` locally (it prints "deploy block N (discovered on-chain)"). (Fixed in-toolkit: `deploy-code` now records the clone-CREATION block, and discovers it on-chain rather than trusting the last-tx receipt.)
|
|
28
31
|
- **directory-mode live view 302s to a doubled URL** (`https://arweave.net/https://arweave.net/<txid>/index.html`) → the `code` locator was stored as a full gateway URL and the gateway got prefixed again. Fixed in-toolkit (deploy stores the bare txid/CID; the resolver serves an already-absolute locator verbatim). A resolver image built before the fix still doubles — redeploy it to pick up the resolver-side tolerance.
|
|
29
32
|
- **the thumbnail stays a placeholder on a HOSTED resolver even after `abx render` reports `ran=1`** → the render bytes landed in a store the hosted resolver can't read. A local `abx render` with the default `fs` backend writes to your laptop; `ipfs`/`arweave` write a LOCAL key→CID index the resolver doesn't have. Fix: render **to** the resolver — `abx render <addr> --remote <resolver>` (uploads to your `ABX_STORAGE_BACKEND` home and **publishes** a locator/bytes the resolver serves), or stand up the runner beside it (`abx deploy-effects --resolver-url <resolver>`). Only a SHARED `s3`/`cloud` bucket makes a bare local render visible to a hosted resolver. Confirm with `abx verify <addr>`.
|
|
30
33
|
- **resolver won't start / errors about `ABX_PUBLIC_BASE_URL`** → it refuses a placeholder `.example` base (a scaffold leftover) or a `localhost` base in a hosted image (`ABX_HOSTED=1`), because it bakes that base into every image/animation URL it serves — a bad one serves dead links, so a loud fail beats silent breakage. Set `ABX_PUBLIC_BASE_URL` to the resolver's real public URL (`fly secrets set ABX_PUBLIC_BASE_URL=https://<app>.fly.dev`, or your custom domain). The current scaffold bakes the real platform hostname by default, so this only bites a hand-edited/old artifact or a stripped env.
|
|
@@ -32,5 +35,9 @@ When `abx doctor` flags a tight range, or a reconstruction would be large/slow
|
|
|
32
35
|
- **the project's on-chain `tokenURI` points at a HOSTED resolver but it isn't serving** → **do NOT "fix" it by running `abx serve` locally and handing over a `localhost` link.** The baked URL is the hosted one; a local serve resolves for no one but you. Fix the HOSTED resolver instead: re-run `abx add <addr> --remote` (now forwards the deploy block), check `GET /` on the host, and vet its RPC with `abx doctor`. A local serve is only ever the answer when the baked base is that same local machine.
|
|
33
36
|
- **port hygiene before `serve`** → check the port is free first (`lsof -i:8787`). If an old session holds it, kill that process or serve this project on a distinct `--port <n>` (and point its `--public-base-url`/tokenURI accordingly) — a stale server silently answering on the port is the #1 cause of "works for me, `unknown project` for everyone."
|
|
34
37
|
- **serving code projects is RPC-heavier than static art** → the live view rebuilds `tokenData` per request, and template-mode script chunks + data-backed (`String`/`Bytes`) params are read **live per view** (scalar PostParams come from the indexed projection, so they don't hit RPC each view). A resolver serving many code projects under marketplace traffic wants a range-generous, reliable RPC — this is a genuine scaling consideration, distinct from the getLogs range cap above.
|
|
38
|
+
- **a remote command returns 401 unauthorized** → the token the CLI resolved is missing/wrong for that target — the error names the var it used (`ABX_REMOTE_<NAME>_TOKEN` for a named remote, else `ABX_REMOTE_SELF_TOKEN`, else `--remote-token`). Check that var, or rotate the key at the provider if it may have leaked. Named remotes deliberately never fall back to `ABX_REMOTE_SELF_TOKEN` (or the resolver's own server-side `ABX_RESOLVER_ADMIN_TOKEN`).
|
|
39
|
+
- **a remote command returns 403 forbidden** → the key is **valid but not authorized** for this contract/chain — provider-side scoping, not a typo. Don't retry-loop and don't swap tokens blindly: check the provider dashboard, and the descriptor's `chains` (`abx remote <name>`).
|
|
40
|
+
- **register refused with `unsupported_chain`** → the service doesn't serve the project's chain — its descriptor `chains` says which it does. Pick a provider that covers the chain, or self-host.
|
|
41
|
+
- **`/.well-known/abx-service` 404s** → an older self-hosted node (fine if it's yours — the remote commands still work against it once redeployed to the current image) or **not an ABX service at all** — verify the URL with `abx remote <url>` before registering anything; don't register blind.
|
|
35
42
|
- **wallet lane: "no injected wallet"** → the human has no extension wallet; fall back to the hot lane (`--send`) if they're comfortable, or the cold lane for a Safe.
|
|
36
43
|
- **owner op reverts** → the signer isn't the owner/holder. Check `abx status` / the `/api/project` state for the current owner; sign as that wallet.
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[← back to SKILL.md](../SKILL.md)
|
|
4
4
|
|
|
5
|
-
**Diagnose before you act — INSPECT first, don't guess.** The reads: `abx state <addr>` (on-chain owner/supply/paused/minter/renderer), `abx tokenuri <addr> [--token N]` (the actual served/on-chain JSON + `abx_provenance` per field — the fastest "is this really on-chain right now"), `abx verify <addr>` / `abx verify --remote <url>` (does the served image match the chain; is a render real vs a placeholder). Most "it's broken" reports are a stale *marketplace cache*, not a broken token.
|
|
5
|
+
**Diagnose before you act — INSPECT first, don't guess.** The reads: `abx state <addr>` (on-chain owner/supply/paused/minter/renderer), `abx tokenuri <addr> [--token N]` (the actual served/on-chain JSON + `abx_provenance` per field — the fastest "is this really on-chain right now"), `abx verify <addr>` / `abx verify <addr> --remote <name|url>` (does the served image match the chain; is a render real vs a placeholder). Most "it's broken" reports are a stale *marketplace cache*, not a broken token.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**`abx verify` answers two separate questions — read both lines.** *Renders*: is there a current thumbnail, or a placeholder (only relevant to code projects). *Bytes*: do the served bytes still hash to the on-chain commitment — **the one that explains a blank/wrong image**. On `--remote` the byte check runs on the service (it holds both the bytes and the chain), so it needs a credential: without one you'll see **`byte integrity NOT checked`** — that is NOT a pass, so supply the token and re-run before you tell a creator it's fine. A byte MISMATCH exits non-zero and has exactly two causes: durable bytes (`ipfs://`/`ar://`) that were never bridged to the service (`abx add <addr> --remote <name>` forwards the locator), or bytes that only exist on the creator's own machine (`fs` custody) — a hosted resolver can never serve those, so they must be uploaded to a durable backend and re-pointed.
|
|
8
|
+
|
|
9
|
+
Note: `abx state` / `abx tokenuri` / `abx contracturi` are **pure RPC reads** — they work on any address with no local setup. Reach for **`abx contracturi <addr>`** for anything collection-level (ERC-7572: name/description/image of the *collection*, the marketplace banner): it reads `contractURI()` from the contract, follows it, and decodes — so it also proves whether the collection URL the contract commits to is actually being served. **Never hand-build the URL to check that** (a guessed path that 404s reads exactly like an outage; see [SKILL.md → Read first](../SKILL.md#read-first-every-session)). `abx verify` / `abx index` / data-plane ops need the project **registered on this node first** (`abx add <addr>`); their error says so.
|
|
8
10
|
|
|
9
11
|
### Gray placeholder / no image on the marketplace
|
|
10
12
|
Walk the cause down:
|
|
@@ -16,13 +18,38 @@ Walk the cause down:
|
|
|
16
18
|
### I changed the metadata but the marketplace shows the old value
|
|
17
19
|
- **First confirm the change landed:** `abx tokenuri <addr> --token N` (or the resolver's JSON) — if it shows the NEW value with `abx_provenance` `onChain: true`, the write worked. The gap is 100% the marketplace's cache.
|
|
18
20
|
- **Nudge it:** `abx refresh <addr>` — ABX emits **ERC-4906** on metadata changes so 4906-aware marketplaces self-refresh; `refresh` calls OpenSea directly with `OPENSEA_API_KEY`, else prints the link to click. Marketplace caches still lag on their own schedule — that's their side.
|
|
19
|
-
- **
|
|
21
|
+
- **Remote resolver (self-hosted or a managed provider)?** It serves from its own store — a local edit must reach it: `abx add <addr> --remote <name|url>` / `abx index <addr> --remote <name|url>` re-indexes the remote node. Never "resubmit the transaction" or redeploy. If the nudge reported `backfilling`, the service is still catching up — `abx status <addr> --remote <name> --watch` until `live` before blaming the marketplace.
|
|
22
|
+
|
|
23
|
+
### An edition's URI re-point didn't refresh a marketplace/indexer
|
|
24
|
+
`abx refresh` and ERC-4906 are 721-only — an edition (`OneOfOneEdition`/`EditionImage`/`EditionCode`)
|
|
25
|
+
signals a metadata change with ERC-1155's native `URI(string,uint256)` event instead. A contract-wide
|
|
26
|
+
re-point (`abx set-token-uri`/`abx set-renderer`) only emits its own config event, not a per-id `URI` —
|
|
27
|
+
so a marketplace/indexer that honors *only* the native per-id event misses it. Fix: `abx ping-uri <addr>
|
|
28
|
+
--token-ids <csv|range>` re-emits `URI` for the ids you name — permissionless, no owner check needed.
|
|
29
|
+
|
|
30
|
+
### "It's registered but serves nothing" / the add seemed to hang
|
|
31
|
+
**Ask the service where it got to before doing anything else: `abx status <addr> --remote <name>`.** The five states and what each one means for you are in [hosting.md → Indexing status](hosting.md#indexing-status--backfilling-is-not-a-failure). In short: `queued`/`backfilling` = **wait** (a cold replay on a rate-limited RPC takes minutes; the registration is already durable, so re-adding just makes you wait again); `failed` = read `error.class` (`rpc_rate_limited`/`rpc_unavailable` are the *service's* RPC, not your key — it retries on backoff); `stale` = it's serving, just behind. Never diagnose this by redeploying, rotating the key, or re-pointing the on-chain URI.
|
|
32
|
+
|
|
33
|
+
**On your own node:** the same command without `--remote`. A project stuck `queued` on a node that isn't running `abx serve` has nothing to drain it → start the resolver (or run `abx index <addr>` once). `stale` on your own node means the chain watcher has fallen far behind head — check the node's RPC.
|
|
20
34
|
|
|
21
35
|
### `abx tokenuri` / Etherscan reverts on a fully-on-chain code project
|
|
22
|
-
A large on-chain `tokenURI` document can exceed the **unauthenticated eth_call gas cap** some RPCs/explorers impose on a public read — the call reverts in that UI but the data is fine on a normal RPC. This is **expected for a big on-chain doc, NOT an indexing problem** — do not `abx index --full` or redeploy. Read it via a
|
|
36
|
+
A large on-chain `tokenURI` document can exceed the **unauthenticated eth_call gas cap** some RPCs/explorers impose on a public read — the call reverts in that UI but the data is fine on a normal RPC. This is **expected for a big on-chain doc, NOT an indexing problem** — do not `abx index --full` (there is no index in the on-chain path) or redeploy. Read it via `abx tokenuri`, a wallet-connected read, or a high-gas RPC.
|
|
23
37
|
|
|
24
38
|
### The on-chain tokenURI points at `localhost`
|
|
25
39
|
The base URL baked on-chain is a localhost/placeholder (a dev-escape deploy, or a base set without a public host) → it resolves for no one. Re-point to a public resolver: `abx set-token-uri <addr> --uri https://<your-resolver>` (+ `set-contract-uri`), then `abx refresh`. A normal `deploy-code`/`deploy` **refuses** a localhost base — this only happens via the `ABX_DEV_ALLOW_LOCALHOST_URI` dev escape.
|
|
26
40
|
|
|
41
|
+
### `deploy-code` deployed the contract but the SETUP transaction failed
|
|
42
|
+
A code deploy is **two** transactions: create the clone, then one atomic setup `multicall` (script chunks + schemas + dependencies + the on-chain-URI legs + any reserve mints). When the second fails you own a contract that exists but has no program — `abx verify` reports no code, the live view 404s — and the CREATE2 salt for that address is **spent**, so the dry run's pinned-salt re-run command lands somewhere else now.
|
|
43
|
+
|
|
44
|
+
**The contract is recoverable. Do NOT redeploy, and do NOT hand-assemble a multicall with `cast`.** Finish it:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
abx deploy-code --resume <address> <the SAME content flags the original deploy used> # add --dry-run first
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
It reads what is already on-chain and sends only what is missing, in one transaction — so it is safe to run twice, and if nothing is missing it sends nothing and tells you so. Chunks are compared by **content** (a partial hand repair is respected), a schema that already exists is left alone, and reserve mints are a **shortfall** against current supply, never a re-send. You must pass the script/`--code-dir` again: those bytes are not recoverable from a failed transaction. `--salt`, `--721c`, `--bootstrap-factory` and `--mint-all` are refused — they describe how a contract is *created*, and 721C enrollment in particular can never be added after deploy.
|
|
51
|
+
|
|
52
|
+
If the DEPLOY (first) transaction is what failed, there is nothing to resume — no contract exists. Run a normal deploy.
|
|
53
|
+
|
|
27
54
|
### `abx index`/`abx verify` says "isn't registered"
|
|
28
55
|
Register the project on this node once: `abx add <addr>` (discovers the deploy block, indexes it). Then `index`/`verify` work. `state`/`tokenuri` never need this.
|