@artblocks/abx-cli 0.1.0-alpha.16 → 0.1.0-alpha.18
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 +385 -0
- package/dist/commands/deploy.d.ts +181 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +2974 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/project.d.ts +134 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +919 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/reads.d.ts +64 -0
- package/dist/commands/reads.d.ts.map +1 -0
- package/dist/commands/reads.js +635 -0
- package/dist/commands/reads.js.map +1 -0
- package/dist/commands/scaffold.d.ts +55 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/scaffold.js +470 -0
- package/dist/commands/scaffold.js.map +1 -0
- package/dist/commands/service.d.ts +41 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +572 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/storage.d.ts +37 -0
- package/dist/commands/storage.d.ts.map +1 -0
- package/dist/commands/storage.js +335 -0
- package/dist/commands/storage.js.map +1 -0
- package/dist/config.d.ts +72 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +201 -4
- package/dist/config.js.map +1 -1
- package/dist/deps.d.ts +6 -39
- package/dist/deps.d.ts.map +1 -1
- package/dist/deps.js +4 -68
- package/dist/deps.js.map +1 -1
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -0
- package/dist/flags.d.ts +34 -0
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +74 -0
- package/dist/flags.js.map +1 -1
- package/dist/jsonout.d.ts +37 -0
- package/dist/jsonout.d.ts.map +1 -0
- package/dist/jsonout.js +68 -0
- package/dist/jsonout.js.map +1 -0
- package/dist/main.js +133 -6223
- package/dist/main.js.map +1 -1
- package/dist/mintpage.d.ts.map +1 -1
- package/dist/mintpage.js +34 -40
- package/dist/mintpage.js.map +1 -1
- package/dist/output.d.ts +137 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +504 -0
- package/dist/output.js.map +1 -0
- package/dist/ownerops.d.ts +37 -44
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +274 -233
- package/dist/ownerops.js.map +1 -1
- package/dist/preview.d.ts +8 -4
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +8 -17
- package/dist/preview.js.map +1 -1
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +10 -7
- package/dist/provision.js.map +1 -1
- package/dist/remote.d.ts +72 -7
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +211 -9
- package/dist/remote.js.map +1 -1
- package/dist/riskgate.d.ts +29 -0
- package/dist/riskgate.d.ts.map +1 -0
- package/dist/riskgate.js +91 -0
- package/dist/riskgate.js.map +1 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +12 -16
- package/dist/schema.js.map +1 -1
- package/dist/served.d.ts +30 -0
- package/dist/served.d.ts.map +1 -0
- package/dist/served.js +112 -0
- package/dist/served.js.map +1 -0
- package/dist/signer.d.ts +13 -0
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +42 -19
- package/dist/signer.js.map +1 -1
- package/dist/update-check.d.ts +27 -0
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +30 -0
- package/dist/update-check.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +9 -13
- package/skill/reference/code-projects.md +7 -6
- package/skill/reference/hosting.md +6 -6
- package/skill/reference/operating.md +2 -5
- package/skill/reference/setup.md +4 -3
- package/skill/reference/troubleshooting.md +14 -1
- package/dist/gas.d.ts +0 -63
- package/dist/gas.d.ts.map +0 -1
- package/dist/gas.js +0 -76
- package/dist/gas.js.map +0 -1
- package/dist/inspect.d.ts +0 -48
- package/dist/inspect.d.ts.map +0 -1
- package/dist/inspect.js +0 -290
- package/dist/inspect.js.map +0 -1
- package/dist/migrate.d.ts +0 -65
- package/dist/migrate.d.ts.map +0 -1
- package/dist/migrate.js +0 -180
- package/dist/migrate.js.map +0 -1
- package/dist/onchain-uri.d.ts +0 -81
- package/dist/onchain-uri.d.ts.map +0 -1
- package/dist/onchain-uri.js +0 -251
- package/dist/onchain-uri.js.map +0 -1
- package/dist/upload.d.ts +0 -28
- package/dist/upload.d.ts.map +0 -1
- package/dist/upload.js +0 -41
- package/dist/upload.js.map +0 -1
package/skill/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: abx-self-host
|
|
|
3
3
|
description: Launch and operate a self-hosted ABX NFT end to end with the ABX CLI (`abx`) on testnet — a 1/1 (`abx deploy`), a multi-token Series from a folder of media (`abx deploy-series`), or a generative/code drop (`abx deploy-code`). Covers on-chain vs off-chain metadata, storage custody (local disk, S3/R2, IPFS, Arweave), deploy + mint (now or pre-warmed at a predicted address), rendered thumbnails and on-chain traits for code art, primary sales via the shared fixed-price minter, and owner ops (transfer, refresh, re-point URIs, royalties, lock fields, pause/unpause, supply cap, delegate minting). Use when the user wants to self-host an ABX project, take an image to an NFT on testnet, deploy a collection from a folder of images, launch generative/code art, mint or run a primary sale, refresh a listing, operate a project they launched, choose a storage backend, stand up hosting they own, or point a project at a hosted/managed metadata provider with an API key.
|
|
4
4
|
compatibility: Drives the abx CLI (@artblocks/abx-cli). Co-versioned with it — install/refresh with `abx skill install` so this skill matches the CLI's `abx version`. Requires Node 22.5+.
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.1.0-alpha.
|
|
6
|
+
version: "0.1.0-alpha.18"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# ABX Self-Host Toolkit (`abx`)
|
|
@@ -15,10 +15,7 @@ L3 agentic surface: image → live self-hosted NFT the creator owns — a **1/1*
|
|
|
15
15
|
## Read first (every session)
|
|
16
16
|
|
|
17
17
|
- **YOU run the `abx` commands — never tell the creator to run one.** You have a shell; use it. Run `doctor`, `ls`, `--dry-run`, `tokenuri`, `state`, `refresh`, `balance`, etc. yourself and read the output — don't paste a command and wait for them to run it or copy back results. The creator's hands-on steps are approving in their **browser wallet** (`--sign`), giving you a value you asked for (their address, a name), and **looking at the art in `abx preview`** — you run that command, but the URL it prints is theirs to open and play with ([Phase 0](#phase-0--make-the-work-first-skip-every-gate-below-until-its-good)). Even "next steps" after a deploy: **run the read-only ones** (`tokenuri` to prove it resolves) and **offer to run** the actions (`refresh`, `unpause`, `mint`) — don't hand over a list of commands to run. Exceptions: a genuinely interactive/again-in-their-env command (an OS login, `gcloud auth`), and the **in-chain Solidity lane's Foundry step** (`forge build/test/deploy` a renderer — `abx` never compiles/deploys Solidity; see [Code projects](#code-projects-generative--code-based-drops)) — then run it if you have the tool, else hand it over.
|
|
18
|
-
- **Version drift —
|
|
19
|
-
- **Skill ⇄ CLI.** This skill is co-versioned with the CLI. Run `abx version`, compare to this file's frontmatter `metadata.version`. Differ → `abx skill install`, then reload the skill before continuing.
|
|
20
|
-
- **CLI ⇄ npm.** If any `abx` command prints **`update available`**, upgrade it yourself: `npm i -g @artblocks/abx-cli@latest` (global install) or `npm i --save-dev @artblocks/abx-cli@latest` (project-local — match however it was installed), then `abx skill install`, then reload. Do **not** relay the notice and carry on: a stale CLI can hold canonical addresses that have since moved, so it deploys against dead singletons, and a mid-run `… is not a function` is usually this. You have a shell — upgrading is your job, not the creator's.
|
|
21
|
-
- **Resolve the CLI before you install anything — local beats global.** Probe `./node_modules/.bin/abx version` (project-local), then `abx version` (global); only install if both miss, and default to the **project-local** `npm install --save-dev @artblocks/abx-cli`. The package is `@artblocks/abx-cli`; `@artblocks/abx-sdk` is the library and ships no binary. **Never probe with `npx abx`** (the bare name is a squat, and `--no-install` doesn't save you — it can serve a stale cached binary). Full ladder → [Setup](#setup--environment).
|
|
18
|
+
- **Version drift — `abx doctor`'s "Version & provenance" block catches it: binary source (npx is a ✗ — it can silently keep serving a stale cached copy), npm currency, and skill↔CLI match, each with its own fix command.** Run it before any deploy and act on a ✗ yourself (`abx skill install` / `npm i -g @artblocks/abx-cli@latest`) — never just relay the notice: a stale CLI can hold canonical addresses that have since moved, so it deploys against dead singletons.
|
|
22
19
|
- **`abx doctor` first, always** — full preflight (Node, pnpm, RPC, signing key, storage). Fix any ✗ before deploying ([Setup](#setup--environment)). A missing public-base-url is not a "set up IPFS" signal: for tiny art go on-chain, for larger art pick an off-chain backend — see [Quick start](#quick-start).
|
|
23
20
|
- **Never collect secrets in chat, and never `cat`/`grep` `.env`.** Keys, `PINATA_JWT`, S3 secrets, provider API keys → the project's `.env`. The Arweave/Turbo key is a CLI-managed file (`.abx-self-host/arweave-key.json`) — never paste it. Name the var/file; never take the value. **To see what's configured, ask the tool, not the file:** `abx doctor` and `abx remote` report each credential as set/unset without ever printing one. Reading `.env` spills every secret in it into the transcript — irreversible, and a plain `abx doctor` tells you the same thing.
|
|
24
21
|
- **Testnet only today** — every launch is on a testnet: **Base Sepolia by default** (`ABX_CHAIN` unset), with **Sepolia** also shipped (`ABX_CHAIN=sepolia`). Say "testnet"; don't imply mainnet. **Testnet IS the preview + e2e environment**: it runs the *real* wiring (renderers, generator, on-chain tokenURI assembly), so a creator should deploy there, inspect the actual result (`abx tokenuri` / the live view / `abx verify`), confirm it looks right, and only *then* go to mainnet — no separate local "preview" is as faithful as the real testnet drop, and a testnet deploy is ~free + ~minutes. **One cross-chain gotcha: on-chain library deps (`--dep p5@…`) resolve to on-chain bytes only where an Art Blocks dependency registry exists — that's Sepolia, NOT Base Sepolia.** A no-dependency script (vanilla JS/GLSL) goes fully on-chain on either; a drop that needs a registry-hosted library on-chain must target `ABX_CHAIN=sepolia` (or run the resolver lane).
|
|
@@ -26,7 +23,7 @@ L3 agentic surface: image → live self-hosted NFT the creator owns — a **1/1*
|
|
|
26
23
|
- **Is the work finished yet?** If the creator is still *making* the piece, you're in **[Phase 0](#phase-0--make-the-work-first-skip-every-gate-below-until-its-good)** — iterate on the art and keep every deploy question off the table until they say ship. The gates below apply to launching something that already exists.
|
|
27
24
|
- **Two gates decide everything: (1) demo or real? (2) who signs?** Settle both first — *once there's something to launch*.
|
|
28
25
|
- **Confirm the full config before any on-chain write** ([readout](#confirm-before-sending)); wait for go-ahead. Never invent a field silently (name/symbol from filename, an auto description) — show it, flag it `inferred`.
|
|
29
|
-
- **Never hand-build a service URL — ask the chain, then check the reference.** A contract commits its own metadata URL on-chain, so `abx tokenuri <addr
|
|
26
|
+
- **Never hand-build a service URL — ask the chain, then check the reference.** A contract commits its own metadata URL on-chain, so `abx tokenuri <addr> --fetch` (token) and `abx contracturi <addr>` (ERC-7572 collection) give you the answer *and* follow it, printing **what is actually served** — no route grammar to remember, no curl. (Bare `abx tokenuri` reads the chain only; `--fetch` is what GETs the URL the contract names.) **A 404/error on a URL you constructed is evidence about your URL, never about the service.** Don't infer a path from a similar-looking one (dropping the token id off `/t/<chain>/<addr>/<id>` does **not** give collection metadata — that's `/c/<chain>/<addr>`); look it up in [hosting.md](reference/hosting.md#token-api-the-resolver). Before telling anyone a service is broken, reproduce it with a **CLI command** — a real service miss says which of three things it is in a machine `code` (`invalid_request` = your path shape · `unknown_route` = no such route here · `not_registered` = this node doesn't index that contract), and none of those mean "down".
|
|
30
27
|
- **Safe to explore:** `abx <cmd> --help` and `abx deploy --dry-run` never send. You never run a real write just to learn flags.
|
|
31
28
|
- **`deploy` returns; `demo`/`serve`/`preview` block** (they serve) — background them or warn. Background `abx preview` and relay its URL, then keep working while the creator looks; `--shoot` is the one preview mode that exits on its own.
|
|
32
29
|
|
|
@@ -111,8 +108,7 @@ Every write builds an unsigned tx; pick the lane by stakes:
|
|
|
111
108
|
|
|
112
109
|
- **Sign page is operation-aware** — shows decoded intent ("Transfer #0 → 0x…"), gates on network + signing wallet + the tx, and the CLI refuses a mismatched signer server-side too.
|
|
113
110
|
- **Multi-tx signs in ONE session** (hot + wallet only). `--onchain-image` stages bytes before the deploy that references them; the human connects once and walks `Transaction 1 of N`. Cold can't (staging is interactive) — `--unsigned --onchain-image` errors; use `--send`/`--sign`.
|
|
114
|
-
- **Keep tx count low and say it up front.** A configured token deploys in one tx; `--onchain-image` adds staging tx(s); several owner edits collapse into one atomic `multicall`.
|
|
115
|
-
- **Off-chain storage uploads are NOT wallet approvals — never count them as signatures.** An IPFS upload (Pinata `PINATA_JWT`) or a managed-key Arweave upload happens *before* signing with **no wallet prompt**; only **on-chain staging + the deploy** are approvals. So an off-chain IPFS/Arweave deploy is **one** wallet approval (the deploy). Say it that way — *"1 approval (the deploy); your N images upload to IPFS first, no signature"* — don't fold uploads into the MetaMask count (the classic "2 txs, approve both" mistake). *Exception:* `--storage-signer eth --sign` makes each upload a `personal_sign` in the same session (no gas) — then, and only then, they're approvals too.
|
|
111
|
+
- **Keep tx count low and say it up front — every preview prints `approvals N wallet approval(s)`, the exact signature count.** A configured token deploys in one tx; `--onchain-image` adds staging tx(s); several owner edits collapse into one atomic `multicall`. Off-chain storage uploads (IPFS/Arweave) are listed separately in the preview and never counted — they happen before signing with no wallet prompt. *Exception:* `--storage-signer eth --sign` turns each upload into a same-session `personal_sign` — then it IS counted.
|
|
116
112
|
|
|
117
113
|
## Quick start
|
|
118
114
|
|
|
@@ -218,7 +214,7 @@ vs off-chain resolution · when to mint. Full detail, tradeoffs and failure mode
|
|
|
218
214
|
|
|
219
215
|
## Confirm before sending
|
|
220
216
|
|
|
221
|
-
Run `abx deploy --dry-run` for real values, present **this exact shape** — one row per on-chain value — then wait for go-ahead. **Mirror the dry-run's values; don't compose your own.**
|
|
217
|
+
Run `abx deploy --dry-run` for real values (pin the salt it prints — [below](#deploy-strategy--when-to-mint) — with a second `--dry-run --salt <it>` to see the real address), present **this exact shape** — one row per on-chain value — then wait for go-ahead. **Mirror the dry-run's values; don't compose your own.**
|
|
222
218
|
|
|
223
219
|
**No signing key in `.env` yet? `--dry-run` still needs a deployer address — pass `--for 0x<the creator's wallet>`.** The address is a pure function of (factory, salt, deployer), so a preview can't compute it from nothing; it signs nothing, so no key is involved. Ask the creator for their wallet address once, up front — it's also what the real wallet-lane deploy takes (`--sign --for 0x…`).
|
|
224
220
|
|
|
@@ -262,7 +258,7 @@ The deploy address is **deterministic** — a pure function of `(factory, salt)`
|
|
|
262
258
|
|
|
263
259
|
- **Fully on-chain ⇒ mint at deploy; do NOT add `--no-mint`.** The careful path warms an *off-chain resolver*; a fully on-chain token has none (the renderer resolves the instant the contract exists). Deferring buys nothing and adds a tx. Defer an on-chain mint only for a genuine primary sale.
|
|
264
260
|
- **`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.
|
|
265
|
-
-
|
|
261
|
+
- **`--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.
|
|
266
262
|
- **`abx refresh <addr>`** asks marketplaces to re-index. We emit **ERC-4906** on URI changes so 4906-aware marketplaces self-refresh; `refresh` is the fallback (+ genesis mint). With `OPENSEA_API_KEY` it calls OpenSea directly; else it prints the links to click.
|
|
267
263
|
- **There is no "add it to a marketplace" step — don't offer one.** Marketplaces discover the collection from chain (the mint + ERC-4906); `abx refresh` is the only nudge. Suggesting a manual listing implies work that doesn't exist.
|
|
268
264
|
- A `--no-mint` deploy reconstructs + serves normally (dashboard shows #0 "not yet minted"; metadata/image already resolve).
|
|
@@ -287,7 +283,7 @@ A token is **not "just a picture."** It anchors **named, typed files** ("artifac
|
|
|
287
283
|
- **PostParams are the exception — they need no resolver.** Params enumerate on-chain, so a bare `tokenURI` already carries every set value under **`abx_params`**. The line to give a creator: *attachments always need a resolver; params never do.*
|
|
288
284
|
- **Effect outputs are artifacts too.** A code project's effect runner publishes `render/image`, `render/traits`, and any extra declared output (e.g. a hi-res `render/print`) into the same manifest automatically, at the current settled state — files appear as tokens are minted and params change (see [code-projects](reference/code-projects.md)).
|
|
289
285
|
- **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. Depth (representations, verify, reserved keys, on-chain-vs-resolver) → [operating.md](reference/operating.md#attaching-files--the-data-plane).
|
|
290
|
-
- **Set expectations honestly (say it up front).** No mainstream marketplace (OpenSea/Blur) shows a "files" tab **today** — they render `image`/`animation_url` only. Attached files are a durable, cryptographically-anchored part of the token *now*, read by **data-plane-aware tools and any resolver**; broad marketplace display is future adoption. So a creator verifies an attach
|
|
286
|
+
- **Set expectations honestly (say it up front).** No mainstream marketplace (OpenSea/Blur) shows a "files" tab **today** — they render `image`/`animation_url` only. Attached files are a durable, cryptographically-anchored part of the token *now*, read by **data-plane-aware tools and any resolver**; broad marketplace display is future adoption. So a creator verifies an attach with **`abx tokenuri <addr> --fetch`** — the served document carries the `artifacts` manifest — not by refreshing OpenSea (which won't show it).
|
|
291
287
|
|
|
292
288
|
## After launch — tell the creator (durability + owner care)
|
|
293
289
|
|
|
@@ -325,9 +321,9 @@ Ask before installing **globally** — it's a machine-wide change to their PATH,
|
|
|
325
321
|
Whichever you land on, **keep using that same invocation for every command in the session** (`npx abx …` vs `abx …`) — don't mix them, or you'll silently drive two different CLI versions. Then run `abx doctor` (preflight: signing key/wallet, RPC, canonical factory, storage).
|
|
326
322
|
|
|
327
323
|
`.env` (in the creator's project dir) = **secrets only**:
|
|
328
|
-
- **Signing:**
|
|
324
|
+
- **Signing:** `ABX_DEPLOYER_PK` (the only name read — older `SEPOLIA_FUNDED_PK`/`SEPOLIA_WALLET_PK` are retired, and the CLI's error names them if it finds neither) is needed ONLY for hot/unattended signing. If the creator owns a wallet, prefer **`--sign`** — no key in `.env`. `doctor`'s missing-key ✗ is **not fatal** on the `--sign` path.
|
|
329
325
|
- `ABX_RPC_URLS`, `ABX_PUBLIC_BASE_URL` (remote-resolver custody only), optional `OPENSEA_API_KEY`, plus any backend secret.
|
|
330
|
-
- **
|
|
326
|
+
- **One client-side credential grammar: `ABX_REMOTE_<NAME>_URL` + `ABX_REMOTE_<NAME>_TOKEN`** (same normalization as `ABX_RPC_URLS_<CHAIN>`) — a node you run is just the name **`self`** (`deploy-resolver` generates `ABX_REMOTE_SELF_TOKEN` locally; bare `--remote` defaults to it), a managed provider is its own name. `ABX_RESOLVER_ADMIN_TOKEN` is the **resolver's own server-side config** (never read client-side) — a stale copy of it in `.env` with no `ABX_REMOTE_SELF_TOKEN` gets a pointed error naming both vars, and `abx doctor` flags it too. Only `_URL`/`_TOKEN` are read — `ABX_REMOTE_<NAME>_KEY` is ignored (the CLI flags a near-miss name).
|
|
331
327
|
|
|
332
328
|
<sub>Working from the abx **source repo** (contributor)? `pnpm install`, then `pnpm abx <cmd>` or `pnpm sandbox`. Every command below is identical.</sub>
|
|
333
329
|
|
|
@@ -170,8 +170,8 @@ A schema is `key:Type:Auth` (repeat comma-separated: `--schema palette:HexColor:
|
|
|
170
170
|
`deploy-code --image-base https://cdn.you/orbit` bakes the on-chain `image` as a **`url-template`** (`https://cdn.you/orbit/{id}.png`) — a stable per-token URL the chain names — and the effect runner writes each token's still to that exact object (overwrite in place) when you render it. Marketplaces read the on-chain `tokenURI` → the image URL → the bytes the runner PUT; **no resolver serves the image.**
|
|
171
171
|
|
|
172
172
|
- **⚠ Thumbnail freshness — the honest tradeoff.** With **no resolver there is no chain-watcher**, so on this lane the still is **backfill / manual**: run `abx render <addr> [ids]` after minting, and **re-run it after any PostParam change**. A param change updates the on-chain **animation instantly** (it reads the param live), but the **S3 still stays stale until you re-render**. It's a **pick-one at the baseline: on-chain-URI durability with *manual* thumbnails, OR an off-chain resolver with *continuous/live* thumbnails** (the resolver is the watcher). Don't promise "fully on-chain AND auto-updating thumbnails."
|
|
173
|
-
- **
|
|
174
|
-
- **Provisioning
|
|
173
|
+
- **Needs a mutable, path-addressed host** — S3 / R2 / a CDN (`--backend cloud`), **not ipfs/arweave** (content-addressed: the URL changes with the bytes, defeating a fixed per-token address). `deploy-code --dry-run` validates this combo and prints `render/storage ✓|✗ <reason>`; a real run refuses a bad one outright.
|
|
174
|
+
- **Provisioning:** the upload side (`ABX_S3_ENDPOINT`/`BUCKET`/`ACCESS_KEY_ID`/`SECRET_ACCESS_KEY`) and the public serve side (`ABX_S3_PUBLIC_BASE`, which must equal `--image-base`) are DIFFERENT hosts (API endpoint vs public read URL — R2/S3 both have this split) — verify both are live and agree with `abx storage show --check` (detail: [hosting.md](hosting.md#storage-backends-byte-custody)).
|
|
175
175
|
- **How a render writes:** the runner keys the still by the URL the on-chain template names (token N → `{key}/N.png`), plus a `…N.png.abxhash` sidecar for idempotency (skip when unchanged; overwrite when the inputsHash advances). It needs a live view to screenshot — a local `abx serve` render aid (run it while rendering, kill it after; never baked on-chain) or a resolver.
|
|
176
176
|
- **Pairs with `--onchain-uri`** for the "no metadata server" drop: tokenURI + animation on-chain, thumbnail at a deterministic S3 URL on-chain, and — if the traits port — `--attributes-renderer` for on-chain traits.
|
|
177
177
|
|
|
@@ -249,13 +249,14 @@ The render effect is the **ONLY source of a real thumbnail** — skip it and `im
|
|
|
249
249
|
## Verify it actually resolves — before you tell the creator it's live
|
|
250
250
|
|
|
251
251
|
**This testnet drop IS the preview / e2e** — it's the *real* wiring (renderers, generator, on-chain assembly), so inspecting it here is how a creator gains confidence before any mainnet launch; there's no local approximation to trust. A code project has the most that can silently break. Confirm the whole chain through the **baked** URL; don't announce success off a deploy receipt alone:
|
|
252
|
-
1. `abx tokenuri <addr>` → shows the **resolver base**, never `localhost` (or, on `--onchain-uri`, decodes straight from the contract).
|
|
252
|
+
1. `abx tokenuri <addr>` → shows the **resolver base**, never `localhost` (or, on `--onchain-uri`, decodes straight from the contract). Etherscan's "Read Contract" reverting on a big on-chain doc is a client gas cap, not indexing lag or a broken token → [troubleshooting.md](troubleshooting.md#abx-tokenuri--etherscan-reverts-on-a-fully-on-chain-code-project).
|
|
253
253
|
- **The rendered thumbnail is only real if the on-chain `image` had a destination at deploy** (`--image-base <bucket>` or a resolver). If you deployed `--onchain-uri` with neither, `abx render` writes to a local store the tokenURI never points at → **orphaned**; the marketplace still is the placeholder forever. There is no fix without a re-point tx (`set-field image <public url>` then re-render) — which is why it's a deploy-time decision.
|
|
254
254
|
2. the resolver serves `/t/<chainId>/<addr>/0` (real JSON, **not** `{"error":"unknown project"}`). If it errors, it's still backfilling or scanning from block 0 ([setup.md](setup.md)) — fix the **hosted** resolver; a local `abx serve` does **not** fix a hosted-baked token.
|
|
255
|
-
3. the live view `/a/<chainId>/<addr>/0` loads. **`{"error":"no live view — not a code project"}` on a project that IS a code drop is a scan-floor bug, NOT a version/compat problem** — the resolver indexed *above* the deploy block, so it missed the `code` field written at deploy (`collectionFields` comes back `[]`). Check `GET /api/project/<addr>` → `fromBlock` should equal the deploy block and `collectionFields` should contain `code`. Fix: `abx add <addr> --remote <url> --from-block <deployBlock>` (a *changed* floor forces a full replay). Do **not** conclude "the resolver doesn't support code projects" or redeploy as a static NFT. Directory mode then 302s to the gateway — the redirect must have **exactly one** gateway prefix (a doubled `https://arweave.net/https://arweave.net/…` is a stored-locator bug, fixed in-toolkit).
|
|
256
|
-
4. `abx
|
|
255
|
+
3. the live view `/a/<chainId>/<addr>/0` loads. A **`503`** ("its on-chain code has not been folded into the projection yet") is the retry-able answer — the resolver knows it's a code project but hasn't read the code yet; wait a beat and re-ask. A **`404`** is the terminal one, and the split is the diagnosis: **`404 {"error":"no live view — not a code project"}` on a project that IS a code drop is a scan-floor bug, NOT a version/compat problem** — the resolver indexed *above* the deploy block, so it missed the `code` field written at deploy (`collectionFields` comes back `[]`). Check `GET /api/project/<addr>` → `fromBlock` should equal the deploy block and `collectionFields` should contain `code`. Fix: `abx add <addr> --remote <url> --from-block <deployBlock>` (a *changed* floor forces a full replay). Do **not** conclude "the resolver doesn't support code projects" or redeploy as a static NFT. Directory mode then 302s to the gateway — the redirect must have **exactly one** gateway prefix (a doubled `https://arweave.net/https://arweave.net/…` is a stored-locator bug, fixed in-toolkit).
|
|
256
|
+
4. `abx tokens <addr>` → **every** token's owner, seed, and params in one read, straight from the contract (no indexer, no resolver, no server). This is the "what did the seeds actually deal?" answer, and for a generative collection **the seed list IS the collection** — the natural input to any distribution check before a real launch. `--json` for the machine form (`{tokenId, owner, seed, params}` per token). Note what it is *not*: traits come from running the script against the seed, so a trait spread comes from `abx render` / the effects runner, never from this command. Do **not** hand-roll the old workaround (serve → GET the project API → base64-decode `tokenURI` → base64-decode the `animation_url` inside it → regex the seed out of the HTML); every value is a plain contract read.
|
|
257
|
+
5. `abx verify <addr>` → per-minted-token render presence + live-data posture. **For a HOSTED drop use `abx verify <addr> --remote <resolver>`** — it reads the resolver's effect-status API and reports the real **4-state** per token: `up to date` · `rendering` · `failed` (with the actual error + attempt count — fix, then `abx render <addr> <id> --force --remote`) · `stale` (the next notify/sweep picks it up). Plain `abx verify` only checks THIS machine's store, so a render **published** to a hosted resolver reads as a false placeholder locally. A big batch drains through the runner's queue in ascending token order — `stale → rendering → up to date` is normal.
|
|
257
258
|
|
|
258
|
-
**⚠ Arweave (`--backend arweave`) directory mode propagates with a DELAY — set this expectation.** Unlike IPFS/Pinata (a pin is servable almost immediately), a Turbo/Arweave upload settles over **minutes (sometimes longer)** before the gateway serves it. So right after an arweave directory deploy: the live view `/a` 302s to `arweave.net`, which **404s until it propagates**, and a render run *now* will (correctly) **fail with "content isn't servable yet" and store nothing** — expected, not a bug, and it will NOT leave a garbage 404-page thumbnail. **Re-run the render (or let the effects service sweep) once the content is live** — check with
|
|
259
|
+
**⚠ Arweave (`--backend arweave`) directory mode propagates with a DELAY — set this expectation.** Unlike IPFS/Pinata (a pin is servable almost immediately), a Turbo/Arweave upload settles over **minutes (sometimes longer)** before the gateway serves it. So right after an arweave directory deploy: the live view `/a` 302s to `arweave.net`, which **404s until it propagates**, and a render run *now* will (correctly) **fail with "content isn't servable yet" and store nothing** — expected, not a bug, and it will NOT leave a garbage 404-page thumbnail. **Re-run the render (or let the effects service sweep) once the content is live** — check with **`abx storage status ar://<manifestTxid>/index.html`** (`ready` vs `propagating` vs `unreachable`; exits non-zero until it serves, so `until abx storage status <loc>; do sleep 10; done` is the whole wait). Do NOT reach for `curl` here — the command also probes other gateways, which is what distinguishes "yours is behind" from "the locator is wrong", and it tells you plainly not to re-upload. IPFS directory content is renderable right after the pin.
|
|
259
260
|
|
|
260
261
|
## Mint order, timing, pause, supply
|
|
261
262
|
|
|
@@ -29,7 +29,7 @@ All backends verify via the on-chain keccak commitment wherever the bytes live.
|
|
|
29
29
|
|
|
30
30
|
- **Identity is a lane (`--storage-signer`) — Turbo is multi-chain, so credits attach to whichever identity signs the upload.** Three choices, mirroring the tx signing lanes:
|
|
31
31
|
- **`arweave`** *(default)* — a CLI-managed Arweave key auto-generated at `.abx-self-host/arweave-key.json` on first upload (address printed). Reused across every upload/project; it signs *and* holds credits. Zero-config. Bring-your-own via `ARWEAVE_JWK` (inline) / `ABX_ARWEAVE_KEY_FILE` (path). **Back it up:** `abx storage backup-key --out <path>` (copies the file — the key is never printed to the terminal/log).
|
|
32
|
-
- **`--storage-signer eth`** — reuse the **`.env` EVM signing key** as the Turbo identity
|
|
32
|
+
- **`--storage-signer eth`** — reuse the **`.env` EVM signing key** (`ABX_DEPLOYER_PK`) as the Turbo identity. Its ETH address holds the credits, so credits funded on that wallet's Turbo balance are spendable. Hot lane (key in `.env`).
|
|
33
33
|
- **`--storage-signer eth` + `--sign`** — the **browser wallet** signs each upload data-item via `personal_sign` (no gas, no funds move; the upload is paid from the wallet's Turbo credits), in the same sign session as the deploy tx. The key never leaves the wallet. *(New; the mechanism matches arbundles' `InjectedEthereumSigner` and is unit-tested at the CLI↔page contract, but the live MetaMask↔Turbo path wants a manual smoke test. If a live upload misbehaves, fall back to `--storage-signer eth` with a key, or fund the managed key.)*
|
|
34
34
|
- **Under 100 KB → free**, permanent, zero setup. The CLI prints this from the file size (in `--dry-run` too).
|
|
35
35
|
- **Over 100 KB → prepaid credits, one-time.** `abx storage balance` shows the funded address + credits; `abx storage topup --usd <n>` returns a Stripe checkout link. A **pre-upload balance guard** stops *before* the deploy if credits are short — printing the address + fund options — so a shortfall never fails mid-deploy after txs already landed. One-time payment, no recurring fee, nothing to re-pin (contrast `cloud`/`ipfs`). Credits are non-refundable.
|
|
@@ -54,7 +54,7 @@ A failed upload is the #1 place an agent goes off the rails: it invents a cause
|
|
|
54
54
|
|
|
55
55
|
- **Uniform extension → O(1) directory.** Files are renamed to the token-id convention (`0.png`, `1.png`, …), uploaded as one IPFS directory pin (Pinata) / Arweave path manifest (Turbo), and a single collection-scope `image` field gets the **`url-template`** representation — `<gatewayBase>/{id}.png`. The renderer substitutes each id → one field renders the whole collection, any size. No per-token on-chain data.
|
|
56
56
|
- **Mixed extensions → per-token `url`** fields (O(N)) — still no server. A uniform extension unlocks the single-template path; flag it if a folder is mixed.
|
|
57
|
-
- **`cloud` (S3/R2/CDN)** works the same but
|
|
57
|
+
- **`cloud` (S3/R2/CDN)** works the same, but the signed-API host (`--endpoint`/`ABX_S3_ENDPOINT`, where uploads go) and the public read host (`--public-base`/`ABX_S3_PUBLIC_BASE`, also what `deploy-code --image-base` takes) are **different hosts by design** — one auth-only, one public (R2's own API endpoint is never public; its public URL is a separate `pub-<hash>.r2.dev` or custom domain you enable). **`abx storage show --check` proves the two agree** — a real PUT through the API + an unsigned GET through the public base (`abx doctor` runs the same probe). Files upload under a content-derived prefix → `<publicBase>/<prefix>/{id}.png`. **Caveat:** S3 is centralized, mutable, not content-addressed (no CID/txid root). Good if the creator already runs a bucket/CDN; IPFS/Arweave are the trustless/permanent options.
|
|
58
58
|
- **Integrity** (IPFS/Arweave) comes from the content-addressed root (CID / manifest txid), not a per-token keccak. The gateway host is baked on-chain → moving gateways is a `set-field` (bytes stay put). Prefer a **dedicated** gateway.
|
|
59
59
|
- **`url-template`** is a first-class representation ([spec](../../../../specs/protocol/onchain-metadata.md)); set by hand with `abx set-field <addr> --collection --field image --representation url-template --text "<gateway>/ipfs/<cid>/{id}.png"` then `abx set-renderer <addr>`.
|
|
60
60
|
|
|
@@ -62,7 +62,7 @@ A failed upload is the #1 place an agent goes off the rails: it invents a cause
|
|
|
62
62
|
|
|
63
63
|
The other way to have a resolver: be a **customer** of a hosted provider instead of running a node — one base URL + one API key, no cloud account, no Dockerfile, nothing to keep alive. Same interface, same commands as self-hosting ([spec](../../../../specs/self-host-toolkit/remote-services.md)); a project moves between a provider and your own node with one re-point + re-register ([operating.md → Moving your hosting](operating.md#moving-your-hosting--two-cases-dont-conflate)).
|
|
64
64
|
|
|
65
|
-
- **Named remotes.** Put the provider in `.env`: `ABX_REMOTE_<NAME>_URL=<base>` + `ABX_REMOTE_<NAME>_TOKEN=<api key>`. **`<NAME>` is YOUR label for the provider, not their legal name** — you pick it, you pass the same word to `--remote`. Normalization matches `ABX_RPC_URLS_<CHAIN>`: uppercase, runs of non-alphanumerics → `_`. So `--remote my-provider` reads `ABX_REMOTE_MY_PROVIDER_*`. For a multi-word brand, pick ONE short word and use it consistently — "Meridian Metadata" → `--remote meridian` + `ABX_REMOTE_MERIDIAN_URL`/`_TOKEN` (using `meridian metadata` would work too, as `ABX_REMOTE_MERIDIAN_METADATA_*`, but short is kinder to type). Then every remote command takes the name: `abx add <addr> --remote <name>`, ditto `index` / `forget` / `render` / `verify`, `abx migrate --from/--to <name>`, and owner-op nudges. Ad-hoc: `--remote <url> --remote-token <key>`. **Bare `--remote` stays the self-host default** (`ABX_PUBLIC_BASE_URL` + `
|
|
65
|
+
- **Named remotes.** Put the provider in `.env`: `ABX_REMOTE_<NAME>_URL=<base>` + `ABX_REMOTE_<NAME>_TOKEN=<api key>`. **`<NAME>` is YOUR label for the provider, not their legal name** — you pick it, you pass the same word to `--remote`. Normalization matches `ABX_RPC_URLS_<CHAIN>`: uppercase, runs of non-alphanumerics → `_`. So `--remote my-provider` reads `ABX_REMOTE_MY_PROVIDER_*`. For a multi-word brand, pick ONE short word and use it consistently — "Meridian Metadata" → `--remote meridian` + `ABX_REMOTE_MERIDIAN_URL`/`_TOKEN` (using `meridian metadata` would work too, as `ABX_REMOTE_MERIDIAN_METADATA_*`, but short is kinder to type). Then every remote command takes the name: `abx add <addr> --remote <name>`, ditto `index` / `forget` / `render` / `verify`, `abx migrate --from/--to <name>`, and owner-op nudges. Ad-hoc: `--remote <url> --remote-token <key>`. **Bare `--remote` stays the self-host default** (`ABX_PUBLIC_BASE_URL` + `ABX_REMOTE_SELF_TOKEN` — "self" is just another named remote) — nothing changes for a node you deployed. A named remote never falls back to `ABX_REMOTE_SELF_TOKEN` (or the resolver's own server-side `ABX_RESOLVER_ADMIN_TOKEN`), so neither secret can leak to a provider.
|
|
66
66
|
- **Ask up front whether they serve YOUR domain.** Whatever base URL is baked on-chain is what a marketplace fetches. If that's a domain the creator controls (pointed at the provider), leaving later is a DNS change — no tx, no gas. If it's the provider's own hostname, leaving costs an **owner-signed `set-token-uri`/`set-contract-uri` tx**. Same exit either way, different price; worth knowing before you bake it, not after.
|
|
67
67
|
- **Read the descriptor BEFORE registering — match the project to the provider.** `abx remote <name>` (or `curl <base>/.well-known/abx-service`, public, no key) prints what the service supports: **`chains`** must cover the project's chain (else registration is refused with `unsupported_chain`); **`render.attached`** means thumbnails/traits for code drops are rendered behind the provider — **skip `abx deploy-effects` entirely** (without it, renders are still yours: `abx render <addr> --remote <name>` or your own runner); **`auth.signupUrl`** is where a human gets a key.
|
|
68
68
|
- **The API key is the human's step — a membrane ask, never a chat paste.** Say: *"This provider covers your chain and manages rendering. You'll need an API key from `<signupUrl>` — put it in `.env` as `ABX_REMOTE_<NAME>_TOKEN` (never paste it in chat) and I'll do the rest."* Then verify with `abx remote <name>`: it lists the projects visible to the key. **401** = the key is missing/wrong (check the var, rotate at the provider). **403** = the key is valid but not authorized for this contract/chain — provider-side scoping, not a typo; don't retry-loop, check the provider dashboard.
|
|
@@ -77,7 +77,7 @@ For the large/mutable default, when the creator runs the node themselves (the al
|
|
|
77
77
|
|
|
78
78
|
**Never inline a secret into a command.** The generated steps **source from `.env`** (`set -a; . ../../.env; set +a` then `fly secrets set ABX_RPC_URLS="$ABX_RPC_URLS"`) — keep that shape. Never substitute a literal secret into command text; it must never appear in output or shell history.
|
|
79
79
|
|
|
80
|
-
`deploy-resolver` also **generates `
|
|
80
|
+
`deploy-resolver` also **generates `ABX_REMOTE_SELF_TOKEN`** → your local `.env` (a secret — never printed, env-only; "self" is just the named remote for a node you run); the printed steps set that same value on the host under **its own** var name, `ABX_RESOLVER_ADMIN_TOKEN` (server-side config, unrelated to how any client authenticates). That token is what later lets `abx add <clone> --remote` tell the hosted node which contracts to index — the only new secret the hosted path adds, and it authorizes indexing control, not signing.
|
|
81
81
|
|
|
82
82
|
**Gather the decisions in one pass.** The hosted path has a few small choices — provider, custom domain (or the provider-hostname tradeoff), app name, plus identity. Collect them together, then show the full plan; don't drip one question per turn. Once the human says "you run it," run the steps — don't flip between "here are commands" and "I'll do it."
|
|
83
83
|
|
|
@@ -85,7 +85,7 @@ For the large/mutable default, when the creator runs the node themselves (the al
|
|
|
85
85
|
|
|
86
86
|
## Render runner — `abx deploy-effects`
|
|
87
87
|
|
|
88
|
-
A code project's marketplace still is rendered off-chain, so a **runner** (Playwright + Chromium — the resolver image stays browserless) must render each token's live view and hand the resolver the result. **A managed provider with `render.attached` in its descriptor does all of this for you — skip this section.** Self-hosting it: `abx deploy-effects --resolver-url <resolver>` scaffolds it for Fly (a self-contained `Dockerfile.effects` + `fly.toml` + vendored source), wired to the resolver and your storage home. **Decide where renders live up front** (`ABX_STORAGE_BACKEND`): **cloud (S3/R2 + a public base) · ipfs · arweave** — equal options, pick on cost/ops. **NOT** the default `fs` for a hosted setup, and this is now refused rather than warned: the runner HOLDS its render bytes and hands the resolver a URL, so a backend that can't name one has no lane (`abx deploy-effects` errors out, and the runner refuses to start). The runner **registers** each render with the resolver's control plane (`POST /v1/effect-artifacts`, gated by the
|
|
88
|
+
A code project's marketplace still is rendered off-chain, so a **runner** (Playwright + Chromium — the resolver image stays browserless) must render each token's live view and hand the resolver the result. **A managed provider with `render.attached` in its descriptor does all of this for you — skip this section.** Self-hosting it: `abx deploy-effects --resolver-url <resolver>` scaffolds it for Fly (a self-contained `Dockerfile.effects` + `fly.toml` + vendored source), wired to the resolver and your storage home. **Decide where renders live up front** (`ABX_STORAGE_BACKEND`): **cloud (S3/R2 + a public base) · ipfs · arweave** — equal options, pick on cost/ops. **NOT** the default `fs` for a hosted setup, and this is now refused rather than warned: the runner HOLDS its render bytes and hands the resolver a URL, so a backend that can't name one has no lane (`abx deploy-effects` errors out, and the runner refuses to start). The runner **registers** each render with the resolver's control plane (`POST /v1/effect-artifacts`, gated by the same token — your local `ABX_REMOTE_SELF_TOKEN`, the resolver's own `ABX_RESOLVER_ADMIN_TOKEN` — index-control only, never signing), so it does NOT need to share the resolver's disk. What travels: a **locator** for the still (the resolver 302-redirects; it never proxies or fetches it), and the **content** for `traits` (≤64KB — traits stitch into the token JSON, so a pointer there could never work). It does not matter which scheme the URL uses: an `https://` gateway or bucket URL is a peer of `ipfs://`/`ar://`, since a lost render is a re-render. It only has to be fetchable by someone other than you — a loopback gateway or a presigned expiring URL is refused. Two topologies:
|
|
89
89
|
|
|
90
90
|
- **Locator bridge (default for a hosted drop):** runner on its own host + a public storage home; it registers locators, the resolver redirects. `deploy-effects` sets this up.
|
|
91
91
|
- **Co-located:** runner beside the resolver sharing one backend/volume (no admin token needed — both read/write the same store). This is the escape hatch when there's no public storage home: `fs` is fine here.
|
|
@@ -96,7 +96,7 @@ One-shot without a service: **`abx render <addr> --remote <resolver>`** renders
|
|
|
96
96
|
|
|
97
97
|
A project is served only if it's in **the resolver's own** store. `abx deploy`/`abx add` (no `--remote`) index into **this machine's** store; a remote resolver — self-hosted or a managed provider — is a **different store** and doesn't learn about a contract just because you deployed it. A local deploy pointing its `tokenURI` at a remote resolver returns `{"error":"unknown project"}` for everyone — "works on my `serve`, broken for the world." **Bridge it:**
|
|
98
98
|
- **Local resolver** (`abx serve` here): a local `deploy`/`add` already indexed it — done.
|
|
99
|
-
- **Remote resolver** (baked URL is remote): after deploy run **`abx add <clone> --remote [name|url]`** to register + index it on the node (bare `--remote` defaults to `ABX_PUBLIC_BASE_URL` + `
|
|
99
|
+
- **Remote resolver** (baked URL is remote): after deploy run **`abx add <clone> --remote [name|url]`** to register + index it on the node (bare `--remote` defaults to `ABX_PUBLIC_BASE_URL` + `ABX_REMOTE_SELF_TOKEN`; a named remote uses its own `ABX_REMOTE_<NAME>_TOKEN`). Also **bridges** what the node can't derive: the durable `ipfs://`/`ar://` locator and off-chain traits. Post-deploy nudge (after a deferred mint) is **`abx index <clone> --remote`** (idempotent). Remove with **`abx forget <clone> --remote`**. Every remote command prints `REMOTE → <url>`.
|
|
100
100
|
|
|
101
101
|
### Indexing status — `backfilling` is not a failure
|
|
102
102
|
|
|
@@ -18,7 +18,7 @@ After launch the owner operates the project. Each command builds a tx, signs it
|
|
|
18
18
|
| `abx deploy-effects --resolver-url <resolver>` | scaffold the render runner (Playwright) beside a hosted resolver — renders code-token stills off-chain and publishes them | the storage home (`ABX_STORAGE_BACKEND`: ipfs/arweave/s3, not local `fs`) |
|
|
19
19
|
| `abx render <addr> [id…] [--remote <name\|url>]` | render missing stills/traits now (repair lane); `--remote` publishes to a REMOTE resolver | none — idempotent; local captures need Playwright chromium |
|
|
20
20
|
| `abx remote [<name\|url>]` | inspect a remote service: named remotes in `.env`, or a target's descriptor (chains · managed rendering · signup URL) + the projects your token sees | none — read-only; the "is my provider key valid?" check |
|
|
21
|
-
| `abx add <addr> --remote <name\|url>` · `abx index <addr> --remote` · `abx forget <addr> --remote` | register / re-index (nudge) / deregister a contract on a REMOTE resolver (control plane) | which remote — a name (`ABX_REMOTE_<NAME>_URL/_TOKEN`, a managed provider's key) or bare `--remote` (self-host default: `ABX_PUBLIC_BASE_URL` + `
|
|
21
|
+
| `abx add <addr> --remote <name\|url>` · `abx index <addr> --remote` · `abx forget <addr> --remote` | register / re-index (nudge) / deregister a contract on a REMOTE resolver (control plane) | which remote — a name (`ABX_REMOTE_<NAME>_URL/_TOKEN`, a managed provider's key) or bare `--remote` (self-host default: `ABX_PUBLIC_BASE_URL` + `ABX_REMOTE_SELF_TOKEN`); ad-hoc `--remote <url> --remote-token <t>` |
|
|
22
22
|
| `abx migrate <addr> --from <src name\|url> --to <dest name\|url>` | move a contract's **off-chain state** to a new resolver instance (see below) | confirm the cutover step (DNS vs base URI); re-pin any source-only images; only the DESTINATION needs a token |
|
|
23
23
|
| `abx set-royalty <addr> --bps <0-10000> [--receiver 0x..]` | change the royalty (receiver defaults to current) | the rate, and whether the payee changes |
|
|
24
24
|
| `abx attach <addr> <key> <ipfs://…\|ar://…\|https://…>` | attach a named file to a token → the `artifacts` manifest (the data plane) | the key (how it appears); locator vs `--file` on-chain |
|
|
@@ -32,10 +32,7 @@ After launch the owner operates the project. Each command builds a tx, signs it
|
|
|
32
32
|
|
|
33
33
|
### Authorship + rights (credit + license)
|
|
34
34
|
|
|
35
|
-
The reserved **collection** keys are `artist`, `display_notes`, `artist_links`, `license` (a rights *identifier* like `CC0-1.0` / `CC-BY-NC-4.0`).
|
|
36
|
-
|
|
37
|
-
- **At deploy** (all three deploy commands): `abx deploy … --artist "Casey Reas" --license "CC BY-NC 4.0"` (also `--display-notes`, `--artist-links`) — baked into the deploy tx, parity with `--description`/`--external-url`.
|
|
38
|
-
- **Later / to change**: `abx set-field <addr> --collection --field artist --text "Casey Reas"`.
|
|
35
|
+
The reserved **collection** keys are `artist`, `display_notes`, `artist_links`, `license` (a rights *identifier* like `CC0-1.0` / `CC-BY-NC-4.0`). `--artist`/`--license`/`--display-notes`/`--artist-links` on any deploy command are **sugar over `set-field`** — they pre-fill the same on-chain inline collection fields at deploy time (parity with `--description`/`--external-url`); change them later with `abx set-field <addr> --collection --field artist --text "Casey Reas"` directly.
|
|
39
36
|
|
|
40
37
|
They project into `contractURI` on both the resolver and the on-chain renderer; unset ⇒ omitted. Works on **any** token type (1/1 · Series · code) — reserved keys in the shared metadata store, not a code-only feature. Being *reserved*, they never become `artifacts` entries the way an arbitrary `attach` key would.
|
|
41
38
|
|
package/skill/reference/setup.md
CHANGED
|
@@ -19,14 +19,15 @@ When `abx doctor` flags a tight range, or a reconstruction would be large/slow
|
|
|
19
19
|
|
|
20
20
|
## Troubleshooting
|
|
21
21
|
|
|
22
|
-
- **doctor: signing key** → **not fatal — a missing key is a fork, not a blocker.** A key in `.env` is only needed to sign *unattended* (hot lane). To sign in a browser wallet instead, run the write with `--sign` and no env key is needed. Offer both: add `
|
|
22
|
+
- **doctor: signing key** → **not fatal — a missing key is a fork, not a blocker.** A key in `.env` is only needed to sign *unattended* (hot lane). To sign in a browser wallet instead, run the write with `--sign` and no env key is needed. Offer both: add `ABX_DEPLOYER_PK` for autonomous signing, **or** use `--sign`. **deployer unfunded** → fund the printed address from a faucet for the active chain (Base Sepolia by default, else Sepolia).
|
|
23
23
|
- **a write reverts with no clear reason (`--onchain-image` / staging)** → don't thrash by retrying variations. Get the actual reason: re-run the same call read-only with `cast call <addr> "<sig>" <args> --rpc-url <one of your ABX_RPC_URLS>`, which surfaces the revert string. The classic culprit is a stale chunk store (deployed before a contract upgrade) — the toolkit now detects this and auto-deploys a current store with a yellow notice, so you shouldn't hit the silent revert, but verify the resolved chunk-store/factory addresses are live (`cast code <addr>`; `abx doctor` / `abx status` print the factory) if anything looks off. **Never silently switch the representation you told the user you'd use** (e.g. fall back from `reader` to `inline`) — if the path changes, say so and why.
|
|
24
24
|
- **storage 'cloud' missing accessKeyId/…** → set `ABX_S3_*` in `.env`. **'ipfs' unreachable** → Kubo not running or `PINATA_JWT` missing.
|
|
25
25
|
- **re-index slow, or a "scan too large" stop** → **first check the scan floor: is it indexing from block 0 instead of the deploy block?** That's the usual cause of a "huge" scan (an `add` that lost the deploy block). The floor is `flags['from-block'] → the stored deploy block → on-chain discovery`; if none resolve, the CLI refuses rather than sweeping genesis. Only once the floor is correct does the RPC's `eth_getLogs` range matter: the toolkit chunks automatically, but a genuinely large job stops with an estimate rather than grinding. Real fix then: research a current higher-range free endpoint and set `ABX_RPC_URLS` (`abx doctor` reports your range); or re-run with `--yes` to chunk through. **archive-range getLogs refused** → use an archive-capable provider.
|
|
26
26
|
- **`serve`/resolver returns `{"error":"unknown project"}`** → the resolver you hit doesn't have that contract in *its own* projection store **yet**. Two distinct situations — diagnose which, and NEVER default to "RPC limit":
|
|
27
27
|
- **A remote resolver you just registered** (`abx add … --remote`): it may still be **backfilling** — hit `GET /` (or `abx remote <name|url>`, which lists the projects the token sees) to see if it's appearing. If it's slow or stuck, the cause is almost always a **from-genesis scan (from-block=0)**, not the RPC tier — a fixed `abx add` forwards the deploy block, so re-run it and confirm the floor. (Fixed in-toolkit: a first remote add now forwards/derives the deploy block and refuses a genesis default.)
|
|
28
28
|
- **A local `abx serve`**: a **store/port** problem. Usual causes, in order: (1) a **stale/duplicate `abx serve` from an old session** holds the port and serves a *different* store — hit `GET /` and see what it lists; (2) you're serving a different store directory than the deploy indexed into; (3) the contract was never registered there. Fix the server/port/registration.
|
|
29
|
-
- **`/a/…` returns `
|
|
29
|
+
- **`/a/…` returns `503` with "its on-chain code has not been folded into the projection yet"** → exactly what it says: the resolver knows this is a code project (from the deployed extensions) but hasn't read its code yet. **Retry** — this is normal right after a register. It only means trouble if it persists, and then it's the scan-floor bug below.
|
|
30
|
+
- **`/a/…` returns `404 {"error":"no live view — not a code project"}` on a project that IS a code drop** → the resolver indexed *above* the deploy block, so it never saw the deploy at all — a scan-floor bug, **not** a resolver version/compat gap (do NOT redeploy as a static NFT). The 404-vs-503 split is the diagnosis: 404 on a real code drop means the fold never saw this contract's deploy, which is the scan-floor signature. This is the *opposite* of the genesis bug: the floor is too **high**, not too low. It happened when a `--sign` code deploy spanned blocks (deploy at N, mint at N+2) and the mint block was recorded as the floor. Confirm: `GET /api/project/<addr>` → `collectionFields` is `[]` and `fromBlock` sits above the deploy block. Fix: `abx add <addr> --remote <url> --from-block <deployBlock>` — a *changed* floor forces a full replay that picks up the `code` field. Find the true deploy block with `abx add <addr>` locally (it prints "deploy block N (discovered on-chain)"). (Fixed in-toolkit: `deploy-code` now records the clone-CREATION block, and discovers it on-chain rather than trusting the last-tx receipt.)
|
|
30
31
|
- **directory-mode live view 302s to a doubled URL** (`https://arweave.net/https://arweave.net/<txid>/index.html`) → the `code` locator was stored as a full gateway URL and the gateway got prefixed again. Fixed in-toolkit (deploy stores the bare txid/CID; the resolver serves an already-absolute locator verbatim). A resolver image built before the fix still doubles — redeploy it to pick up the resolver-side tolerance.
|
|
31
32
|
- **the thumbnail stays a placeholder on a HOSTED resolver even after `abx render` reports `ran=1`** → the render bytes landed in a store the hosted resolver can't read. A local `abx render` with the default `fs` backend writes to your laptop; `ipfs`/`arweave` write a LOCAL key→CID index the resolver doesn't have. Fix: render **to** the resolver — `abx render <addr> --remote <resolver>` (uploads to your `ABX_STORAGE_BACKEND` home and **publishes** a locator/bytes the resolver serves), or stand up the runner beside it (`abx deploy-effects --resolver-url <resolver>`). Only a SHARED `s3`/`cloud` bucket makes a bare local render visible to a hosted resolver. Confirm with `abx verify <addr>`.
|
|
32
33
|
- **resolver won't start / errors about `ABX_PUBLIC_BASE_URL`** → it refuses a placeholder `.example` base (a scaffold leftover) or a `localhost` base in a hosted image (`ABX_HOSTED=1`), because it bakes that base into every image/animation URL it serves — a bad one serves dead links, so a loud fail beats silent breakage. Set `ABX_PUBLIC_BASE_URL` to the resolver's real public URL (`fly secrets set ABX_PUBLIC_BASE_URL=https://<app>.fly.dev`, or your custom domain). The current scaffold bakes the real platform hostname by default, so this only bites a hand-edited/old artifact or a stripped env.
|
|
@@ -34,7 +35,7 @@ When `abx doctor` flags a tight range, or a reconstruction would be large/slow
|
|
|
34
35
|
- **the project's on-chain `tokenURI` points at a HOSTED resolver but it isn't serving** → **do NOT "fix" it by running `abx serve` locally and handing over a `localhost` link.** The baked URL is the hosted one; a local serve resolves for no one but you. Fix the HOSTED resolver instead: re-run `abx add <addr> --remote` (now forwards the deploy block), check `GET /` on the host, and vet its RPC with `abx doctor`. A local serve is only ever the answer when the baked base is that same local machine.
|
|
35
36
|
- **port hygiene before `serve`** → check the port is free first (`lsof -i:8787`). If an old session holds it, kill that process or serve this project on a distinct `--port <n>` (and point its `--public-base-url`/tokenURI accordingly) — a stale server silently answering on the port is the #1 cause of "works for me, `unknown project` for everyone."
|
|
36
37
|
- **serving code projects is RPC-heavier than static art** → the live view rebuilds `tokenData` per request, and template-mode script chunks + data-backed (`String`/`Bytes`) params are read **live per view** (scalar PostParams come from the indexed projection, so they don't hit RPC each view). A resolver serving many code projects under marketplace traffic wants a range-generous, reliable RPC — this is a genuine scaling consideration, distinct from the getLogs range cap above.
|
|
37
|
-
- **a remote command returns 401 unauthorized** → the token the CLI resolved is missing/wrong for that target — the error names the var it used (`ABX_REMOTE_<NAME>_TOKEN` for a named remote, else `
|
|
38
|
+
- **a remote command returns 401 unauthorized** → the token the CLI resolved is missing/wrong for that target — the error names the var it used (`ABX_REMOTE_<NAME>_TOKEN` for a named remote, else `ABX_REMOTE_SELF_TOKEN`, else `--remote-token`). Check that var, or rotate the key at the provider if it may have leaked. Named remotes deliberately never fall back to `ABX_REMOTE_SELF_TOKEN` (or the resolver's own server-side `ABX_RESOLVER_ADMIN_TOKEN`).
|
|
38
39
|
- **a remote command returns 403 forbidden** → the key is **valid but not authorized** for this contract/chain — provider-side scoping, not a typo. Don't retry-loop and don't swap tokens blindly: check the provider dashboard, and the descriptor's `chains` (`abx remote <name>`).
|
|
39
40
|
- **register refused with `unsupported_chain`** → the service doesn't serve the project's chain — its descriptor `chains` says which it does. Pick a provider that covers the chain, or self-host.
|
|
40
41
|
- **`/.well-known/abx-service` 404s** → an older self-hosted node (fine if it's yours — the remote commands still work against it once redeployed to the current image) or **not an ABX service at all** — verify the URL with `abx remote <url>` before registering anything; don't register blind.
|
|
@@ -26,10 +26,23 @@ Walk the cause down:
|
|
|
26
26
|
**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.
|
|
27
27
|
|
|
28
28
|
### `abx tokenuri` / Etherscan reverts on a fully-on-chain code project
|
|
29
|
-
A large on-chain `tokenURI` document can exceed the **unauthenticated eth_call gas cap** some RPCs/explorers impose on a public read — the call reverts in that UI but the data is fine on a normal RPC. This is **expected for a big on-chain doc, NOT an indexing problem** — do not `abx index --full` or redeploy. Read it via a
|
|
29
|
+
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.
|
|
30
30
|
|
|
31
31
|
### The on-chain tokenURI points at `localhost`
|
|
32
32
|
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.
|
|
33
33
|
|
|
34
|
+
### `deploy-code` deployed the contract but the SETUP transaction failed
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
**The contract is recoverable. Do NOT redeploy, and do NOT hand-assemble a multicall with `cast`.** Finish it:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
abx deploy-code --resume <address> <the SAME content flags the original deploy used> # add --dry-run first
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
If the DEPLOY (first) transaction is what failed, there is nothing to resume — no contract exists. Run a normal deploy.
|
|
46
|
+
|
|
34
47
|
### `abx index`/`abx verify` says "isn't registered"
|
|
35
48
|
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.
|
package/dist/gas.d.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gas limits for a sequenced transaction — decided HERE, once, rather than re-read at signing time.
|
|
3
|
-
*
|
|
4
|
-
* The failure this exists to prevent, in full, because it is not obvious and it cost a reporter every
|
|
5
|
-
* `deploy-code` attempt of a session: a project deploys in two transactions, and the second targets
|
|
6
|
-
* the contract the first just created. If the node answering `eth_estimateGas` has not yet seen the
|
|
7
|
-
* deploy block, the target looks like an account with no code — and an estimate for a call to a
|
|
8
|
-
* codeless account is just the calldata cost, around 200k. That number is then sent as the gas limit.
|
|
9
|
-
* Once the contract does exist the same call needs ~941k, because storing a program on-chain is
|
|
10
|
-
* dominated by CREATE code deposit at ~200 gas per byte. The CREATE receives 63/64 of a budget that
|
|
11
|
-
* cannot cover the deposit, returns 0, and Solady's `SSTORE2.write` reverts `DeploymentFailed()` — a
|
|
12
|
-
* revert that reads like a contract bug and is really an out-of-gas.
|
|
13
|
-
*
|
|
14
|
-
* (Measured, from the real transactions: `gasLimit 201,616 / gasUsed 198,870` and
|
|
15
|
-
* `gasLimit 169,301 / gasUsed 166,810` — 98.6% and 98.5% of their limits. Replaying those payloads
|
|
16
|
-
* against a codeless address reproduces both limits to the gas; against the real contract the same
|
|
17
|
-
* calls estimate at 941,331.)
|
|
18
|
-
*
|
|
19
|
-
* The deploy loop already pinned the *nonce* against exactly this read-after-write lag on a
|
|
20
|
-
* distributed RPC. The rule generalizes, and it is the reason this module exists: **anything read at
|
|
21
|
-
* send time on a distributed RPC needs pinning, not just the nonce.**
|
|
22
|
-
*/
|
|
23
|
-
import type { Address, Hex, PublicClient } from 'viem';
|
|
24
|
-
/**
|
|
25
|
-
* Wait until `address` has code from THIS client's point of view. A deploy receipt proves the
|
|
26
|
-
* contract exists on chain; it does not prove the node answering the next request has caught up.
|
|
27
|
-
* Bounded — on timeout we proceed and let the gas floor carry it, since a slow RPC is not a reason
|
|
28
|
-
* to refuse to continue a deploy that already spent money.
|
|
29
|
-
*/
|
|
30
|
-
export declare function waitForCodeAt(client: PublicClient, address: Address, timeoutMs?: number): Promise<boolean>;
|
|
31
|
-
/**
|
|
32
|
-
* Decide a transaction's gas limit once, here.
|
|
33
|
-
*
|
|
34
|
-
* The important design point, because the obvious approach is wrong: when an estimate looks too low
|
|
35
|
-
* we do **not** substitute a computed number. A caller can only compute the *provable* part of a
|
|
36
|
-
* payload's cost (see `PreparedTx.gasFloor` — code deposit is 200 gas/byte and nothing else is
|
|
37
|
-
* physics); the same setup multicall also carries schema writes, dependency legs, URI legs and
|
|
38
|
-
* mints, whose cost we cannot derive without simulating them. A "probably enough" constant is a
|
|
39
|
-
* number tuned to whatever case was in front of its author: it papers over the symptom, then
|
|
40
|
-
* under-funds the next payload that carries a few more legs, producing the identical
|
|
41
|
-
* `DeploymentFailed()` with a fresh mystery attached.
|
|
42
|
-
*
|
|
43
|
-
* So the floor is used as a **detector**. An estimate below a provable minimum is not "low", it is
|
|
44
|
-
* *impossible* — proof that the node answering us is looking at the wrong state (typically it has
|
|
45
|
-
* not seen the deploy block yet, so the target reads as an account with no code and the estimate
|
|
46
|
-
* comes back as the calldata cost alone). The right response to a broken measurement is to take it
|
|
47
|
-
* again, and if it stays broken, to refuse — sending a transaction we can prove is under-funded
|
|
48
|
-
* would burn the gas AND orphan the contract.
|
|
49
|
-
*
|
|
50
|
-
* When the estimate IS plausible it is trusted, plus headroom for state drift between estimate and
|
|
51
|
-
* inclusion.
|
|
52
|
-
*/
|
|
53
|
-
export declare function pinGas(client: PublicClient, tx: {
|
|
54
|
-
from: Address;
|
|
55
|
-
to: Address | null;
|
|
56
|
-
data: Hex;
|
|
57
|
-
value?: Hex;
|
|
58
|
-
gasFloor?: Hex;
|
|
59
|
-
}, opts?: {
|
|
60
|
-
attempts?: number;
|
|
61
|
-
delayMs?: number;
|
|
62
|
-
}): Promise<bigint>;
|
|
63
|
-
//# sourceMappingURL=gas.d.ts.map
|
package/dist/gas.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gas.d.ts","sourceRoot":"","sources":["../src/gas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAC,OAAO,EAAE,GAAG,EAAE,YAAY,EAAC,MAAM,MAAM,CAAC;AAErD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAQhH;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,MAAM,CAC1B,MAAM,EAAE,YAAY,EACpB,EAAE,EAAE;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,EAAE,GAAG,CAAA;CAAC,EAC/E,IAAI,GAAE;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAM,GAC/C,OAAO,CAAC,MAAM,CAAC,CAmCjB"}
|
package/dist/gas.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wait until `address` has code from THIS client's point of view. A deploy receipt proves the
|
|
3
|
-
* contract exists on chain; it does not prove the node answering the next request has caught up.
|
|
4
|
-
* Bounded — on timeout we proceed and let the gas floor carry it, since a slow RPC is not a reason
|
|
5
|
-
* to refuse to continue a deploy that already spent money.
|
|
6
|
-
*/
|
|
7
|
-
export async function waitForCodeAt(client, address, timeoutMs = 15_000) {
|
|
8
|
-
const deadline = Date.now() + timeoutMs;
|
|
9
|
-
for (;;) {
|
|
10
|
-
const code = await client.getCode({ address }).catch(() => undefined);
|
|
11
|
-
if (code && code !== '0x')
|
|
12
|
-
return true;
|
|
13
|
-
if (Date.now() >= deadline)
|
|
14
|
-
return false;
|
|
15
|
-
await new Promise((r) => setTimeout(r, 750));
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Decide a transaction's gas limit once, here.
|
|
20
|
-
*
|
|
21
|
-
* The important design point, because the obvious approach is wrong: when an estimate looks too low
|
|
22
|
-
* we do **not** substitute a computed number. A caller can only compute the *provable* part of a
|
|
23
|
-
* payload's cost (see `PreparedTx.gasFloor` — code deposit is 200 gas/byte and nothing else is
|
|
24
|
-
* physics); the same setup multicall also carries schema writes, dependency legs, URI legs and
|
|
25
|
-
* mints, whose cost we cannot derive without simulating them. A "probably enough" constant is a
|
|
26
|
-
* number tuned to whatever case was in front of its author: it papers over the symptom, then
|
|
27
|
-
* under-funds the next payload that carries a few more legs, producing the identical
|
|
28
|
-
* `DeploymentFailed()` with a fresh mystery attached.
|
|
29
|
-
*
|
|
30
|
-
* So the floor is used as a **detector**. An estimate below a provable minimum is not "low", it is
|
|
31
|
-
* *impossible* — proof that the node answering us is looking at the wrong state (typically it has
|
|
32
|
-
* not seen the deploy block yet, so the target reads as an account with no code and the estimate
|
|
33
|
-
* comes back as the calldata cost alone). The right response to a broken measurement is to take it
|
|
34
|
-
* again, and if it stays broken, to refuse — sending a transaction we can prove is under-funded
|
|
35
|
-
* would burn the gas AND orphan the contract.
|
|
36
|
-
*
|
|
37
|
-
* When the estimate IS plausible it is trusted, plus headroom for state drift between estimate and
|
|
38
|
-
* inclusion.
|
|
39
|
-
*/
|
|
40
|
-
export async function pinGas(client, tx, opts = {}) {
|
|
41
|
-
const floor = tx.gasFloor ? BigInt(tx.gasFloor) : 0n;
|
|
42
|
-
const attempts = opts.attempts ?? 3;
|
|
43
|
-
let lastEstimate = null;
|
|
44
|
-
let lastError;
|
|
45
|
-
for (let i = 0; i < attempts; i++) {
|
|
46
|
-
try {
|
|
47
|
-
const estimate = await client.estimateGas({
|
|
48
|
-
account: tx.from,
|
|
49
|
-
to: tx.to ?? undefined,
|
|
50
|
-
data: tx.data,
|
|
51
|
-
...(tx.value && tx.value !== '0x0' ? { value: BigInt(tx.value) } : {}),
|
|
52
|
-
});
|
|
53
|
-
// Plausible (or nothing provable to check it against) → trust it.
|
|
54
|
-
if (estimate >= floor)
|
|
55
|
-
return (estimate * 125n) / 100n;
|
|
56
|
-
lastEstimate = estimate;
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
lastError = err;
|
|
60
|
-
// A revert during estimation is a real answer about the transaction, not a lagging node —
|
|
61
|
-
// surface it immediately, since its message ("caller is not the owner") is the useful part.
|
|
62
|
-
if (floor === 0n)
|
|
63
|
-
throw err;
|
|
64
|
-
}
|
|
65
|
-
if (i < attempts - 1)
|
|
66
|
-
await new Promise((r) => setTimeout(r, opts.delayMs ?? 1_500));
|
|
67
|
-
}
|
|
68
|
-
if (lastEstimate !== null) {
|
|
69
|
-
throw new Error(`Refusing to send: the RPC estimated ${lastEstimate} gas for a transaction that provably needs at least ${floor} ` +
|
|
70
|
-
`(storing ${floor / 200n} bytes on-chain costs 200 gas/byte in code deposit alone). An estimate below the floor means the node ` +
|
|
71
|
-
`is answering from stale state — usually it has not seen the contract's deploy block yet, so the call looks like it is going to ` +
|
|
72
|
-
`an empty account. Sending this would revert DeploymentFailed() and burn the gas. Retry in a few seconds, or use a different RPC.`);
|
|
73
|
-
}
|
|
74
|
-
throw lastError instanceof Error ? lastError : new Error('gas estimation failed');
|
|
75
|
-
}
|
|
76
|
-
//# sourceMappingURL=gas.js.map
|
package/dist/gas.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gas.js","sourceRoot":"","sources":["../src/gas.ts"],"names":[],"mappings":"AAwBA;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAoB,EAAE,OAAgB,EAAE,SAAS,GAAG,MAAM;IAC5F,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,SAAS,CAAC;QACR,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,EAAC,OAAO,EAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACpE,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACvC,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,MAAoB,EACpB,EAA+E,EAC/E,OAA8C,EAAE;IAEhD,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;IACpC,IAAI,YAAY,GAAkB,IAAI,CAAC;IACvC,IAAI,SAAkB,CAAC;IAEvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC;gBACxC,OAAO,EAAE,EAAE,CAAC,IAAI;gBAChB,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,SAAS;gBACtB,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;aACrE,CAAC,CAAC;YACH,kEAAkE;YAClE,IAAI,QAAQ,IAAI,KAAK;gBAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;YACvD,YAAY,GAAG,QAAQ,CAAC;QAC1B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,GAAG,GAAG,CAAC;YAChB,0FAA0F;YAC1F,4FAA4F;YAC5F,IAAI,KAAK,KAAK,EAAE;gBAAE,MAAM,GAAG,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC,GAAG,QAAQ,GAAG,CAAC;YAAE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC;IACvF,CAAC;IAED,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,uCAAuC,YAAY,uDAAuD,KAAK,GAAG;YAChH,YAAY,KAAK,GAAG,IAAI,wGAAwG;YAChI,iIAAiI;YACjI,kIAAkI,CACrI,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;AACpF,CAAC"}
|
package/dist/inspect.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `abx inspect <script.js>` — static analysis of a generative script, BEFORE picking a lane.
|
|
3
|
-
*
|
|
4
|
-
* The membrane fix behind this: an agent (or creator) should DERIVE the deployment lane from what
|
|
5
|
-
* the script actually needs — are there traits? are they reproducible on-chain? how big is the
|
|
6
|
-
* assembled document (does a single `tokenURI` eth_call even fit)? — instead of guessing "fully
|
|
7
|
-
* on-chain!" and walking it back. This module is the pure analysis; `cmdInspect` formats it.
|
|
8
|
-
*
|
|
9
|
-
* Static only — it never executes the script (that's the resolver/runner's job, and executing
|
|
10
|
-
* untrusted art in the CLI would be a footgun). Everything here is regex/heuristic over the source.
|
|
11
|
-
*/
|
|
12
|
-
export type TraitFeasibility = 'none' | 'exact-likely' | 'careful' | 'infeasible' | 'unknown';
|
|
13
|
-
export interface ScriptAnalysis {
|
|
14
|
-
bytes: number;
|
|
15
|
-
estChunks: number;
|
|
16
|
-
traits: {
|
|
17
|
-
present: boolean;
|
|
18
|
-
keys: string[];
|
|
19
|
-
};
|
|
20
|
-
paramHints: string[];
|
|
21
|
-
prng: {
|
|
22
|
-
seeded: boolean;
|
|
23
|
-
usesBareRandom: boolean;
|
|
24
|
-
usesMathRandom: boolean;
|
|
25
|
-
usesNoise: boolean;
|
|
26
|
-
};
|
|
27
|
-
depHints: string[];
|
|
28
|
-
looksP5: boolean;
|
|
29
|
-
feasibility: {
|
|
30
|
-
verdict: TraitFeasibility;
|
|
31
|
-
reason: string;
|
|
32
|
-
};
|
|
33
|
-
doc: {
|
|
34
|
-
estBytes: number;
|
|
35
|
-
deps: string[];
|
|
36
|
-
fitsSingleCall: boolean;
|
|
37
|
-
unknownDepSizes: boolean;
|
|
38
|
-
};
|
|
39
|
-
runtime: {
|
|
40
|
-
readsTokenData: boolean;
|
|
41
|
-
reportsTraits: boolean;
|
|
42
|
-
wrongGlobal: string | null;
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
export declare function analyzeScript(source: string, declaredDeps?: string[]): ScriptAnalysis;
|
|
46
|
-
/** A one-line lane recommendation derived from the analysis (the decision-tree output). */
|
|
47
|
-
export declare function recommendLane(a: ScriptAnalysis): string;
|
|
48
|
-
//# sourceMappingURL=inspect.d.ts.map
|
package/dist/inspect.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../src/inspect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,cAAc,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;AAE9F,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAC,CAAC;IAC3C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAC,CAAC;IAC9F,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE;QAAC,OAAO,EAAE,gBAAgB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,CAAC;IACzD,GAAG,EAAE;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,EAAE,OAAO,CAAC;QAAC,eAAe,EAAE,OAAO,CAAA;KAAC,CAAC;IAM3F,OAAO,EAAE;QAAC,cAAc,EAAE,OAAO,CAAC;QAAC,aAAa,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAC,CAAC;CACxF;AAoID,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,EAAO,GAAG,cAAc,CA0FzF;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CA0BvD"}
|