@artblocks/abx-cli 0.1.0-alpha.31 → 0.1.0-alpha.32
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 +161 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/deploy.js.map +1 -1
- package/dist/flag-allowlists.js +1 -1
- package/dist/flag-allowlists.js.map +1 -1
- package/dist/flags.d.ts +0 -4
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +0 -6
- package/dist/flags.js.map +1 -1
- package/dist/main.js +9 -11
- package/dist/main.js.map +1 -1
- package/dist/ownerops.d.ts +30 -23
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +80 -51
- package/dist/ownerops.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +50 -23
- package/skill/reference/capabilities.md +301 -0
- package/skill/reference/code-projects.md +1 -1
- package/skill/reference/decisions.md +63 -55
|
@@ -10,8 +10,8 @@ Master call is **custody × mutability**:
|
|
|
10
10
|
|
|
11
11
|
| | **Mutable** (name/desc/traits may change) | **Immutable** (never changes) |
|
|
12
12
|
|---|---|---|
|
|
13
|
-
| **Small static** (≲
|
|
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](
|
|
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
15
|
|
|
16
16
|
**Four patterns, by where bytes live × how `tokenURI` resolves:**
|
|
17
17
|
1. **Fully on-chain** (`--onchain-image`) — bytes + JSON on-chain. Small content only, and the binding limit is READ gas, not storage cost (below).
|
|
@@ -59,16 +59,16 @@ Get decisions 1–2 right before deploy (image commitment + resolver URL are wri
|
|
|
59
59
|
**1. Storage permanence** — where bytes live. Not irreversible: bytes are content-addressed by their on-chain keccak, so start on one backend and move later (`abx verify` confirms the hash). Don't let it block a first deploy.
|
|
60
60
|
- `arweave` = pay-once permanent, no recurring fee. `cloud` (S3/R2) = durable, you maintain it. `ipfs` = decentralized, you pin it. `fs` = zero-config start, dies with the disk → move before it matters.
|
|
61
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](
|
|
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
63
|
|
|
64
64
|
**2. Public host URL — and who runs the resolver** (**off-chain custody only**). Baked into `tokenURI` at deploy, so the CLI **refuses an off-chain deploy without a public URL** (`ABX_PUBLIC_BASE_URL` or `--public-base-url https://…`) and **never bakes localhost** (that token resolves for no one). No exceptions.
|
|
65
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
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](
|
|
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
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
69
|
|
|
70
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](
|
|
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
72
|
- **Propose a real name/symbol and confirm — never silently bake a generic folder-name guess.** A folder called `series`/`images`/`photos` infers junk ("Series" / "SRS"), and on all three deploy commands the CLI *refuses* a real send that would bake its own placeholder identity (`--name`/`--symbol` missing) — because on-chain identity is effectively permanent. **In `--dry-run` the same check only warns** (so a preview still runs before you have the creator's title); don't read that warning as "the CLI allows it" — the real deploy stops. Suggest a specific title + a short ticker-style symbol drawn from the actual work, and get an explicit yes before deploying. Inference is a suggestion to confirm, not a default to ship — if the folder name is generic, say so and ask rather than proposing it.
|
|
73
73
|
|
|
74
74
|
**4. Image placement** — `--image <path>` (png · jpg · gif · svg · webp). The on-chain keccak256 (`image` field) anchors integrity; size is bounded by the backend, not the chain.
|
|
@@ -77,59 +77,67 @@ Get decisions 1–2 right before deploy (image commitment + resolver URL are wri
|
|
|
77
77
|
|
|
78
78
|
**Inline vs reader — default to the reader for real work.** `--onchain-uri` alone inlines the SVG (1 tx, ~700 gas/byte); `--onchain-image --compress fastlz` stages via SSTORE2 + a small `reader` pointer (~200 gas/byte, +1 tx) — **cheaper above ~0.5 KB** and widening with size. So: tiny (<~0.5 KB, a one-line SVG/short text) → `--onchain-uri` inline; real work (a few KB+) → `--onchain-image --compress fastlz`. **Never `--compress gzip` for an on-chain-rendered token** — gzip decodes off-chain only, breaking `--onchain-uri`; use fastlz (it decodes *in* the reader).
|
|
79
79
|
|
|
80
|
-
### The READ
|
|
80
|
+
### The READ cost — who can see it, and why nothing is refused
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
every call, measured at ~360,000–405,000 gas per KB across the 10–100 KB range that matters — and the
|
|
84
|
-
rate climbs with size** (~460,000/KB at 187 KB, ~510,000/KB at 256 KB), because EVM memory expansion is
|
|
85
|
-
quadratic. Quote it as a range, never as one flat per-KB number. `inline` and `reader` measure within
|
|
86
|
-
~1% of each other up to 75 KB (the cost is the renderer's string building, not the storage mechanism),
|
|
87
|
-
diverging to ~5% at 187 KB and ~10% at 256 KB as the chunk store's read loop dominates. So
|
|
88
|
-
`--compress fastlz` makes it cheaper to *write* and not one gas cheaper to *read*.
|
|
82
|
+
Two axes, and only one of them is about size limits at all.
|
|
89
83
|
|
|
90
|
-
|
|
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 |
|
|
91
99
|
|---|---|---|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
`
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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.
|
|
133
141
|
|
|
134
142
|
## Locks — what actually freezes, and what a buyer is really asking
|
|
135
143
|
|