@artblocks/abx-cli 0.1.0-alpha.32 → 0.1.0-alpha.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/capabilities.d.ts +94 -0
  3. package/dist/capabilities.d.ts.map +1 -0
  4. package/dist/capabilities.js +135 -0
  5. package/dist/capabilities.js.map +1 -0
  6. package/dist/commands/deploy.d.ts.map +1 -1
  7. package/dist/commands/deploy.js +13 -17
  8. package/dist/commands/deploy.js.map +1 -1
  9. package/dist/commands/feedback.d.ts +7 -0
  10. package/dist/commands/feedback.d.ts.map +1 -0
  11. package/dist/commands/feedback.js +147 -0
  12. package/dist/commands/feedback.js.map +1 -0
  13. package/dist/commands/reads.js +1 -1
  14. package/dist/commands/reads.js.map +1 -1
  15. package/dist/commands/scaffold.d.ts +9 -1
  16. package/dist/commands/scaffold.d.ts.map +1 -1
  17. package/dist/commands/scaffold.js +60 -13
  18. package/dist/commands/scaffold.js.map +1 -1
  19. package/dist/commands/service.d.ts.map +1 -1
  20. package/dist/commands/service.js +5 -4
  21. package/dist/commands/service.js.map +1 -1
  22. package/dist/flag-allowlists.d.ts.map +1 -1
  23. package/dist/flag-allowlists.js +19 -0
  24. package/dist/flag-allowlists.js.map +1 -1
  25. package/dist/main.js +45 -11
  26. package/dist/main.js.map +1 -1
  27. package/dist/ownerops.d.ts +3 -3
  28. package/dist/ownerops.js +5 -5
  29. package/dist/ownerops.js.map +1 -1
  30. package/dist/remote.d.ts +5 -1
  31. package/dist/remote.d.ts.map +1 -1
  32. package/dist/remote.js +31 -2
  33. package/dist/remote.js.map +1 -1
  34. package/dist/scaffold.js +1 -1
  35. package/dist/scaffold.js.map +1 -1
  36. package/dist/update-check.d.ts +6 -1
  37. package/dist/update-check.d.ts.map +1 -1
  38. package/dist/update-check.js +40 -17
  39. package/dist/update-check.js.map +1 -1
  40. package/package.json +6 -6
  41. package/skill/SKILL.md +174 -526
  42. package/skill/agents/openai.yaml +4 -0
  43. package/skill/reference/capabilities.md +171 -285
  44. package/skill/reference/code.md +210 -0
  45. package/skill/reference/creator-token.md +90 -95
  46. package/skill/reference/deploy.md +167 -0
  47. package/skill/reference/diagnose.md +165 -0
  48. package/skill/reference/hosting.md +148 -126
  49. package/skill/reference/operate.md +181 -0
  50. package/skill/reference/services.md +76 -0
  51. package/skill/reference/setup.md +108 -62
  52. package/skill/reference/code-projects.md +0 -368
  53. package/skill/reference/decisions.md +0 -182
  54. package/skill/reference/operating.md +0 -220
  55. package/skill/reference/troubleshooting.md +0 -65
@@ -0,0 +1,165 @@
1
+ # Diagnosis and recovery
2
+
3
+ Use this reference when a command fails, a project is incomplete, metadata is wrong, a render is
4
+ missing, storage is propagating, a remote rejects a request, or an agent is tempted to add retries.
5
+
6
+ ## Contents
7
+
8
+ - [Diagnose one state transition at a time](#diagnose-one-state-transition-at-a-time)
9
+ - [State-to-action table](#state-to-action-table)
10
+ - [Nonces and serialized writes](#nonces-and-serialized-writes)
11
+ - [Incomplete deployments](#incomplete-deployments)
12
+ - [RPC and chain failures](#rpc-and-chain-failures)
13
+ - [Metadata and resolution failures](#metadata-and-resolution-failures)
14
+ - [Storage failures](#storage-failures)
15
+ - [Render and effects failures](#render-and-effects-failures)
16
+ - [Secret-safe reporting](#secret-safe-reporting)
17
+
18
+ ## Diagnose one state transition at a time
19
+
20
+ 1. Capture the command, exit code, typed error/status, active chain, target address, and signer lane.
21
+ Never capture secret values or complete credential-bearing URLs.
22
+ 2. Run `abx version` and `abx doctor`; correct binary/skill drift before interpreting behavior.
23
+ 3. Run the relevant read command (`state`, `tokens`, `tokenuri`, `contracturi`, `status`, `verify`,
24
+ `remote`, or `storage status`) to establish current state.
25
+ 4. Classify the result as nonterminal, terminal/configuration, terminal/on-chain, or integrity fault.
26
+ 5. Choose one documented transition. Re-read state after it. Stop when the state changes or a human
27
+ decision is required.
28
+
29
+ Do not run the same failed write with random flags, switch representations silently, rotate providers
30
+ without evidence, or wrap the CLI in an unbounded retry loop.
31
+
32
+ ## State-to-action table
33
+
34
+ | Observation | Meaning | Next action |
35
+ |---|---|---|
36
+ | `indexing`, `queued`, `running`, `propagating` | nonterminal work | use the command's `--watch`/status and wait |
37
+ | `401` from a remote | token absent/invalid | replace the configured credential, then probe once |
38
+ | `403` from a remote | credential lacks scope | fix provider authorization; do not rotate blindly |
39
+ | wrong chain id | endpoint/config mismatch | correct `ABX_CHAIN`/RPC; do not deploy another contract |
40
+ | no code at address | wrong chain/address or incomplete deploy | verify explorer/chain and deploy state before retrying |
41
+ | transaction reverted | on-chain rule rejected the exact call | inspect typed reason/tx; change intent or inputs, not nonce |
42
+ | integrity/hash mismatch | served bytes differ from commitment | stop publication; restore committed bytes or explicitly repoint |
43
+ | placeholder image | image surface not published/wired | choose renderer, public image base, or resolver/effects path |
44
+ | storage accepted but gateway 404 | propagation may be pending | `storage status`; wait if propagating |
45
+ | empty historical reconstruction | pruning/range-capped RPC may have answered `[]` | put a full-history endpoint first, then re-index |
46
+ | skill version/name warning | agent membrane is stale or duplicated | run the exact project/global `abx skill install` commands shown |
47
+
48
+ ## Nonces and serialized writes
49
+
50
+ ABX already owns nonce handling. A hot sender reads pending and latest counts once, uses the safe
51
+ maximum, increments locally for the sequence, waits for newly deployed code before dependent calls,
52
+ pins gas with bounded estimation, and reports a reverted receipt as an error.
53
+
54
+ The operational rule is therefore simple: **one EOA, one write process at a time**. Two processes can
55
+ start from the same nonce before either sees the other's pending transaction. If contention occurred:
56
+
57
+ 1. Stop additional writers.
58
+ 2. Read nonce coherence with `abx doctor` and check pending/mined transactions on the active chain.
59
+ 3. Wait for the pending transaction or resolve it using the wallet's standard replacement flow.
60
+ 4. Re-read project state; resume only the missing documented step.
61
+
62
+ Do not add “nonce too low” retries, increment a guessed nonce, or send concurrent replacement
63
+ transactions from the agent. Those layers fight the CLI's serializer and can duplicate value-bearing
64
+ operations.
65
+
66
+ ## Incomplete deployments
67
+
68
+ A failed multi-transaction code-project setup can leave a valid clone with missing setup legs. Use
69
+ `deploy-code --resume <address>` only where the capability output says resume is supported. It reads
70
+ the contract and sends only missing work; it is not a general “try deploy again” switch.
71
+
72
+ Before resume:
73
+
74
+ - verify the address, chain, contract family, owner, and original artifacts;
75
+ - use the same normalized script/dependencies/schema/renderer plan;
76
+ - read the resume dry run and confirm every proposed transaction;
77
+ - ensure no second writer is operating the same EOA.
78
+
79
+ EditionCode resume is not currently supported. Inspect state and stop for an explicit recovery or new
80
+ deployment decision rather than applying the 721 recipe.
81
+
82
+ ## RPC and chain failures
83
+
84
+ Use `abx doctor` rather than probing secret endpoints manually. Distinguish:
85
+
86
+ - connectivity/rate limit: endpoint did not provide a usable response;
87
+ - wrong network: endpoint chain id differs from `ABX_CHAIN`;
88
+ - shallow history: recent reads work but old logs are unavailable;
89
+ - range cap: large `eth_getLogs` queries fail or return misleading empty ranges;
90
+ - read-gas cap: a large on-chain `tokenURI` call exceeds that endpoint's allowance;
91
+ - stale distributed view: pending nonce or newly deployed code lags the endpoint's own head.
92
+
93
+ Put the best archive endpoint first. Fallback transports can accept an empty successful answer from a
94
+ pruned endpoint and never reach a healthy second endpoint. For large on-chain content, state whose RPC
95
+ was measured; do not generalize creator reach to marketplace reach.
96
+
97
+ If a write simulation or estimate reverts, preserve the exact representation the creator chose. Use a
98
+ read-only call/CLI diagnostic to surface the contract reason. Never silently replace an on-chain reader
99
+ with an off-chain URL merely to make the command pass.
100
+
101
+ ## Metadata and resolution failures
102
+
103
+ Start from the contract, not a constructed URL:
104
+
105
+ 1. `abx state <addr>` — contract family, renderer, owner powers, schemas/locks.
106
+ 2. `abx tokenuri <addr> --token <id>` — actual on-chain URI and decoded metadata.
107
+ 3. `abx contracturi <addr>` — actual collection URI.
108
+ 4. `abx verify <addr> --json` — commitments, chain-completeness, placeholders, public bytes.
109
+
110
+ Then follow the failing surface:
111
+
112
+ - URI absent/wrong: inspect URI pointer/renderer and deploy configuration.
113
+ - Metadata resolves but image fails: inspect image representation and public locator/gateway.
114
+ - Animation fails: inspect generated document, dependencies, target token data, and hosting.
115
+ - Traits missing: inspect decoded `attributes`, renderer/effects output, and publication—not only
116
+ program console traits.
117
+ - Attachments absent: verify resolver artifacts enumeration; bare on-chain metadata cannot enumerate
118
+ arbitrary keys.
119
+
120
+ Use `refresh` only after the current path is correct. Refreshing a broken URI makes a marketplace fetch
121
+ the same broken document again.
122
+
123
+ ## Storage failures
124
+
125
+ Run `abx storage show --check` with the intended backend overrides. For cloud, inspect both the private
126
+ put endpoint and public read base by hostname only; never print credentials. For IPFS, separate pinning
127
+ from gateway retrieval. For Arweave, use structured status to distinguish propagation from failure.
128
+
129
+ Retry policy:
130
+
131
+ - accepted/deduplicated upload: success; do not upload again;
132
+ - propagating locator: wait using status backoff;
133
+ - terminal authentication/configuration error: fix configuration before one new attempt;
134
+ - integrity mismatch: stop and restore/republish the committed bytes;
135
+ - transient provider failure: use the client's bounded retry or retry once after status evidence;
136
+ - repeated unknown failure: stop and report the smallest redacted reproduction.
137
+
138
+ Do not switch backend or public locator without telling the creator; that changes the custody plan.
139
+
140
+ ## Render and effects failures
141
+
142
+ A still is derived state keyed by current inputs. Diagnose the graph in order:
143
+
144
+ 1. Token/id is minted and indexed.
145
+ 2. Live document loads for that real id.
146
+ 3. Dependencies and PostParams resolve.
147
+ 4. Effects runner is authenticated and receives the job.
148
+ 5. Render bytes are non-placeholder and uploaded to public storage.
149
+ 6. Resolver receives/publishes the locator and traits.
150
+ 7. Token metadata exposes them.
151
+
152
+ Use `abx render --force` only when an existing render is known bad or stale; a plain render should
153
+ idempotently skip an existing current artifact. A PostParam change produces a new inputs hash; verify
154
+ the watcher or run the explicit one-shot path. Solidity image renderers have no effects job—diagnose
155
+ their on-chain call instead.
156
+
157
+ ## Secret-safe reporting
158
+
159
+ Include command name, CLI version, chain key, redacted host labels, contract address, transaction hash,
160
+ exit code, typed status/error, and expected versus observed state. Exclude `.env`, keys, tokens, JWKs,
161
+ wallet-session URLs, query strings, and raw provider responses containing request headers.
162
+
163
+ If the same blocking state survives three evidence-based transitions, stop. Report what was proven,
164
+ what was ruled out, and the exact external decision or state change required. Repetition is not
165
+ progress.
@@ -1,182 +1,204 @@
1
- # Hosting infrastructure
1
+ # Hosting, storage, and remote operation
2
2
 
3
- [← back to SKILL.md](../SKILL.md)
3
+ Use this reference when a project needs public resolution, managed remote service, creator-operated
4
+ resolver/effects, storage configuration, render publication, lifecycle monitoring, or migration.
4
5
 
5
- ## Storage backends (byte custody)
6
+ ## Contents
6
7
 
7
- Storage is **stateless** there's no config file. Choose a backend **per command** with flags (`--backend …`), or declare defaults in `.env` (`ABX_STORAGE_BACKEND`, `ABX_IPFS_GATEWAY`, `ABX_S3_BUCKET`, …); secrets stay in `.env`. Resolution is `flag → env → default`. Verify with `abx doctor`; `abx storage show` prints the resolved config with provenance.
8
+ - [First decide whether a host exists](#first-decide-whether-a-host-exists)
9
+ - [Choose managed or creator-operated resolution](#choose-managed-or-creator-operated-resolution)
10
+ - [Choose storage independently](#choose-storage-independently)
11
+ - [Operate resolver and effects services](#operate-resolver-and-effects-services)
12
+ - [Use lifecycle states](#use-lifecycle-states)
13
+ - [Migrate without losing canonicity](#migrate-without-losing-canonicity)
8
14
 
9
- | Backend | Guarantee | Choose (per command, or `.env`) | Secret in `.env` |
10
- |---|---|---|---|
11
- | **`fs`** (default) | Operator-held, local disk | — | — |
12
- | **`cloud`** | S3-compatible: AWS S3 / R2 / B2 / MinIO | `--backend cloud --endpoint <url> --bucket <b> --region <r>` (or `ABX_S3_ENDPOINT`/`ABX_S3_BUCKET`/`ABX_S3_REGION`) | `ABX_S3_ACCESS_KEY_ID`, `ABX_S3_SECRET_ACCESS_KEY` |
13
- | **`ipfs`** | Decentralized (CID) | kubo: `--backend ipfs --mode kubo --api-url http://127.0.0.1:5001 --gateway http://127.0.0.1:8080` · pinata: `--backend ipfs --mode pinata --gateway https://<you>.mypinata.cloud` | `PINATA_JWT` (pinata mode) |
14
- | **`arweave`** | Pay-once permanent | `--backend arweave` (Turbo default; or `ABX_STORAGE_BACKEND=arweave`) | the Turbo key (auto-managed) |
15
+ ## First decide whether a host exists
15
16
 
16
- All backends verify via the on-chain keccak commitment wherever the bytes live. **What a marketplace fetches is always an HTTPS gateway URL** → IPFS/Arweave custody needs a *public* gateway. The two public **floors**, used whenever a project states no preference, are:
17
+ Do not ask “where should we host?” until the public surfaces require a host.
17
18
 
18
- | Scheme | Floor (used when the project sets nothing) |
19
- |---|---|
20
- | `ipfs` | `https://ipfs.io/ipfs/` |
21
- | `arweave` | `https://arweave.net/` |
19
+ No ABX resolver is required when:
22
20
 
23
- Do not guess these and do not substitute the *upload* gateway for them — a project that uploaded through Pinata still **serves** from `ipfs.io` unless it said otherwise, and telling a creator otherwise gives them a URL their token does not use. A `127.0.0.1` gateway is preview-only (the CLI warns); the shared `gateway.pinata.cloud` is rate-limited — use a **dedicated** gateway for anything real, and set it with `--ipfs-gateway` (deploy) or `abx set-gateway` (after).
21
+ - static bytes and metadata resolve on-chain;
22
+ - on-chain JSON points at public Arweave/IPFS/cloud media;
23
+ - Solidity field renderers compute every required metadata surface on-chain.
24
24
 
25
- **A resolver adds nothing marketplace-facing to a project that already resolves on-chain — say so before you set one up.** The docs below explain when a resolver *helps* (mutable metadata, code drops needing rendering, off-chain traits); the inverse is just as load-bearing and was missing. Check first: `abx state <addr>` printing `renderer on-chain (0x…)` means `tokenURI` answers from the chain, so wallets and marketplaces read **that** document and never touch a resolver. Registering with one is still worth doing for **indexing, the live view, and managed rendering** — but it will not change a single thing a collector sees, and a creator told to "put it on a hosted service so it shows up properly" needs to hear that plainly rather than be walked through a setup that changes nothing. (`abx add --remote` now says this too.) Conversely, a project whose renderer is **off-chain** genuinely depends on a resolver, and that is the case the rest of this page is about.
25
+ A resolver is useful or required when:
26
26
 
27
- **Which gateway, and who can change it.** For a `keccak256`-custody field the resolver builds that URL from the backend's own gateway. For an `ipfs`/`arweave` field — what `--onchain-uri --backend ipfs|arweave` writes the chain holds only the CID/txid, and the prefix comes from the collection's `abx_gateway_ipfs` / `abx_gateway_arweave` setting (public defaults when unset). Set it at deploy with `--ipfs-gateway` / `--arweave-gateway` (plain `--gateway` seeds the one matching your backend), and change it any time with **`abx set-gateway <addr> --ipfs <prefix> --arweave <prefix>`** — one tx, every token, no re-upload, and it works on fields that are already locked. `--gateway` remains the UPLOAD/probe gateway; these are the SERVING one, and they are the same value in almost every project.
27
+ - metadata must remain operationally editable;
28
+ - a JavaScript/build project needs a hosted live document;
29
+ - marketplace stills/traits are produced by an effects runner;
30
+ - attached artifacts must be enumerated and fetched;
31
+ - large on-chain content needs an HTTP reader in front of endpoint-dependent `eth_call` execution;
32
+ - a project needs indexed status/dashboard/API surfaces.
28
33
 
29
- **Code projects (directory mode) need a gateway that serves HTML.** A `deploy-code --code-dir` build's live view 302-redirects to `<gateway>/ipfs/<cid>/index.html` — and the **shared Pinata public gateway blocks HTML** (`ERR_ID:00023`, "HTML content cannot be served through the pinata public gateway"). So a code directory drop requires a **dedicated** Pinata gateway (paid) or **Arweave** (`https://arweave.net` serves HTML). Static image directories (`deploy-series`) are unaffected — only the executable HTML build hits this.
34
+ Storage can be on-chain while resolution is hosted, or external while metadata resolution is
35
+ on-chain. Keep those axes separate.
30
36
 
31
- **Durability is not the same across backends — educate the creator by name:**
32
- - **`arweave`** — pay once, permanent. Arweave's storage endowment funds retention for centuries (~200 years at current economics); the bytes resolve as long as the network and any gateway are up. Nothing to renew, nothing to re-pin. The strongest "never lapses" custody.
33
- - **`ipfs`** — **only as durable as the pin.** The token's `image` resolves through the creator's Pinata (or kubo) gateway; if the pin is dropped — account lapses, billing fails, node goes offline — the bytes can become unretrievable and the image breaks. IPFS is *content-addressed* (the CID proves integrity) but **not self-storing** (someone must keep hosting the bytes). Tell the creator plainly: with IPFS they've taken on an ongoing pinning responsibility. Mitigate by pinning to a durable service and not letting it lapse — or prefer `arweave` and be done.
34
- - **`cloud`/`fs`** — durable only while the creator maintains the bucket/disk; centralized, mutable.
35
- - **Backends are swappable** — the on-chain keccak is the anchor, so bytes are portable: re-upload to the new backend and re-point the `image` field (`abx set-field <addr> --field image …`), then `abx verify` confirms the served bytes still match the on-chain hash. For a hosted-resolver project, `abx migrate` re-pins node-custody images to a durable backend as part of the move (see [operating.md](operating.md)). Re-pointing is an owner-signed tx, so keep the owner wallet secure. Starting on `fs`/`ipfs` and moving to `arweave` later is fine; just don't let an interim backend lapse before the move.
37
+ ## Choose managed or creator-operated resolution
36
38
 
37
- ### Arweave via Turbo the easy permanent path (read before quoting setup)
39
+ Both routes implement the same public resolver contract. The token holds a base URI; the operator
40
+ reconstructs canonical state from chain and serves metadata/live/data surfaces.
38
41
 
39
- `arweave` defaults to the **Turbo** provider (ArDrive). For small content it needs **no setup and costs nothing** — no wallet dashboard, no JWT (don't send anyone to `app.arweave.org`). How it works:
42
+ ### Managed remote
40
43
 
41
- - **Identity is a lane (`--storage-signer`) Turbo is multi-chain, so credits attach to whichever identity signs the upload.** Three choices, mirroring the tx signing lanes:
42
- - **`arweave`** *(default)* — a CLI-managed Arweave key auto-generated at `.abx-self-host/arweave-key.json` the first time a command actually needs it — an upload, or a `storage balance` / `storage topup` (so a creator can fund the identity *before* uploading). Reads that never need a signer (`doctor`, `--dry-run`, `storage show`) do not create one. The address is printed with a back-it-up notice on creation. Reused across every upload/project; it signs *and* holds credits. Zero-config. Bring-your-own via `ARWEAVE_JWK` (inline) / `ABX_ARWEAVE_KEY_FILE` (path). **Back it up:** `abx storage backup-key --out <path>` (copies the file — the key is never printed to the terminal/log).
43
- - **`--storage-signer eth`** — reuse the **`.env` EVM signing key** (`ABX_DEPLOYER_PK`) as the Turbo identity. Its ETH address holds the credits, so credits funded on that wallet's Turbo balance are spendable. Hot lane (key in `.env`).
44
- - **`--storage-signer eth` + `--sign`** — the **browser wallet** signs each upload data-item via `personal_sign` (no gas, no funds move; the upload is paid from the wallet's Turbo credits), in the same sign session as the deploy tx. The key never leaves the wallet. *(New; the mechanism matches arbundles' `InjectedEthereumSigner` and is unit-tested at the CLI↔page contract, but the live MetaMask↔Turbo path wants a manual smoke test. If a live upload misbehaves, fall back to `--storage-signer eth` with a key, or fund the managed key.)*
45
- - **Under 100 KB → free**, permanent, zero setup. The CLI prints this from the file size (in `--dry-run` too).
46
- - **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.
47
- - **Credits attach to the IDENTITY, not "you".** With the default `arweave` lane, a top-up funds the CLI-managed Arweave address (derived from the key file) — a *different* identity from the ETH wallet used to sign deploys. So a fresh managed key shows **0 credits even if the creator "funded Turbo before"**: those old credits sit on whatever identity they funded. If they funded their **ETH wallet's** Turbo balance, reach it with `--storage-signer eth` (key) or `--storage-signer eth --sign` (browser). If they hold a funded **Arweave** key, import it via `ARWEAVE_JWK`/`ABX_ARWEAVE_KEY_FILE`.
48
- - **If the Stripe link fails**, fall back to Turbo's hosted top-up (`https://turbo-topup.com`) and fund the **exact address `abx storage topup` prints** (it accepts a recipient address, so credits still land on the intended identity). Not ideal, but it beats abandoning Arweave for IPFS on a link glitch.
44
+ Use a named remote already configured in the environment rather than standing up duplicate
45
+ infrastructure. Start with:
49
46
 
50
- #### When a Turbo upload fails — diagnose, don't guess
47
+ ```bash
48
+ abx remote <name>
49
+ ```
51
50
 
52
- A failed upload is the #1 place an agent goes off the rails: it invents a cause and bails. Don't.
51
+ `abx` is the built-in name for the first-party service at `services.abx.io`; it needs only
52
+ `ABX_SERVICES_API_KEY`, not a remote URL variable. Read [services.md](services.md) for its verified
53
+ signup, current access model, and provider-feedback path. Other providers use their configured name.
53
54
 
54
- - **Read the actual error and show the human verbatim.** The CLI prints Turbo's real message (incl. the raw response). **Never invent a cause** — "transient hiccup", "Turbo is down", "the browser path is new/flaky", "known issue per the docs" are hallucinations unless the error text says so. If you don't know, say "here's the exact error" and read it.
55
- - **"…has already been uploaded to this service!" is SUCCESS, not failure.** Turbo deduplicates identical bytes (same signer + bytes → same data-item id), so re-uploads (a retry, a redeploy, two identical images) return that sentence instead of JSON. The CLI treats it as the existing txid and continues. If you ever see it surface as an error, the toolkit is stale — don't work around it.
56
- - **Check BOTH balances before concluding "needs funding."** `abx storage balance --backend arweave` prints the managed key's balance **and** the deployer wallet's Turbo balance (Turbo exposes balance-by-address publicly — no key needed). The managed key showing 0 does NOT mean "top up": if the **wallet** has credits, use them (`--storage-signer eth`, or `+ --sign` for a browser wallet). The pre-upload funds guard does this check for you and recommends the wallet lane when it applies — checking only the managed key's 0 and pushing a top-up is the classic wrong turn.
57
- - **Don't reflexively push a $5 top-up or "switch to IPFS."** Both throw away what the creator chose (their funded wallet credits; permanence). Only top up if *neither* the managed key nor the wallet can cover the bytes — and **back up the managed key first** (`abx storage backup-key --out <path>`), since it will hold the credits you buy. Only suggest IPFS if they ask or Arweave is truly unavailable.
58
- - **A blind retry re-signs the same bytes → same dedup reply.** Retry only after you've identified and fixed the actual cause.
59
- - **Gateway swappable, integrity independent.** The on-chain keccak256 (or the CID itself) is the anchor; `arweave.net` is the default gateway (`--gateway` at upload, `abx set-gateway` for serving). A gateway issue is a re-point, never a lost asset.
60
- - **Provider swappable.** `--provider http-bundler --upload-url <bundler>` uses a dep-free token-authed endpoint you run (secret `ARWEAVE_UPLOAD_TOKEN`); funding then means "fund the wallet directly" (only Turbo tracks credits). Default to Turbo unless asked.
55
+ This reports the service descriptor, supported chains, rendering policy, and authentication status.
56
+ Do not infer provider capability from its hostname or marketing page.
61
57
 
62
- ### Directory-base image off-chain, JSON on-chain, no server (the medium/large static default)
58
+ Interpret control-plane failures precisely:
63
59
 
64
- `abx deploy-series --onchain-uri --backend arweave` (or `ipfs`/`cloud`) puts the **image off-chain** while the **on-chain renderer** assembles the JSON — **no resolver to run**, the token self-resolves. How the CLI wires it:
60
+ - `401` means the supplied token is missing, stale, or invalid; replace the credential.
61
+ - `403` means the credential is recognized but lacks permission for this chain/project; fix provider
62
+ scope rather than rotating keys blindly.
63
+ - a conformance failure means the service contract is incomplete or incompatible; do not register a
64
+ production launch until the failing assertion is understood.
65
65
 
66
- - **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.
67
- - **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.
68
- - **`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.
69
- - **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.
70
- - **`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>`.
66
+ Register or operate a project using `--remote <name>` and monitor with `abx status --remote <name>`.
67
+ If the provider advertises managed rendering, confirm it for the active chain/project. Otherwise the
68
+ creator still owns the effects and storage path.
71
69
 
72
- ## Managed providers a resolver someone else runs (`--remote <name>`)
70
+ Never invent a provider, signup flow, price, quota, or key source. If no configured provider exists,
71
+ offer the fully supported creator-operated route.
73
72
 
74
- 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)).
73
+ ### Creator-operated resolver
75
74
 
76
- - **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.
77
- - **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.
78
- - **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.
79
- - **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.
80
- - **The exit is guaranteed by the interface**, not by the provider's goodwill: registration is never load-bearing for resolution, and `abx migrate --from <provider> --to <anywhere>` reads only the provider's *public* endpoints — leaving is one config change plus (for a provider-hostname base) one on-chain re-point.
81
- - **Honesty:** no default provider is baked into the CLI, the SDK, or this skill, and the provider market is only starting to form. A creator with a provider key (or a named remote already in `.env`) should use it; otherwise **self-hosting (next section) is the fully-supported path today** and what the rest of this file assumes.
75
+ Use `abx deploy-resolver --provider …` to generate hosting artifacts for the selected platform. The
76
+ creator owns the cloud account, domain, secrets, monitoring, and upgrades. Review the generated
77
+ configuration before deploying it; do not copy repository `.env` wholesale.
82
78
 
83
- ## Self-hosted resolver `abx deploy-resolver`
79
+ The host needs read-only RPC access, storage access where applicable, and a stable public URL. Keep
80
+ signing keys off the resolver. Use the project/state API and on-chain reconstruction instead of a
81
+ private source-of-truth database.
84
82
 
85
- For the large/mutable default, when the creator runs the node themselves (the alternative to a managed provider above — same interface, they own the cloud account). `abx deploy-resolver --provider <fly|render|vps> --domain <meta.you.xyz>` scaffolds the artifact and prints the exact next steps, the DNS record, and the bake reminder. Providers: **fly.io** / **render** (Docker PaaS, custom domain) and a **VPS** (compose + Caddy auto-TLS). **Do not quote their plans or promise a free tier** — those move, and a stale claim in here becomes a stale claim to a creator; point at the provider's own pricing page and let them read it. The host is **read-only** serves + accepts admin index-control, no signing key on it (writes are signed locally), so a compromised host can at worst serve wrong bytes (the keccak catches it). Prefer **a domain you control** (move = DNS, not a tx) — but without one the scaffold now bakes the **real platform hostname** (`<app>.fly.dev` / `<app>.onrender.com`) so the resolver works out of the box (add a custom domain later). It NO LONGER bakes a dead `<app>.example` placeholder, and the resolver **refuses to serve** an `.example`/placeholder base (or a localhost base in a hosted image, `ABX_HOSTED=1`) — a loud fail beats silently serving dead image/animation links. The one sub-decision is *which provider* (ask + recommend); you scaffold, the human owns the cloud account + domain.
83
+ Prefer a creator-controlled domain in the on-chain base URI. Provider-specific hostnames work, but a
84
+ custom domain makes migration a DNS operation rather than a contract operation. Tunnels and localhost
85
+ are preview-only and must never be baked into a launch.
86
86
 
87
- **The artifact is fully self-contained.** `deploy-resolver` writes `deploy/<provider>/` with its OWN `Dockerfile` + `.dockerignore` + config (a production image installs the published CLI: `npm i -g --no-optional @artblocks/abx-cli`). **Run every next step from that dir.** You never supply, copy, or hand-edit a Dockerfile. **If a step seems to need a file from elsewhere (`../Dockerfile`, a `packages/` dir, the repo), STOP — that's a scaffold bug, not something to work around.** Report it; don't MacGyver it. (Local from-source/contributor dev sets `ABX_RESOLVER_SOURCE=1` → a build-from-source artifact instead, still self-contained; you don't set this.)
87
+ After provisioning:
88
88
 
89
- **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.
89
+ 1. Run health/conformance checks.
90
+ 2. Register the contract with its deploy block when known.
91
+ 3. Wait for indexing readiness.
92
+ 4. Fetch contract and token metadata through the public URL.
93
+ 5. Verify byte commitments with `abx verify`.
94
+ 6. Mint only after required token-specific surfaces can become ready.
90
95
 
91
- `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.
96
+ ## Choose storage independently
92
97
 
93
- **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."
98
+ Storage is stateless configuration selected per command or through environment defaults. Inspect the
99
+ resolved choice with:
94
100
 
95
- **Driving the deploy (fly/render/vps):** the human owns the cloud account. **Preflight the CLI FIRST — don't assume it's missing OR installed:** run `fly version` (is it there? `flyctl` and `fly` are the SAME binary — the modern alias is `fly`; don't tell them to install what they already have) and `fly auth whoami` (logged in?). Only send them to `fly auth login` — interactive, they do it — if `whoami` fails. Then run the generated steps for them. Deploys take minutes: run `fly deploy` and let it finish, don't background-and-poll. If it fails, read the actual error — a build that needs repo files means the artifact is wrong (above), not that you should copy things in.
101
+ ```bash
102
+ abx storage show --check
103
+ ```
96
104
 
97
- ## Render runner `abx deploy-effects`
105
+ | Backend | Good for | Operator responsibility |
106
+ |---|---|---|
107
+ | `fs` | local development | disk durability and no public reach by default |
108
+ | `cloud` | mutable/fast public assets | bucket, auth endpoint, public read base/CDN, retention |
109
+ | `ipfs` | content-addressed distribution | pinning and public gateway availability |
110
+ | `arweave` | permanent external custody | upload identity/credits and propagation |
98
111
 
99
- 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:
112
+ Never silently fall back to `fs` when a selected backend is incomplete. Fix the missing configuration
113
+ or change the plan explicitly.
100
114
 
101
- - **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.
102
- - **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.
115
+ ### Cloud
103
116
 
104
- One-shot without a service: **`abx render <addr> --remote <resolver>`** renders locally and registers with the hosted resolver same requirement, so pass `--backend cloud|ipfs|arweave` (the default `fs` is refused before Chromium launches, naming the fix). Re-run any time: registration is idempotent and repairs a resolver that lost its rows without re-rendering. Optional immediacy: set `ABX_EFFECTS_URL` on the resolver so it pings the runner on re-index; the periodic sweep is the eventual floor regardless.
117
+ Separate the authenticated S3/R2 API endpoint used for writes from the public HTTP base used by
118
+ collectors. An R2 S3 endpoint is not a marketplace image URL. `--check` performs a real write/read
119
+ round trip through the public base; require it to pass.
105
120
 
106
- ## Local vs remote — two SEPARATE projection stores (read before deploying to a remote resolver)
121
+ ### IPFS
107
122
 
108
- 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:**
109
- - **Local resolver** (`abx serve` here): a local `deploy`/`add` already indexed it done.
110
- - **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>`.
123
+ Pinning success and gateway retrieval are distinct. Use a durable pinning service and public gateway
124
+ for launches. A local kubo node is suitable for development only. The on-chain field may hold the
125
+ bare CID while a collection gateway preference chooses the serving prefix; `abx set-gateway` can
126
+ change that prefix without changing the content.
111
127
 
112
- ### Indexing status — `backfilling` is not a failure
128
+ ### Arweave
113
129
 
114
- A service may accept a registration and catch up **asynchronously** (a cold replay on a rate-limited RPC takes minutes). `abx add --remote` handles it: prints `registered — backfilling…`, polls, then prints the normal summary once the project is **`live`**. So a slow add is *slow*, not broken — don't re-run `add` to "retry" it (a re-POST never starts a second replay, but you'll just wait again). `--no-wait` returns immediately when you don't want to block.
130
+ The accepted upload and a retrievable gateway object are separate lifecycle states. Use
131
+ `abx storage status <locator> --json`; wait for `ready` instead of uploading duplicates during
132
+ propagation. Upload deduplication is success, not an instruction to top up or switch backends.
115
133
 
116
- **Check anytime same five words locally and remotely:** `abx status <address>` (this node) · `abx status <address> --remote <name> [--watch]` (a service) · `abx remote <name>` (roll-up: *"3 live, 1 backfilling, 1 failed (rpc_rate_limited)"*).
134
+ Turbo credits attach to the signing identity. `abx storage balance` reports the managed identity and
135
+ wallet-related lanes; choose the intended payer before funding. Back up the managed key using the
136
+ dedicated command and never expose the JWK.
117
137
 
118
- **On a node YOU run**, two knobs shape this (both optional): `ABX_REGISTER_DEADLINE_MS` (default 8000) is how long a register may hold the HTTP request before answering "accepted, still catching up" and finishing in the background — the registration is durable either way, so this only decides who waits; and `ABX_STALE_LAG_BLOCKS` (default 5000) is how far behind chain head the watcher may fall before it reports its projects `stale`. A backfill interrupted by a restart is re-queued automatically on the next `abx serve` — you don't have to re-add it.
138
+ ### Publication bridge
119
139
 
120
- | status | what it means | what to do |
121
- | --- | --- | --- |
122
- | `queued` / `backfilling` | accepted, catching up (registration is already durable) | **wait** poll, don't re-add |
123
- | `live` | caught up, tracking head | done |
124
- | `stale` | was live, now lagging — **still serving** old state | usually the service's RPC; investigate if it sticks |
125
- | `failed` | catch-up errored; retried with backoff | read the **class** (below); the registration survived |
140
+ An effects runner stores rendered bytes, then publishes their locator to the resolver. The resolver
141
+ usually redirects to that public object rather than proxying it. Therefore the storage backend must be
142
+ reachable from both the runner and collectors. A local `fs` output from one machine is orphaned when
143
+ the public resolver runs elsewhere.
126
144
 
127
- `failed` names a cause: **`rpc_rate_limited`/`rpc_unavailable`** = the *service's* RPC, not your key and not your address — it retries, so wait or tell the operator; **`not_abx_contract`** = wrong address/chain (or a provider that validates clones refusing a non-clone); **`internal`** = the operator's logs have it. Report the class verbatim to the user rather than guessing a cause.
145
+ ## Operate resolver and effects services
128
146
 
129
- **A `live` project with 0 events is a red flag, not a success** — a real ABX clone always emits a spine, so 0 events means the service scanned the wrong chain or its RPC didn't serve the logs (check `abx remote <name>` covers your chain, and that the deploy block was forwarded). The CLI warns instead of printing ✓ here.
147
+ The resolver handles indexed state and public metadata/live/data routes. The effects service executes
148
+ programs and publishes stills/traits. Deploy them separately so heavy browser work does not destabilize
149
+ metadata reads.
130
150
 
131
- ### Deploying to a hosted resolver pre-warm flow (preferred)
151
+ For an active JavaScript sale, run continuous effects. For a fixed supply or repair, a one-shot render
152
+ may be sufficient. With an on-chain Solidity image renderer, no effects service is needed for the
153
+ image; do not deploy infrastructure merely because the collection is a code contract.
132
154
 
133
- The deploy address is deterministic (`predict` = a pure function of factory + salt), so have the resolver watch the address *before* the token mints metadata is live the instant a marketplace sees it, no cached blank:
134
- ```bash
135
- abx predict # 1. prints predicted address + reserved --salt (+ exact deploy cmd)
136
- abx add <predicted> --remote --from-block <now> # 2. resolver starts WATCHING that address (nothing there yet)
137
- ABX_PUBLIC_BASE_URL=https://meta.you.xyz abx deploy --image ./art.png --name "Aurora" --salt <salt> # 3. deploy + MINT in one tx (same salt!)
138
- abx add <predicted> --remote # 4. one nudge → pulls the deploy events + bridges the image locator
139
- abx refresh <predicted> # 5. marketplaces (already resolvable → they cache the real thing)
140
- ```
141
- Use the **exact `--salt` that `predict` printed** in step 3 (a plain `deploy` reserves a *different* salt → a different address than the resolver is watching). `--from-block <now>` = current block (`cast block-number`, or just `0` on a quiet testnet).
155
+ Protect public effects endpoints with the generated token. An unauthenticated force-render endpoint
156
+ can burn compute and storage. Keep resolver admin/effects credentials separate from wallet signing.
142
157
 
143
- Why pre-warm: minting *before* the resolver knows the contract lets a marketplace fetch in that window, cache a miss, and need a later refresh. Registering the predicted address first closes the window. (There's no background poller — the resolver indexes only what you tell it — so a single nudge after deploy is still needed; the win is *ordering*, not eliminating the nudge.)
158
+ Verify the operational graph:
144
159
 
145
- **Simpler, if you don't mind the small window:** skip the pre-warm — `deploy` (mint at deploy) → `abx add <addr> --remote` → `abx refresh`. Fine for a quiet testnet; prefer pre-warm for anything real.
160
+ 1. Resolver can reconstruct chain state from its configured RPC.
161
+ 2. Effects can load the exact live document for a minted id.
162
+ 3. Effects storage produces a publicly retrievable locator.
163
+ 4. Resolver publishes or redirects to that locator.
164
+ 5. Token metadata exposes the resulting image/attributes.
165
+ 6. A PostParam change reaches the watcher and creates the next inputs-hash render.
146
166
 
147
- ### Local off-chain serve flow (resolver on this machine)
167
+ ## Use lifecycle states
148
168
 
149
- ```bash
150
- abx doctor # 1. green light (incl. storage backend)
151
- export ABX_STORAGE_BACKEND=arweave # 2. pick custody (or pass --backend arweave per deploy); Turbo identity auto-managed (back up the key file)
152
- ABX_PUBLIC_BASE_URL=https://my.node abx deploy --image ./art.png --name "Aurora" --no-mint # 3. custody → commit → deploy (no mint) → index
153
- abx serve # 4. serve the resolver at the known address (blocks)
154
- abx mint <address> # 5. issue token #0 once metadata is live
155
- abx refresh <address> # 6. nudge marketplaces to index it
156
- ```
157
- Drop `--no-mint` (steps 5–6 collapse) for the express path. Add `--sign` to step 3/5 for the wallet lane. Confirm integrity with `abx verify <address>` (re-hashes served bytes vs the on-chain commitment — no server needed; the `/verify` HTTP route is the same check while serving). `abx index <address>` re-reconstructs from chain; `abx status` lists projects + the active backend.
169
+ Prefer status over retries. Typical nonterminal states include deployment accepted, indexing,
170
+ storage propagating, render queued/running, and remote registration pending. Typical terminal faults
171
+ include authentication/authorization failure, invalid contract/chain, interface mismatch, failed
172
+ transaction, integrity mismatch, and unsupported configuration.
158
173
 
159
- ## Token API (the resolver)
174
+ Use:
160
175
 
161
- ⚠ **Read this list; never infer a route from another one — and prefer not needing it at all.** The contract commits its own metadata URL on-chain, so **`abx tokenuri <addr>`** and **`abx contracturi <addr>`** (ERC-7572 collection metadata: reads `contractURI()`, follows it, decodes the JSON) answer "where does this resolve, and what does it say" without you assembling a URL. Guessing has a specific trap: **`/t/<chainId>/<address>` is NOT collection metadata** — collection metadata is `/c/<chainId>/<address>`. A miss now names which of three things happened, in a machine `code`: **400 `invalid_request`** (your path shape — the body names the right template and may carry `didYouMean`) · **404 `unknown_route`** (this node serves no such path; body lists what it does serve) · **404 `not_registered`** (path fine, this node doesn't index that contract → `abx add <addr> --remote`) · **400 `unsupported_chain`** (body carries the `chains` it serves) · **410 `burned`** (that token existed and was destroyed — permanent; stop asking, and drop it from any listing. An *edition* id with zero live copies is NOT this: it still serves, and can mint again). None of those mean the service is down — don't report an outage off a URL you built.
176
+ ```bash
177
+ abx status [address] --watch
178
+ abx status <address> --remote <name> --watch
179
+ abx storage status <locator> --json
180
+ abx verify <address> --json
181
+ ```
162
182
 
163
- Default `http://localhost:8787` (or `ABX_PUBLIC_BASE_URL`). Routes carry the **chainId** (the protocol path grammar) so one host can serve many chains:
164
- - `GET /` read-only index of served contracts · `GET /t/<chainId>/<address>/0` — token metadata (ERC-721 `tokenURI` on a 1/1/Series/code project, ERC-1155 `uri` on an edition — auto-detected, same route) · `…/0/image` — the image
165
- - `GET /c/<chainId>/<address>` — ERC-7572 collection metadata · `GET /api/project/<address>` — full reconstructed state · `GET /d/<chainId>/<address>` — per-contract read-only dashboard (namespaced so one host serves many contracts). No public action buttons anywhere.
166
- - For IPFS/Arweave the served `image` is the **gateway HTTPS URL** (`https://<gateway>/ipfs/<cid>` / `<gateway>/<txid>`), the form wallets/marketplaces render (raw `ipfs://` doesn't). The on-chain commitment is the **keccak256** (backend-neutral anchor, survives a gateway migration); the CID/txid is just the locator. The locator lives in the **deployer's** local index, so a **remote** resolver emits the gateway URL only once it's bridged (`abx add <clone> --remote`), else `image` falls back to the resolver's own `/…/image` route.
167
- - `GET /.well-known/abx-service` — the **service descriptor** (public): what the node supports — `interfaces`, `chains`, `auth` (incl. `signupUrl` for humans), `render.attached` (managed rendering). Read it before registering with any remote service.
168
- - `POST /v1/projects` `{chainId, address, fromBlock?, factory?, description?, externalUrl?, attributes?, contentLocators?}` · `GET /v1/projects` (the projects visible to the token, each with `status`) · `DELETE /v1/projects/<chainId>/<address>` · `POST /v1/projects/<chainId>/<address>/reindex` · `GET /v1/projects/<chainId>/<address>/status` (`status` + `fromBlock`/`toBlock`/`headBlock` + `error.class`) — the **control plane** ([spec](../../../../specs/self-host-toolkit/remote-services.md)): register/forget which contracts the node indexes, bridge off-chain traits + locators; a remote `add`/`forget`. Bearer-gated (`Authorization: Bearer <token>`), 404 code `disabled` when no token is configured. Errors carry a machine `code` (`unauthorized` 401 · `forbidden` 403 · `unsupported_chain` · `not_registered`). Register answers **200** (caught up, with counts) or **202** (accepted, still catching up — poll status); the registration is durable either way. Indexing control only — never signing.
169
- - `GET /api/project/<address>/verify` — bearer-gated too (it triggers outbound fetches). Run from the CLI: `abx verify <addr>` / `abx index <addr> --remote`.
183
+ Wait on nonterminal states using the command's watcher/backoff. Do not wrap the CLI in a second tight
184
+ poller. Do not retry terminal 4xx responses or reverted transactions unchanged. Read
185
+ [diagnose.md](diagnose.md) for the one-transition recovery model.
170
186
 
171
- `/api/project/<address>` key fields: `isCanonical` (factory-verified — note: `isCanonical`, **not** `canonical`), `owner`, `royalty`, `collectionFields[]`/`lockedCollectionFields[]`, `extensions[]`, `tokens[]` each `{minted, owner, tokenURI, fields[] ({field, representation, value}), lockedFields[]}`. The JSON also carries **`abx_provenance`** — per-field `source` + `status` (`on-chain` · `verified` · `mismatch` · `anchored` · `off-chain` · `n/a`), with an `anchor` for off-chain bytes that carry an on-chain hash (`off-chain` = plain operator value, benign; `anchored` = verifiable via `abx verify`). Confirm a deploy with **`abx verify <addr>`**, not curl.
187
+ ## Migrate without losing canonicity
172
188
 
173
- **`serve` heads-up:** the `pnpm`/`tsx` wrapper detaches, so a backgrounded `abx serve &` may report the wrapper process exiting while the server keeps running — check the **port** (curl it), not the process.
189
+ Migration changes a public projection, not canonical on-chain history. Use `abx migrate` to copy and
190
+ compare resolver state without cutting over prematurely.
174
191
 
175
- ## Docker / droplet
192
+ 1. Resolve the source and destination descriptors and credentials.
193
+ 2. Confirm both support the active chain and contract type.
194
+ 3. Reconstruct/register the destination from chain using the deploy block.
195
+ 4. Copy only off-chain operator state and published artifact locators that are not derivable from chain.
196
+ 5. Verify contract metadata, representative token metadata, fields, attachments, renders, and byte
197
+ commitments on both sides.
198
+ 6. Cut over via DNS when using a stable creator domain, or change the on-chain URI pointer only after
199
+ parity is proven.
200
+ 7. Re-emit URI/refresh signals as required, then monitor the destination.
201
+ 8. Keep the old service until marketplace and collector paths have converged.
176
202
 
177
- The container is **emitted by `deploy-resolver --provider vps`** it writes `deploy/vps/` with a `Dockerfile` + `docker-compose.yml` + `Caddyfile` + `setup.sh`, self-contained. Don't hand-write a Dockerfile or compose file. Run compose **from that dir**:
178
- ```bash
179
- cd deploy/vps
180
- docker compose up -d --build # builds the emitted Dockerfile, serves on :8787
181
- ```
182
- One small container (`node:24-slim`, SQLite projection on a volume — no DB service, no native modules). State persists in the `abx-data` volume. Set `ABX_RPC_URLS` / `ABX_PUBLIC_BASE_URL` / secrets via the `.env` the setup writes (sourced, never inlined); Caddy fronts it with automatic TLS. Sizing → [`specs/self-host-toolkit/deployment.md`](../../../../specs/self-host-toolkit/deployment.md).
203
+ Never describe resolver migration as moving the NFT. Ownership, parameters, commitments, and canonical
204
+ events remain on-chain; only the serving/indexing projection changes.