@artblocks/abx-cli 0.1.0-alpha.32 → 0.1.0-alpha.34
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 +64 -0
- package/dist/capabilities.d.ts +94 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +135 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +13 -17
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/feedback.d.ts +7 -0
- package/dist/commands/feedback.d.ts.map +1 -0
- package/dist/commands/feedback.js +147 -0
- package/dist/commands/feedback.js.map +1 -0
- package/dist/commands/reads.js +1 -1
- package/dist/commands/reads.js.map +1 -1
- package/dist/commands/scaffold.d.ts +9 -1
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +60 -13
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/service.d.ts.map +1 -1
- package/dist/commands/service.js +5 -4
- package/dist/commands/service.js.map +1 -1
- package/dist/flag-allowlists.d.ts.map +1 -1
- package/dist/flag-allowlists.js +19 -0
- package/dist/flag-allowlists.js.map +1 -1
- package/dist/main.js +45 -11
- package/dist/main.js.map +1 -1
- package/dist/ownerops.d.ts +3 -3
- package/dist/ownerops.js +5 -5
- package/dist/ownerops.js.map +1 -1
- package/dist/remote.d.ts +5 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +31 -2
- package/dist/remote.js.map +1 -1
- package/dist/scaffold.js +1 -1
- package/dist/scaffold.js.map +1 -1
- package/dist/update-check.d.ts +6 -1
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +40 -17
- package/dist/update-check.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +174 -526
- package/skill/agents/openai.yaml +4 -0
- package/skill/reference/capabilities.md +171 -285
- package/skill/reference/code.md +210 -0
- package/skill/reference/creator-token.md +90 -95
- package/skill/reference/deploy.md +167 -0
- package/skill/reference/diagnose.md +165 -0
- package/skill/reference/hosting.md +148 -126
- package/skill/reference/operate.md +181 -0
- package/skill/reference/services.md +76 -0
- package/skill/reference/setup.md +108 -62
- package/skill/reference/code-projects.md +0 -368
- package/skill/reference/decisions.md +0 -182
- package/skill/reference/operating.md +0 -220
- package/skill/reference/troubleshooting.md +0 -65
|
@@ -1,182 +0,0 @@
|
|
|
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
|
-
| **Small static** (≲ 117 KB/file reads on any endpoint — bigger is supported, see below) | **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](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. Small content only, and the binding limit is READ gas, not storage cost (below).
|
|
18
|
-
2. **Image off-chain, JSON on-chain, no server** (`--onchain-uri --backend arweave|ipfs|cloud`) — the sweet spot for static content. Arweave/IPFS (permanent, content-addressed) or your S3/CDN (`--backend cloud --public-base <url>`; centralized, mutable). Many files → one `url-template`.
|
|
19
|
-
3. **Remote resolver** (`--public-base-url` + a node) — for mutable/dynamic metadata; **self-hosted** (`abx deploy-resolver`, you run it) or a **managed provider** (an API key, they run it). Same interface — swap with one re-point.
|
|
20
|
-
4. **Inline SVG on-chain** — self-contained vector content inlined into `tokenURI`. For a **tiny 1/1** (under ~0.5 KB — a one-line SVG) that's `abx deploy … --onchain-uri`; **at a few KB and up, reach for `--onchain-image --compress fastlz` instead** (cheaper to write past ~0.5 KB — see *Inline vs reader* below, and note the 2.4 KB example SVG lands on that side). Same flag for a **Series** of tiny SVGs: `abx deploy-series … --onchain-image --compress fastlz` (bare `--onchain-uri` on a folder does NOT inline the images — it's the image-custody flag `--onchain-image` that puts SVG bytes on-chain per token).
|
|
21
|
-
|
|
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
|
-
## `--copies` (ERC-1155 editions) — custody is NOT orthogonal to the shape
|
|
28
|
-
|
|
29
|
-
Adding `--copies <n|open>` does not leave the four patterns above intact. Three combinations are
|
|
30
|
-
**refused** today, and each one is something you would reach for first. Check this BEFORE you promise a
|
|
31
|
-
creator a lane — the refusal lands at deploy time, and two of these have already pushed agents into the
|
|
32
|
-
wrong architecture:
|
|
33
|
-
|
|
34
|
-
| Want | `deploy --copies` (1/1-edition) | `deploy-series --copies` (EditionImage) | `deploy-code --copies` (EditionCode) |
|
|
35
|
-
|---|---|---|---|
|
|
36
|
-
| **Pattern 1** — bytes fully on-chain (`--onchain-image`) | ✅ hot + `--sign` | ✅ hot + `--sign` | n/a |
|
|
37
|
-
| **Pattern 2** — image off-chain, JSON on-chain, **no server** (`--onchain-uri --backend arweave\|ipfs`) | ✅ | ✅ (uniform extensions → ONE `url-template` collection field, O(1)) | n/a |
|
|
38
|
-
| **Pattern 4** — inline SVG on-chain (`--onchain-uri`, SVG content) | ✅ | ✅ (every id must be SVG) | n/a |
|
|
39
|
-
| **Pattern 3** — resolver (`--public-base-url`) | ✅ | ✅ | ✅ |
|
|
40
|
-
| on-chain library dep (`--dep p5@…`) | n/a | n/a | ✅ **Sepolia only** (no registry on Base Sepolia) |
|
|
41
|
-
| build directory (`--code-dir`) · field renderers | n/a | n/a | ❌ refused — `--script` only |
|
|
42
|
-
|
|
43
|
-
**One lane genuinely can't be signed offline:** `--onchain-image` is refused on the **cold**
|
|
44
|
-
(`--unsigned`) lane everywhere, 721 and edition alike — staging is a sequence where each chunk tx's
|
|
45
|
-
receipt feeds the next, so use the hot lane or `--sign`.
|
|
46
|
-
|
|
47
|
-
Otherwise custody is now fully symmetric with the 721 side, including the case that used to have no
|
|
48
|
-
answer: **"N works × M copies each" with raster images and no server** is
|
|
49
|
-
`abx deploy-series --dir <folder> --copies <n> --onchain-uri --backend arweave` — a uniform file
|
|
50
|
-
extension uploads the folder as ONE directory and bakes a single `{id}`-substituting collection field,
|
|
51
|
-
so the whole collection costs one field rather than N.
|
|
52
|
-
|
|
53
|
-
**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.)
|
|
54
|
-
|
|
55
|
-
**No-server tradeoff (patterns 1, 2, 4):** with the on-chain renderer only the *image* is off-chain — any **description / traits / animation_url live on-chain** (gas to write, permanent, lockable), vs a hosted resolver where they're free to edit. **PostParams are on-chain either way** — they live in the contract's own store and enumerate from it (`abx state` / `abx tokens`), so a chain-only reader sees the whole configuration with no server on *any* pattern; they just aren't copied into the metadata JSON. 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.
|
|
56
|
-
|
|
57
|
-
Get decisions 1–2 right before deploy (image commitment + resolver URL are written then; re-pointable, but):
|
|
58
|
-
|
|
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
|
-
- `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 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
|
-
- **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](hosting.md#arweave-via-turbo--the-easy-permanent-path-read-before-quoting-setup).
|
|
63
|
-
|
|
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.
|
|
65
|
-
- **First ask whether you need a host at all** — tiny content is cheaper and more durable on-chain (no host). For bigger content, Arweave (no host to run) beats a resolver unless you need mutability or serve many files.
|
|
66
|
-
- **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>`.
|
|
67
|
-
- **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](hosting.md#managed-providers--a-resolver-someone-else-runs---remote-name)). Or **self-host** (`abx deploy-resolver`, [hosting.md](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.
|
|
68
|
-
- **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.
|
|
69
|
-
|
|
70
|
-
**3. Identity** — `--name`, `--symbol`, `--royalty-bps` (default 500 = 5%), `--royalty-cap` (the permanent, **reduce-only** royalty ceiling — default 10%, or your royalty rate if higher; shown in the deploy readout, lowered later with `abx set-royalty-cap`), `--burnable` (opt-in: holders may burn their own token, default off), `--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/).
|
|
71
|
-
- **Credit + license** — deploy flags `--creator "…"` · `--license "…"` (also `--display-notes`, `--creator-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 creator|license --text "…"`. Reserved collection fields served in `contractURI`, on any type (1/1 · Series · code). Detail: [operating.md → Authorship + rights](operating.md#authorship--rights-credit--license).
|
|
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
|
-
|
|
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 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
|
-
- *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
|
-
|
|
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).
|
|
79
|
-
|
|
80
|
-
### The READ cost — who can see it, and why nothing is refused
|
|
81
|
-
|
|
82
|
-
Two axes, and only one of them is about size limits at all.
|
|
83
|
-
|
|
84
|
-
**WRITE is chunked and unbounded.** Content is split into 22,000-byte SSTORE2 chunks across separate
|
|
85
|
-
transactions, so no block gas limit binds it at any size. It is purely money: ~200 gas/byte, paid once.
|
|
86
|
-
|
|
87
|
-
**READ is one `eth_call`, and whether it succeeds belongs to whoever's endpoint is asking.** `tokenURI`
|
|
88
|
-
reassembles the whole document per call, measured at ~360,000–405,000 gas per KB across 10–100 KB and
|
|
89
|
-
climbing (~460,000/KB at 187 KB, ~510,000/KB at 256 KB) because EVM memory expansion is quadratic. Quote
|
|
90
|
-
it as a range, never one flat per-KB number. `inline` and `reader` measure within ~1% of each other up
|
|
91
|
-
to 75 KB, diverging to ~5% at 187 KB — so `--compress fastlz` makes content cheaper to *write* and not
|
|
92
|
-
one gas cheaper to *read*.
|
|
93
|
-
|
|
94
|
-
**The toolkit refuses NO size.** It used to refuse past 100 KB, reasoning from geth's 50M
|
|
95
|
-
`--rpc.gascap` default minus a margin because hosted providers "commonly cap lower". Measured
|
|
96
|
-
2026-08-24, they do not — and the refusal sat below every endpoint that could be found:
|
|
97
|
-
|
|
98
|
-
| Endpoint | `eth_call` cap | Content it serves |
|
|
99
|
-
|---|---|---|
|
|
100
|
-
| **`sepolia.base.org`** — the default endpoint of our default chain | **600M** | **~729 KB** |
|
|
101
|
-
| `base-sepolia.publicnode.com` · `base-sepolia.drpc.org` | 50M | ~117 KB |
|
|
102
|
-
| `ethereum-sepolia.publicnode.com` | 50M | ~117 KB |
|
|
103
|
-
|
|
104
|
-
So 50M is the **floor**, not the ceiling, and a fixed byte threshold could never express this. The CLI
|
|
105
|
-
now **measures the RPC you are actually using** (a state-override `eth_call` that sends nothing and
|
|
106
|
-
costs nothing) and reports it:
|
|
107
|
-
|
|
108
|
-
| Content per token | What the CLI says |
|
|
109
|
-
|---|---|
|
|
110
|
-
| ≲ **117 KB** (~50M gas) | a plain note with the write and read figures — every endpoint measured serves it |
|
|
111
|
-
| **above that** | ⚠ names your RPC's measured cap, what it can serve, and that a 50M-capped provider stops at ~117 KB and will show a **revert** instead of the token |
|
|
112
|
-
| **past your own RPC's cap** | ⚠ says so plainly — nothing you can point at renders it, your own tooling included |
|
|
113
|
-
|
|
114
|
-
**Your RPC is not the marketplace's RPC.** That is the distinction to carry into the conversation: a
|
|
115
|
-
measured cap proves *you* can read the token, never that OpenSea can. So the honest framing is
|
|
116
|
-
permanence versus reach — *"this will exist forever and a lot of viewers won't see it"* — and
|
|
117
|
-
`--backend arweave` (image off-chain, JSON still on-chain, no server) is the other side of that trade.
|
|
118
|
-
Say the numbers, name the trade, let the creator choose. Never call a supported size "impractical", and
|
|
119
|
-
never route them to another protocol for something this does.
|
|
120
|
-
|
|
121
|
-
**Three different limits get called "the gas limit", and conflating them is how this goes wrong.**
|
|
122
|
-
Measured 2026-08-24: an **`eth_call` cap** bounds a read (600M on `sepolia.base.org`, 50M on
|
|
123
|
-
publicnode/drpc) — the one that decides whether a token displays. An **`eth_estimateGas` cap** bounds
|
|
124
|
-
what can be *sent* (16,777,216 on every endpoint tried, which is why writes batch at 8M). A **block
|
|
125
|
-
gas limit** bounds a contract reading another contract *inside a transaction* — 1,200M on Base
|
|
126
|
-
Sepolia, 400M on Base, 60M on Ethereum and Sepolia, so a contract on our default chain can read
|
|
127
|
-
~1,100 KB in a tx, not the ~90 KB this file used to claim. Three limits, three orders of magnitude.
|
|
128
|
-
|
|
129
|
-
**Reach is fixable after the fact — the bytes are permanent either way.** The read cost decides
|
|
130
|
-
whether a token resolves *in one `eth_call`*, not whether the content is reachable. A resolver
|
|
131
|
-
(`abx deploy-resolver`, or a hosted one) reads on-chain content with **its own** RPC and serves it as
|
|
132
|
-
ordinary HTTP, so a marketplace fetches a URL and never makes the big call; `abx set-renderer <addr>
|
|
133
|
-
--off` points `tokenURI` at it while every byte stays exactly where it is. So keep the two questions
|
|
134
|
-
separate: **storage** is where the bytes live and whether they outlive you (on-chain is the strongest
|
|
135
|
-
answer at any size); **serving** is how a viewer gets them today (self-resolving is purest and has a
|
|
136
|
-
reach ceiling, a resolver has none and is a thing someone runs). Choosing on-chain does not commit a
|
|
137
|
-
creator to self-resolving forever.
|
|
138
|
-
|
|
139
|
-
One more thing not to garble: the gate is **per token**, so a 300-piece collection of 5 KB SVGs is
|
|
140
|
-
fine.
|
|
141
|
-
|
|
142
|
-
## Locks — what actually freezes, and what a buyer is really asking
|
|
143
|
-
|
|
144
|
-
Every lock in this toolkit freezes a **pointer or a stored value**, never *behavior*. Say "locked
|
|
145
|
-
metadata", never "immutable work" — the difference is the whole of this section, and a creator who
|
|
146
|
-
promises the second while shipping the first has mis-sold their collection.
|
|
147
|
-
|
|
148
|
-
**An *ungoverned* PostParam has no lock at all.** The owner's raw setter keeps writing it past every
|
|
149
|
-
metadata lock, and the generator keeps injecting the new value as `tokenData`. A *schema'd* param can
|
|
150
|
-
be welded permanently (`set-schema … :lock=now`, `abx retire-param`), which freezes that key's
|
|
151
|
-
**token-scope value** and its **schema** forever. So "no lock" is a statement about the ungoverned
|
|
152
|
-
store, never about a governed key.
|
|
153
|
-
|
|
154
|
-
**The documented exception to a weld: a contract-scope DEFAULT can still be deleted.**
|
|
155
|
-
`clearContractParam` deliberately sits outside the schema guard — it is the recovery path out of a
|
|
156
|
-
value poisoned before the schema existed. So an owner can set a collection-wide default, attach and
|
|
157
|
-
expire a schema on that key, sell tokens that inherit the default, and *then* clear it; every token
|
|
158
|
-
with no override of its own changes. Clearing can only *remove* a fallback — never forge a value,
|
|
159
|
-
never bypass an auth rule, never touch a token-scope value already written. **If a creator is
|
|
160
|
-
promising a frozen collection-wide value, write it per token** (governed, welded) rather than leaving
|
|
161
|
-
it as an inherited default.
|
|
162
|
-
|
|
163
|
-
**A `Registry` dependency's bytes are re-fetched on every read.** `lock-dependencies` pins which
|
|
164
|
-
library a ref *means*, not what it returns. `abx verify`'s `chain-complete` is a claim about where
|
|
165
|
-
bytes come from, not that they are frozen.
|
|
166
|
-
|
|
167
|
-
**The one lock a BUYER asks about is `lock-param-hooks`.** A code project's `--transfer` hook is a
|
|
168
|
-
**veto** — its revert fails a transfer, and mints too — so until the hooks are frozen the owner holds
|
|
169
|
-
a standing power over whether a collector can ever sell. Freezing an empty set is how a creator proves
|
|
170
|
-
they never will.
|
|
171
|
-
|
|
172
|
-
**Every lock freezes a POINTER, not behavior.** A locked hook, renderer or reader is still a
|
|
173
|
-
*contract*, and a contract can be a proxy whose code is upgraded later: `paramHooksLocked()` keeps
|
|
174
|
-
reading `true` while a re-pointed proxy starts reverting every transfer. The protocol does not try to
|
|
175
|
-
detect proxies on chain (complex, incomplete) — it defines locks as pointer locks and discloses it. So
|
|
176
|
-
**for any permanence claim, the hook/renderer must itself be an immutable deployment**: say "these
|
|
177
|
-
exact three addresses can never change" and check what is behind them, never "the behavior is frozen".
|
|
178
|
-
|
|
179
|
-
A token that live-adapts to chain state is a legitimate thing to build. Just don't sell a frozen one as
|
|
180
|
-
frozen unless it is. See also
|
|
181
|
-
[code-projects.md → hooks](code-projects.md#live-data-the-augment-hook--the-hook-is-the-setting) and
|
|
182
|
-
https://abx.docs.artblocks.io/protocol/owner-powers/
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
# Operating a project (owner operations)
|
|
2
|
-
|
|
3
|
-
[← back to SKILL.md](../SKILL.md)
|
|
4
|
-
|
|
5
|
-
After launch the owner operates the project. Each command builds a tx, signs it via the [lane](../SKILL.md#gate-2--who-signs-three-lanes) you pick, then re-indexes so the served state updates. All are **owner-only** — the signer must be the contract owner (or, for `transfer`, the token holder).
|
|
6
|
-
|
|
7
|
-
**Check state before and after any op with `abx state <addr>`** — a read-only, on-chain snapshot (no tx, no local index): owner · supply (minted / max, nextTokenId) · paused · minter · primary payee · royalty · royalty cap · burnable · renderer. It's the fast way to confirm an op landed (e.g. after `mint`/`unpause`), and to see who can currently mint — prefer it over piecing state together from per-token `abx tokenuri` calls. Works for a Series or a 1/1.
|
|
8
|
-
|
|
9
|
-
| Command | Does | Decision to surface |
|
|
10
|
-
|---|---|---|
|
|
11
|
-
| `abx mint <addr> [--to 0x.. (default owner)]` | issue a token — the 1/1's token #0, or a Series' next-in-order token | who receives it (owner = pre-mint; a buyer = primary sale) |
|
|
12
|
-
| `abx submit-app <collection> --name … --summary … --description …` | list that collection in the ABX App Store (mint + catalog copy). Optional; not part of deploy. Re-run writes params only if already claimed | confirm store copy (not the collection name); they must own the collection |
|
|
13
|
-
| `abx refresh <addr> [--token 0]` | ask marketplaces to (re)index the token's metadata | none — run it right after a mint or URI change |
|
|
14
|
-
| `abx transfer <addr> --to 0x.. [--token 0]` | settle a sale/gift — move the token to a new holder | who the recipient is (price/terms are off-chain) |
|
|
15
|
-
| `abx set-token-uri <addr> --uri <base>` | re-point the resolver **base** (e.g. move the node); contract derives `{base}/{chainId}/{address}/{tokenId}` | the new base URL |
|
|
16
|
-
| `abx set-token-uri <addr> --override ipfs://<cid> [--token 0]` | pin ONE token to a fixed locator — the immutable `ipfs://` escape; `""` clears it | confirm — this exits the spec; pair with `lock-uri` |
|
|
17
|
-
| `abx set-contract-uri <addr> (--uri <base> \| --override <uri>)` | re-point the collection base, or pin a fixed locator | the new base / locator |
|
|
18
|
-
| `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 |
|
|
19
|
-
| `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`) |
|
|
20
|
-
| `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 |
|
|
21
|
-
| `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 |
|
|
22
|
-
| `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>` |
|
|
23
|
-
| `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 |
|
|
24
|
-
| `abx set-royalty <addr> --bps <0-10000> [--receiver 0x..]` | change the royalty (receiver defaults to current) | the rate, and whether the payee changes |
|
|
25
|
-
| `abx set-royalty-cap <addr> --cap <0-10000>` | **lower** the royalty ceiling (owner-only, reduce-only) — the cap fixed at deploy can only come DOWN | confirm — **one-way**: it never rises, and is refused if it would drop below the live royalty rate |
|
|
26
|
-
| `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 |
|
|
27
|
-
| `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 |
|
|
28
|
-
| `abx set-gateway <addr> [--ipfs <prefix>] [--arweave <prefix>]` | repoint where this collection's `ipfs`/`arweave` fields are SERVED from (`none` clears → public default) | the prefix (must be the whole thing, e.g. `https://you.mypinata.cloud/ipfs/`) |
|
|
29
|
-
| `abx lock-field <addr> --field <name> [--collection]` | **freeze a field forever** (all its representations) | confirm — irreversible |
|
|
30
|
-
| `abx lock-script <addr>` | **freeze the on-chain program (script chunks) forever** — the lock that freezes a code project's *work* (`setScriptChunk`/`removeLastScriptChunk` revert after) | confirm — irreversible; SeriesCode/EditionCode only |
|
|
31
|
-
|
|
32
|
-
**There is no `abx replace-script`.** Until `lock-script`, the contract still accepts `setScriptChunk`, but the supported CLI does not expose post-deploy rewrite (only `deploy-code --resume` for an *incomplete* setup). To change a live program, deploy a new contract. Don't hand-roll ABI writes.
|
|
33
|
-
|
|
34
|
-
| `abx lock-dependencies <addr>` | **freeze the dependency set forever** (library list + registry pointer) | confirm — irreversible |
|
|
35
|
-
| `abx lock-param-hooks <addr>` | **freeze the three param hooks forever** (configure/augment/transfer) — the lock a *buyer* cares about: the transfer hook is a veto over transfers **and** mints, so until this is sent the owner can arm one | confirm — irreversible; SeriesCode/EditionCode only. Freezing an EMPTY set is the proof "no transfer veto can ever be armed"; a hook already set keeps its veto. No carve-out: hooks are owner-only forever and a freeze holds against everyone, including on an **ownerless** project — unlike the 721C validator, don't conflate them ([why](code-projects.md#live-data-the-augment-hook--the-hook-is-the-setting)) |
|
|
36
|
-
| `abx set-seed-source <addr> <0x..\|canonical\|none>` | re-point a **code project's** mint-seed source — the creator's own `IAbxSeedSource` (commit-reveal / VRF), the shared `canonical` one, or `none`. Probed first: it must answer `seed(uint256,address)` with 32 bytes or the command refuses | **future mints only** (assigned seeds are settled) — on a part-sold drop the collection then spans two sources; pause and say so. Code projects only |
|
|
37
|
-
| `abx set-admin <addr> --to 0x..` | hand over contract ownership | confirm the new admin address |
|
|
38
|
-
|
|
39
|
-
**`lock-field` / `lock-uri` / `lock-script` / `lock-dependencies` / `lock-param-hooks` are permanent — always confirm with the human first.**
|
|
40
|
-
|
|
41
|
-
**A code project isn't frozen until its PROGRAM is.** `lock-field`/`lock-uri` freeze *metadata*; they do **not** touch the script chunks. For a generative/code drop (`SeriesCode`/`EditionCode`) the work lives in the on-chain program, and the owner can keep rewriting it (`setScriptChunk`/`removeLastScriptChunk`) until you run `abx lock-script <addr>`. The full set is **`lock-script` + `lock-dependencies` + `lock-field`/`lock-uri`** (and `set-schema … lock=now` for any param whose value should freeze too). `abx verify` reports the script/dependency lock state so you can see what's still mutable.
|
|
42
|
-
|
|
43
|
-
**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/
|
|
44
|
-
|
|
45
|
-
**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:
|
|
46
|
-
|
|
47
|
-
- every `configure-param` on the key reverts `ParamLockExpired`;
|
|
48
|
-
- **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.)
|
|
49
|
-
- the owner's raw setter was already closed on any schema'd key (`SchemaGoverned`), so there is no back door.
|
|
50
|
-
|
|
51
|
-
`lock=` is **monotonic**: a later `set-schema` may only move the deadline *earlier*, never later and never back to open (`ParamLockNotExtendable`). So the weld cannot be un-welded, and telling a creator "this is permanent" is accurate. (`lock-field` refuses a declared param key rather than letting you lock the wrong thing.) **Two honest caveats to keep, both worth saying out loud before a creator advertises a frozen value:**
|
|
52
|
-
|
|
53
|
-
- a weld freezes the *stored* value and schema — an `--augment` hook can still override what a reader is served, until `lock-param-hooks` freezes the hook set (and that lock freezes the hook **addresses**, not the code behind them — an upgradeable proxy at a locked address can still change behavior, so permanence needs immutable hook contracts);
|
|
54
|
-
- a weld covers **token-scope** writes; a **contract-scope default** on the same key can still be cleared. `clearContractParam` deliberately sits outside the schema guard — it is the only recovery path from a value poisoned before the schema existed — so an owner may set a collection-wide default, weld the key, sell tokens that inherit it, and then delete the default, changing every token that never wrote its own value. Clearing can only *remove* a fallback (it can't forge a value, bypass an auth rule, or touch a token-scope value already written). **If a collection-wide value must be permanent, write it per token through the governed path** instead of leaving it as an inherited default.
|
|
55
|
-
|
|
56
|
-
### Authorship + rights (credit + license)
|
|
57
|
-
|
|
58
|
-
The reserved **collection** keys are `creator`, `display_notes`, `creator_links`, `license` (a rights *identifier* like `CC0-1.0` / `CC-BY-NC-4.0`). `--creator`/`--license`/`--display-notes`/`--creator-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 creator --text "Casey Reas"` directly.
|
|
59
|
-
|
|
60
|
-
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.
|
|
61
|
-
|
|
62
|
-
### Attaching files — the data plane
|
|
63
|
-
|
|
64
|
-
A token anchors **named, typed files**; the served JSON's **`artifacts`** array is the *complete* listing, each entry `{key, mimeType, uri}`. `image`/`animation_url` are reserved members; everything else is a file you attach. ([SKILL primer](../SKILL.md#what-a-token-carries--files-beyond-the-image-the-data-plane); spec: [data-plane](../../../../specs/protocol/data-plane.md).)
|
|
65
|
-
|
|
66
|
-
- **No URL yet?** `attach` takes a locator you already host. **`abx storage upload <path> [--backend ipfs|arweave|cloud]`** uploads one file and prints its `ipfs://`/`ar://` (or cloud) locator — the same backends `deploy` uses (Arweave = pay-once permanent; `fs` is refused, it has no public URL). Pipe that locator straight into `attach`.
|
|
67
|
-
- **`abx attach <addr> <key> <uri>`** is the verb. `<key>` = any name you pick (`print`, `certificate`, `stems`, `readme`, `source`) → the manifest entry's key. It's a correct-by-construction wrapper over `set-field`:
|
|
68
|
-
- **representation auto-detected** from the URI scheme — `ipfs://` → `ipfs`, `ar://` → `arweave`, `https://` → `url`. An unrecognized scheme is refused (never silently stored). No `--representation` to guess.
|
|
69
|
-
- **`mimeType` is declared from the URL extension** (`…/master.tiff` → `image/tiff`, `…/coa.pdf` → `application/pdf`). No extension → `application/octet-stream` (a warning fires). **Point the URI at the file itself with its real extension** — the on-chain field has no MIME slot; the extension is the declaration.
|
|
70
|
-
- **`--file <path>`** stores tiny bytes ON-CHAIN (SSTORE2) instead — for a small file with no external host (≈200 gas/byte, so locators are the norm for anything non-trivial).
|
|
71
|
-
- scope: `--token <id>` (default 0) or `--collection`; any lane; `--dry-run` previews the tx.
|
|
72
|
-
**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).
|
|
73
|
-
|
|
74
|
-
- **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.*
|
|
75
|
-
- **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.
|
|
76
|
-
- **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.
|
|
77
|
-
- **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.
|
|
78
|
-
- **`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.
|
|
79
|
-
- **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.
|
|
80
|
-
- **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.*
|
|
81
|
-
|
|
82
|
-
### Series-only owner ops
|
|
83
|
-
|
|
84
|
-
For a multi-token Series ([SKILL → Series](../SKILL.md#series-multi-token-drops)). The mint/minter ops accept the owner **or** an authorized minter as signer; the rest are owner-only.
|
|
85
|
-
|
|
86
|
-
| Command | Does | Decision to surface |
|
|
87
|
-
|---|---|---|
|
|
88
|
-
| `abx mint <addr>` | mint the **next** sequential token (metadata = token id) | who receives it |
|
|
89
|
-
| `abx mint <addr> --count <n>` | mint `n` tokens **in order** (metadata = token id) in one tx | how many |
|
|
90
|
-
| `abx set-minter <addr> --minter 0x..\|none` | set the **single** authorized minting contract (replaces any previous; `none` clears to owner-only). Multiple mechanics → point at a router downstream | the minter address |
|
|
91
|
-
| `abx pause <addr>` · `abx unpause <addr>` | the mint safety switch: **paused ⇒ owner-only minting** (minter + public blocked); unpause to open. Owner can always mint (reserves/config) | when to open the public mint |
|
|
92
|
-
| `abx set-primary-payee <addr> --payee 0x..\|none` | declare where primary-sale proceeds go (`none` clears) | the payout destination (a wallet or splitter) |
|
|
93
|
-
| `abx set-max-invocations <addr> --max <N>` | **lower** the supply cap (monotonic) — e.g. close an open edition early at the minted count | confirm — can only decrease, never below what's minted |
|
|
94
|
-
|
|
95
|
-
`set-field` / `lock-field` work per **token** for a Series — pass the token id (`--field image --file … ` targets that token's work); tokens share the collection scope with `--collection`.
|
|
96
|
-
|
|
97
|
-
### Edition owner ops
|
|
98
|
-
|
|
99
|
-
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 work (transfer, royalty, fields, locking); these are the edition-only twins, keyed by **id** instead of a single owner.
|
|
100
|
-
|
|
101
|
-
| Command | Does | Decision to surface |
|
|
102
|
-
|---|---|---|
|
|
103
|
-
| `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 works; see `abx tokens <addr>`) | which id, how many copies, who receives them |
|
|
104
|
-
| `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 |
|
|
105
|
-
| `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 |
|
|
106
|
-
| `abx ping-uri <addr> --token-ids <csv\|range>` | **owner-only**: re-emit the per-id refresh events 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. Always emits ERC-4906 `MetadataUpdate(id)`; also emits the native ERC-1155 `URI(uri(id), id)` **unless an on-chain renderer is configured** (that event would carry the whole rendered document, at 8 gas per byte of log data) | sign as the owner — run it when a marketplace/indexer only honors the per-id event and missed the re-point |
|
|
107
|
-
| `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 |
|
|
108
|
-
| `abx minter buy <addr> --token-id <n> --quantity <n>` | buy `n` copies of that id in one purchase — pays `price × quantity` | how many copies |
|
|
109
|
-
| `abx tokens <addr> --holder 0x..` | **read** (no tx): adds a `held` column — `balanceOf(holder, id)` per id, straight from the contract. The ONLY way to ask what one address holds: `supply/maxSupply` says how many copies exist, never who has them (an id has many concurrent holders and the set isn't enumerable from a head read) | none — this is the "did my transfer actually land?" read |
|
|
110
|
-
|
|
111
|
-
`--token-id` is required on every edition op above that names a specific work, 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.
|
|
112
|
-
|
|
113
|
-
**Two numbers, not one — get this straight with the creator BEFORE deploying.** An edition has an **id space** (how many distinct works) and a **per-id copy cap** (`--copies`). They multiply. On `deploy-code --copies 100` the id space comes from `--max`, which **defaults to 16** — so "100 copies" without `--max` means *up to 16 different generative variations × 100 copies each*, i.e. as many as 1600 tokens, not 100. A creator who says "an edition of 100" almost always means **one** work: pass `--max 1 --copies 100`. The dry-run states the real shape (`up to N id(s), M copies each`) — read that line back to them before any spend. Same arithmetic on `deploy-series --copies` (ids come from the folder) and `deploy --copies` (a single id, so `--copies` IS the total).
|
|
114
|
-
|
|
115
|
-
### Selling — the shared fixed-price minter
|
|
116
|
-
|
|
117
|
-
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).
|
|
118
|
-
|
|
119
|
-
> **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.
|
|
120
|
-
>
|
|
121
|
-
> **The edition minter is the one exception — it DOES sell a "1/1"-shaped work directly.** `AbxFixedPriceMinter1155` targets `IAbxEditionMint`, which `OneOfOneEdition` implements (id space fixed to `0`), so a priced open/limited edition of a single work 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`.
|
|
122
|
-
|
|
123
|
-
| Command | Does | Notes |
|
|
124
|
-
|---|---|---|
|
|
125
|
-
| `abx minter configure <token> --price <eth> --allocation <n>` | set the sale (owner-only, defers to `token.owner()`); resolves-or-deploys the shared minter | `--erc20 0x..` prices in a token (else ETH); `--price-raw <units>` for exact/non-18-decimal amounts; `--allocation` caps what THIS minter may sell |
|
|
126
|
-
| `abx minter show <token>` | read the sale + readiness (configured? assigned on token? payee set? paused? supply) | read-only — run it to check state |
|
|
127
|
-
| `abx minter buy <token> [--to 0x..]` | buy **one** token (public; any funded signer) | ETH sales attach the price; ERC-20 needs a prior `approve` to the minter |
|
|
128
|
-
|
|
129
|
-
**Go-live sequence (you run all of it):**
|
|
130
|
-
```bash
|
|
131
|
-
abx minter configure <token> --price 0.05 --allocation 100 # prints the shared minter address + the remaining steps
|
|
132
|
-
abx set-minter <token> --minter <that-address> # grant mint rights on the token (a SEPARATE grant from configure)
|
|
133
|
-
abx set-primary-payee <token> --payee 0x… # proceeds destination — sales REVERT without one
|
|
134
|
-
abx unpause <token> # open the sale (the token's pause is the on/off switch)
|
|
135
|
-
```
|
|
136
|
-
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.
|
|
137
|
-
|
|
138
|
-
> **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>`.
|
|
139
|
-
|
|
140
|
-
**Reserves and airdrops — the two recipes creators actually ask for.** Both are plain `mint` calls;
|
|
141
|
-
neither needs a minter. Do not invent a flag for them.
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
abx mint <token> --to 0xALICE # airdrop: one call per recipient, N recipients ⇒ N calls
|
|
145
|
-
abx mint <token> --to 0xBOB
|
|
146
|
-
abx mint <token> --count 5 # reserve: 5 tokens to yourself (Series mints in order)
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
The reserve decision is a **choice between two options — present both, don't pick silently**:
|
|
150
|
-
|
|
151
|
-
1. **Pre-mint the reserve** (`abx mint --count k` before `unpause`) — the k pieces are yours, off the
|
|
152
|
-
market, provably held. The sale then sells whatever is left. Simplest to explain to a buyer.
|
|
153
|
-
2. **Under-allocate the minter** (`--allocation <supply − k>`) — nothing is minted up front, and the
|
|
154
|
-
last k stay unminted until you decide. Keeps your options open; costs nothing until you use it.
|
|
155
|
-
|
|
156
|
-
They compose (pre-mint some, under-allocate the rest), and both bind against `maxInvocations` — the
|
|
157
|
-
tighter of allocation and supply always wins. An airdrop after the sale opens is fine too: owner
|
|
158
|
-
minting is not gated by `paused` (pausing blocks *minter* sales, not the owner).
|
|
159
|
-
|
|
160
|
-
**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`.
|
|
161
|
-
|
|
162
|
-
### A mint website for buyers — `abx mint-page`
|
|
163
|
-
|
|
164
|
-
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.
|
|
165
|
-
|
|
166
|
-
| | |
|
|
167
|
-
|---|---|
|
|
168
|
-
| `abx mint-page <token>` | scaffold the app into `mint-page/` (`--dir` to change; `--name`, `--rpc`, `--minter-contract` to override) |
|
|
169
|
-
| 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 |
|
|
170
|
-
|
|
171
|
-
- **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.
|
|
172
|
-
- **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 work, instead.
|
|
173
|
-
- **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.
|
|
174
|
-
- **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.
|
|
175
|
-
|
|
176
|
-
## Moving your hosting — two cases, don't conflate
|
|
177
|
-
|
|
178
|
-
- **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.
|
|
179
|
-
- **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**.
|
|
180
|
-
- **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.
|
|
181
|
-
|
|
182
|
-
### The cutover, after a clean `migrate`
|
|
183
|
-
|
|
184
|
-
New resolver now serves byte-identical metadata, but traffic still hits the old one. Flip it one way, by how the URL was baked on-chain:
|
|
185
|
-
- **Custom domain** (recommended — you baked `meta.you.xyz`): re-point DNS. On-chain base unchanged → **no tx, no gas**, reversible. This is *why* baking a domain you control matters.
|
|
186
|
-
- **Provider endpoint** (you baked `your-app.fly.dev`): no DNS to flip → re-point the base URI — `abx set-token-uri <addr> --uri <new-base>` (+ `set-contract-uri`). A wallet-signed owner tx; takes a marketplace refresh to propagate.
|
|
187
|
-
- **Keep the old resolver running** until DNS/base-URI propagates. Prefer the domain path for anything real.
|
|
188
|
-
|
|
189
|
-
### Source-only ("node-custody") images — `migrate` handles these automatically
|
|
190
|
-
|
|
191
|
-
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.
|
|
192
|
-
|
|
193
|
-
## Listing in the ABX App Store — `abx submit-app`
|
|
194
|
-
|
|
195
|
-
**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.
|
|
196
|
-
|
|
197
|
-
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.
|
|
198
|
-
2. **You run the command** (same signing lane as the deploy). `--dry-run` first, then send:
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
abx submit-app <collection> \
|
|
202
|
-
--name "…" --summary "…" --description "…" \
|
|
203
|
-
--category Create --stage Prototype \
|
|
204
|
-
--url https://… \
|
|
205
|
-
--sign
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
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.
|
|
209
|
-
4. Humans can instead use the store's `/submit` page; same txs. Don't hand them a list of `configure-param` commands.
|
|
210
|
-
|
|
211
|
-
The wallet must **own the collection** at mint (the gate checks `isAbxClone` + `owner()`). After that, control follows the **entry token**.
|
|
212
|
-
|
|
213
|
-
## Deploy strategy — the mechanics
|
|
214
|
-
|
|
215
|
-
- **`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.
|
|
216
|
-
- **`--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.
|
|
217
|
-
- **`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.
|
|
218
|
-
- **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).
|
|
219
|
-
- A `--no-mint` deploy reconstructs + serves normally (dashboard shows #0 "not yet minted"; metadata/image already resolve).
|
|
220
|
-
- **Etherscan source is already verified** — your deploys are EIP-1167 clones Etherscan auto-recognizes as proxies of the verified implementation.
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# Troubleshooting — "my NFT looks wrong"
|
|
2
|
-
|
|
3
|
-
[← back to SKILL.md](../SKILL.md)
|
|
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 <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
|
-
|
|
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.
|
|
10
|
-
|
|
11
|
-
### Gray placeholder / no image on the marketplace
|
|
12
|
-
Walk the cause down:
|
|
13
|
-
- **On-chain renderer** (`abx state` shows renderer on-chain, e.g. an in-chain SVG or `--onchain-uri`): the image *always* resolves from chain — nothing is broken. The marketplace just hasn't recrawled → `abx refresh <addr>`. Confirm with `abx tokenuri` (you'll see the real `data:`/SVG image).
|
|
14
|
-
- **Code project, off-chain thumbnail** (rendered PNG): the still is rendered off-chain, so it needs (a) a **resolver** serving it and (b) an **effect runner** that rendered it. Check `abx verify` — "PLACEHOLDER / no render" means it was never rendered → run the runner (`abx effects` locally, or `abx deploy-effects`) or a one-shot `abx render <addr>`. If `abx tokenuri` shows the base URL is **`localhost`** or unreachable, the on-chain pointer is bad (see below).
|
|
15
|
-
- **Orphaned image** (`--onchain-uri` deployed with **no** `--image-base` and no resolver): the on-chain `image` points nowhere a marketplace can reach and `abx render` wrote to a local store the chain never names → the still is a placeholder *permanently*. Fix = a re-point tx: host the image (a bucket / resolver) then `abx set-field <addr> --field image …` / re-deploy with a destination. This is a deploy-time decision — surface it *before* deploying.
|
|
16
|
-
- Do **not** reach for `abx index --full` or a redeploy for a render/refresh problem.
|
|
17
|
-
|
|
18
|
-
### I changed the metadata but the marketplace shows the old value
|
|
19
|
-
- **First confirm the change landed:** `abx tokenuri <addr> --token N` (or the resolver's JSON) — if it shows the NEW value, the write worked, and that field's `abx_provenance` entry names the route it took (`source` = `inline`/`reader`/`renderer`/`url`/…, with a `note` like *"stored on chain"*). The gap is then 100% the marketplace's cache. (There is no `onChain` boolean in a provenance entry: the contract can't tell whether a value *resolves* on chain without interpreting it. Whether a value is a self-contained `data:` URI or a pointer is visible in the value itself. On a **resolver**, the extra `status` field is the one that answers "was this checked" — `verified`/`mismatch`/`anchored`/`off-chain`.)
|
|
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.
|
|
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
|
-
**Both lanes emit ERC-4906** — an edition (`OneOfOneEdition`/`EditionImage`/`EditionCode`) advertises
|
|
25
|
-
`0x49064906` and pings `MetadataUpdate(id)` / `BatchMetadataUpdate(0, max)` just like a 721, so a
|
|
26
|
-
4906-aware consumer does see the change. What an edition *also* has is ERC-1155's native
|
|
27
|
-
`URI(string,uint256)`, and that one is **not** emitted everywhere: a contract-wide re-point
|
|
28
|
-
(`abx set-token-uri` / `abx set-renderer`) has no range form of `URI` to express "every id may have
|
|
29
|
-
changed" (looping the whole id space would be a block-gas trap), and a per-id override write skips it
|
|
30
|
-
when an on-chain renderer is configured (the event would carry the entire rendered document). So a
|
|
31
|
-
marketplace/indexer that honors *only* the native per-id event can miss it. Fix: `abx ping-uri <addr>
|
|
32
|
-
--token-ids <csv|range>` re-emits the per-id refresh events for the ids you name — **owner-only** (it
|
|
33
|
-
used to be permissionless; with an on-chain renderer, letting anyone make an indexer render a whole
|
|
34
|
-
document per id was an asymmetric spam primitive), so sign it as the collection owner. It always emits
|
|
35
|
-
ERC-4906 `MetadataUpdate(id)`; the native `URI(uri(id), id)` follows only when there is **no** on-chain
|
|
36
|
-
renderer configured — with one, that event would carry the entire rendered document.
|
|
37
|
-
(Heads-up: `abx refresh`'s own advisory text still says an edition emits no ERC-4906. That line is
|
|
38
|
-
stale against the contracts; the `ping-uri` advice it gives is still the right next step.)
|
|
39
|
-
|
|
40
|
-
### "It's registered but serves nothing" / the add seemed to hang
|
|
41
|
-
**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.
|
|
42
|
-
|
|
43
|
-
**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.
|
|
44
|
-
|
|
45
|
-
### `abx tokenuri` / Etherscan reverts on a fully-on-chain code project
|
|
46
|
-
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.
|
|
47
|
-
|
|
48
|
-
### The on-chain tokenURI points at `localhost`
|
|
49
|
-
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.
|
|
50
|
-
|
|
51
|
-
### `deploy-code` deployed the contract but the SETUP transaction failed
|
|
52
|
-
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.
|
|
53
|
-
|
|
54
|
-
**The contract is recoverable. Do NOT redeploy, and do NOT hand-assemble a multicall with `cast`.** Finish it:
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
abx deploy-code --resume <address> <the SAME content flags the original deploy used> # add --dry-run first
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
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.
|
|
61
|
-
|
|
62
|
-
If the DEPLOY (first) transaction is what failed, there is nothing to resume — no contract exists. Run a normal deploy.
|
|
63
|
-
|
|
64
|
-
### `abx index`/`abx verify` says "isn't registered"
|
|
65
|
-
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.
|