@artblocks/abx-cli 0.1.0-alpha.4 → 0.1.0-alpha.41

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 (189) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/assets/renderer-scaffold/README.md +41 -11
  3. package/assets/renderer-scaffold/foundry.toml +5 -0
  4. package/assets/renderer-scaffold/remappings.txt +1 -1
  5. package/assets/renderer-scaffold/script/DeployHooks.s.sol +24 -0
  6. package/assets/renderer-scaffold/script/Preview.s.sol +99 -0
  7. package/assets/renderer-scaffold/src/MyHooks.sol +20 -0
  8. package/assets/renderer-scaffold/src/MyRenderer.sol +4 -4
  9. package/assets/renderer-scaffold/src/MyTraits.sol +2 -2
  10. package/assets/renderer-scaffold/test/MyRenderer.t.sol +60 -3
  11. package/dist/bin.d.ts +26 -0
  12. package/dist/bin.d.ts.map +1 -0
  13. package/dist/bin.js +63 -0
  14. package/dist/bin.js.map +1 -0
  15. package/dist/capabilities.d.ts +99 -0
  16. package/dist/capabilities.d.ts.map +1 -0
  17. package/dist/capabilities.js +144 -0
  18. package/dist/capabilities.js.map +1 -0
  19. package/dist/commands/auth.d.ts +54 -0
  20. package/dist/commands/auth.d.ts.map +1 -0
  21. package/dist/commands/auth.js +447 -0
  22. package/dist/commands/auth.js.map +1 -0
  23. package/dist/commands/deploy.d.ts +242 -0
  24. package/dist/commands/deploy.d.ts.map +1 -0
  25. package/dist/commands/deploy.js +5429 -0
  26. package/dist/commands/deploy.js.map +1 -0
  27. package/dist/commands/feedback.d.ts +7 -0
  28. package/dist/commands/feedback.d.ts.map +1 -0
  29. package/dist/commands/feedback.js +147 -0
  30. package/dist/commands/feedback.js.map +1 -0
  31. package/dist/commands/maintenance.d.ts +4 -0
  32. package/dist/commands/maintenance.d.ts.map +1 -0
  33. package/dist/commands/maintenance.js +114 -0
  34. package/dist/commands/maintenance.js.map +1 -0
  35. package/dist/commands/project.d.ts +429 -0
  36. package/dist/commands/project.d.ts.map +1 -0
  37. package/dist/commands/project.js +1720 -0
  38. package/dist/commands/project.js.map +1 -0
  39. package/dist/commands/reads.d.ts +85 -0
  40. package/dist/commands/reads.d.ts.map +1 -0
  41. package/dist/commands/reads.js +868 -0
  42. package/dist/commands/reads.js.map +1 -0
  43. package/dist/commands/scaffold.d.ts +89 -0
  44. package/dist/commands/scaffold.d.ts.map +1 -0
  45. package/dist/commands/scaffold.js +738 -0
  46. package/dist/commands/scaffold.js.map +1 -0
  47. package/dist/commands/service.d.ts +67 -0
  48. package/dist/commands/service.d.ts.map +1 -0
  49. package/dist/commands/service.js +748 -0
  50. package/dist/commands/service.js.map +1 -0
  51. package/dist/commands/storage.d.ts +51 -0
  52. package/dist/commands/storage.d.ts.map +1 -0
  53. package/dist/commands/storage.js +399 -0
  54. package/dist/commands/storage.js.map +1 -0
  55. package/dist/commands/submit-app.d.ts +102 -0
  56. package/dist/commands/submit-app.d.ts.map +1 -0
  57. package/dist/commands/submit-app.js +622 -0
  58. package/dist/commands/submit-app.js.map +1 -0
  59. package/dist/config.d.ts +105 -2
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/config.js +351 -12
  62. package/dist/config.js.map +1 -1
  63. package/dist/conformance.d.ts +31 -0
  64. package/dist/conformance.d.ts.map +1 -0
  65. package/dist/conformance.js +390 -0
  66. package/dist/conformance.js.map +1 -0
  67. package/dist/contract-read-error.d.ts +5 -0
  68. package/dist/contract-read-error.d.ts.map +1 -0
  69. package/dist/contract-read-error.js +37 -0
  70. package/dist/contract-read-error.js.map +1 -0
  71. package/dist/deploy-plan.d.ts +150 -0
  72. package/dist/deploy-plan.d.ts.map +1 -0
  73. package/dist/deploy-plan.js +31 -0
  74. package/dist/deploy-plan.js.map +1 -0
  75. package/dist/deps.d.ts +6 -39
  76. package/dist/deps.d.ts.map +1 -1
  77. package/dist/deps.js +4 -68
  78. package/dist/deps.js.map +1 -1
  79. package/dist/errors.d.ts +20 -0
  80. package/dist/errors.d.ts.map +1 -0
  81. package/dist/errors.js +25 -0
  82. package/dist/errors.js.map +1 -0
  83. package/dist/flag-allowlists.d.ts +53 -0
  84. package/dist/flag-allowlists.d.ts.map +1 -0
  85. package/dist/flag-allowlists.js +184 -0
  86. package/dist/flag-allowlists.js.map +1 -0
  87. package/dist/flags.d.ts +47 -0
  88. package/dist/flags.d.ts.map +1 -1
  89. package/dist/flags.js +125 -1
  90. package/dist/flags.js.map +1 -1
  91. package/dist/jsonout.d.ts +37 -0
  92. package/dist/jsonout.d.ts.map +1 -0
  93. package/dist/jsonout.js +79 -0
  94. package/dist/jsonout.js.map +1 -0
  95. package/dist/kind.d.ts +57 -0
  96. package/dist/kind.d.ts.map +1 -0
  97. package/dist/kind.js +122 -0
  98. package/dist/kind.js.map +1 -0
  99. package/dist/main.js +786 -4838
  100. package/dist/main.js.map +1 -1
  101. package/dist/mintpage.d.ts +17 -2
  102. package/dist/mintpage.d.ts.map +1 -1
  103. package/dist/mintpage.js +241 -54
  104. package/dist/mintpage.js.map +1 -1
  105. package/dist/output.d.ts +179 -0
  106. package/dist/output.d.ts.map +1 -0
  107. package/dist/output.js +780 -0
  108. package/dist/output.js.map +1 -0
  109. package/dist/ownerops.d.ts +381 -57
  110. package/dist/ownerops.d.ts.map +1 -1
  111. package/dist/ownerops.js +2014 -359
  112. package/dist/ownerops.js.map +1 -1
  113. package/dist/preview.d.ts +23 -5
  114. package/dist/preview.d.ts.map +1 -1
  115. package/dist/preview.js +95 -43
  116. package/dist/preview.js.map +1 -1
  117. package/dist/prompt.d.ts +17 -0
  118. package/dist/prompt.d.ts.map +1 -0
  119. package/dist/prompt.js +19 -0
  120. package/dist/prompt.js.map +1 -0
  121. package/dist/provision.d.ts +3 -13
  122. package/dist/provision.d.ts.map +1 -1
  123. package/dist/provision.js +19 -21
  124. package/dist/provision.js.map +1 -1
  125. package/dist/remote.d.ts +157 -52
  126. package/dist/remote.d.ts.map +1 -1
  127. package/dist/remote.js +435 -46
  128. package/dist/remote.js.map +1 -1
  129. package/dist/riskgate.d.ts +62 -0
  130. package/dist/riskgate.d.ts.map +1 -0
  131. package/dist/riskgate.js +234 -0
  132. package/dist/riskgate.js.map +1 -0
  133. package/dist/scaffold.d.ts +12 -0
  134. package/dist/scaffold.d.ts.map +1 -0
  135. package/dist/scaffold.js +56 -0
  136. package/dist/scaffold.js.map +1 -0
  137. package/dist/schema.d.ts +36 -1
  138. package/dist/schema.d.ts.map +1 -1
  139. package/dist/schema.js +121 -26
  140. package/dist/schema.js.map +1 -1
  141. package/dist/script-chunks.d.ts +8 -0
  142. package/dist/script-chunks.d.ts.map +1 -0
  143. package/dist/script-chunks.js +35 -0
  144. package/dist/script-chunks.js.map +1 -0
  145. package/dist/served.d.ts +30 -0
  146. package/dist/served.d.ts.map +1 -0
  147. package/dist/served.js +112 -0
  148. package/dist/served.js.map +1 -0
  149. package/dist/signer.d.ts +13 -0
  150. package/dist/signer.d.ts.map +1 -1
  151. package/dist/signer.js +84 -15
  152. package/dist/signer.js.map +1 -1
  153. package/dist/update-check.d.ts +86 -5
  154. package/dist/update-check.d.ts.map +1 -1
  155. package/dist/update-check.js +161 -20
  156. package/dist/update-check.js.map +1 -1
  157. package/package.json +13 -12
  158. package/skill/SKILL.md +181 -347
  159. package/skill/agents/openai.yaml +4 -0
  160. package/skill/reference/capabilities.md +188 -0
  161. package/skill/reference/code.md +220 -0
  162. package/skill/reference/creator-token.md +94 -0
  163. package/skill/reference/deploy.md +174 -0
  164. package/skill/reference/diagnose.md +178 -0
  165. package/skill/reference/hosting.md +186 -93
  166. package/skill/reference/operate.md +220 -0
  167. package/skill/reference/services.md +121 -0
  168. package/skill/reference/setup.md +154 -36
  169. package/assets/renderer-scaffold/src/interfaces/IAbxFieldRenderer.sol +0 -32
  170. package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +0 -26
  171. package/dist/inspect.d.ts +0 -48
  172. package/dist/inspect.d.ts.map +0 -1
  173. package/dist/inspect.js +0 -184
  174. package/dist/inspect.js.map +0 -1
  175. package/dist/migrate.d.ts +0 -65
  176. package/dist/migrate.d.ts.map +0 -1
  177. package/dist/migrate.js +0 -180
  178. package/dist/migrate.js.map +0 -1
  179. package/dist/onchain-uri.d.ts +0 -97
  180. package/dist/onchain-uri.d.ts.map +0 -1
  181. package/dist/onchain-uri.js +0 -243
  182. package/dist/onchain-uri.js.map +0 -1
  183. package/dist/upload.d.ts +0 -28
  184. package/dist/upload.d.ts.map +0 -1
  185. package/dist/upload.js +0 -41
  186. package/dist/upload.js.map +0 -1
  187. package/skill/reference/code-projects.md +0 -246
  188. package/skill/reference/operating.md +0 -116
  189. package/skill/reference/troubleshooting.md +0 -28
@@ -1,138 +1,231 @@
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. **The served `image` is the backend's gateway HTTPS URL** → IPFS/Arweave custody needs a *public* gateway (Arweave's `https://arweave.net` is the public default). 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.
17
+ Do not ask “where should we host?” until the public surfaces require a host.
17
18
 
18
- **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.
19
+ No ABX resolver is required when:
19
20
 
20
- **Durability is not the same across backends — educate the creator by name:**
21
- - **`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.
22
- - **`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.
23
- - **`cloud`/`fs`** — durable only while the creator maintains the bucket/disk; centralized, mutable.
24
- - **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.
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.
25
24
 
26
- ### Arweave via Turbo the easy permanent path (read before quoting setup)
25
+ A resolver is useful or required when:
27
26
 
28
- `arweave` defaults to the **Turbo** provider (ArDrive). For small art it needs **no setup and costs nothing** — no wallet dashboard, no JWT (don't send anyone to `app.arweave.org`). How it works:
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.
29
33
 
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
- - **`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 (`ABX_DEPLOYER_PK`/`SEPOLIA_FUNDED_PK`/`SEPOLIA_WALLET_PK`). Its ETH address holds the credits, so credits funded on that wallet's Turbo balance are spendable. Hot lane (key in `.env`).
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
- - **Under 100 KB → free**, permanent, zero setup. The CLI prints this from the file size (in `--dry-run` too).
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.
36
- - **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`.
37
- - **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.
34
+ Storage can be on-chain while resolution is hosted, or external while metadata resolution is
35
+ on-chain. Keep those axes separate.
38
36
 
39
- #### When a Turbo upload fails — diagnose, don't guess
37
+ ## Choose managed or creator-operated resolution
40
38
 
41
- A failed upload is the #1 place an agent goes off the rails: it invents a cause and bails. Don't.
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.
42
41
 
43
- - **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.
44
- - **"…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.
45
- - **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.
46
- - **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.
47
- - **A blind retry re-signs the same bytes → same dedup reply.** Retry only after you've identified and fixed the actual cause.
48
- - **Gateway swappable, integrity independent.** The on-chain keccak256 is the anchor; `arweave.net` is the default gateway (`--gateway` to override). A gateway issue is a re-point, never a lost asset.
49
- - **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.
42
+ ### Managed remote
50
43
 
51
- ### Directory-base image off-chain, JSON on-chain, no server (the medium/large static default)
44
+ Use a named remote already configured in the environment rather than standing up duplicate
45
+ infrastructure. Start with:
52
46
 
53
- `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:
54
-
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
- - **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 needs a **public read base** (`--public-base` / `ABX_S3_PUBLIC_BASE`, also what `deploy-code --image-base` takes), **distinct from the signed-API `--endpoint`/`ABX_S3_ENDPOINT`** — they are different hosts. ⚠ **R2:** `https://<acct>.r2.cloudflarestorage.com` is the *API endpoint* (auth-only, never public → 403 for marketplaces); the public read URL is a `https://pub-<hash>.r2.dev` you ENABLE in the dashboard, or a custom domain. AWS: a public-read bucket or a CloudFront domain. The full set: `ABX_S3_ENDPOINT`/`BUCKET`/`REGION`/`ACCESS_KEY_ID`/`SECRET_ACCESS_KEY` + `ABX_S3_PUBLIC_BASE` (`abx storage show` prints them). Files upload under a content-derived prefix → `<publicBase>/<prefix>/{id}.png`. **Caveat:** S3 is centralized, mutable, not content-addressed (no CID/txid root). Bake a domain/CDN you control. Good if the creator already runs a bucket/CDN; IPFS/Arweave are the trustless/permanent options.
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
- - **`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>`.
47
+ ```bash
48
+ abx remote <name>
49
+ ```
60
50
 
61
- ## Hosted resolver `abx deploy-resolver`
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
+ OAuth login, manual recovery path, current access model, and provider-feedback path. Other providers
54
+ use their configured name.
62
55
 
63
- For the large/mutable default. `abx deploy-resolver --provider <fly|render|vps> --domain <meta.you.xyz>` scaffolds the artifact and prints the exact next steps, the DNS record, and the bake reminder. Providers: **fly.io** / **render** (Docker PaaS, free tier, custom domain) and a **VPS** (compose + Caddy auto-TLS). The host is **read-only** — serves + accepts admin index-control, no signing key on it (writes are signed locally), so a compromised host can at worst serve wrong bytes (the keccak catches it). Prefer **a domain you control** (move = DNS, not a tx) — but without one the scaffold now bakes the **real platform hostname** (`<app>.fly.dev` / `<app>.onrender.com`) so the resolver works out of the box (add a custom domain later). It NO LONGER bakes a dead `<app>.example` placeholder, and the resolver **refuses to serve** an `.example`/placeholder base (or a localhost base in a hosted image, `ABX_HOSTED=1`) — a loud fail beats silently serving dead image/animation links. The one sub-decision is *which provider* (ask + recommend); you scaffold, the human owns the cloud account + domain.
56
+ This reports the service descriptor, supported chains, rendering policy, and authentication status.
57
+ Do not infer provider capability from its hostname or marketing page.
64
58
 
65
- **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.)
59
+ Interpret control-plane failures precisely:
66
60
 
67
- **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.
61
+ - `401` means the supplied token is missing, stale, or invalid; replace the credential.
62
+ - `403` means the credential is recognized but lacks permission for this chain/project; fix provider
63
+ scope rather than rotating keys blindly.
64
+ - a conformance failure means the service contract is incomplete or incompatible; do not register a
65
+ production launch until the failing assertion is understood.
68
66
 
69
- `deploy-resolver` also **generates `ABX_RESOLVER_ADMIN_TOKEN`** → your local `.env` (a secret never printed, env-only); the printed steps set the **same** token on the host. 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.
67
+ Register or operate a project using `--remote <name>` and monitor with `abx status --remote <name>`.
68
+ If the provider advertises managed rendering, confirm it for the active chain/project. Otherwise the
69
+ creator still owns the effects and storage path.
70
70
 
71
- **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."
71
+ Never invent a provider, auth flow, price, quota, or key source. If no configured provider exists,
72
+ offer the fully supported creator-operated route.
72
73
 
73
- **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.
74
+ ### Creator-operated resolver
74
75
 
75
- ## Render runner — `abx deploy-effects`
76
+ Use `abx deploy-resolver --provider …` to generate hosting artifacts for the selected platform. The
77
+ creator owns the cloud account, domain, secrets, monitoring, and upgrades. Review the generated
78
+ configuration before deploying it; do not copy repository `.env` wholesale.
76
79
 
77
- A code project's marketplace still is rendered off-chain, so a **runner** (Playwright + Chromium — the resolver image stays browserless) must render each token's live view and hand the resolver the result. `abx deploy-effects --resolver-url <resolver>` scaffolds it for Fly (a self-contained `Dockerfile.effects` + `fly.toml` + vendored source), wired to the resolver and your storage home. **Decide where renders live up front** (`ABX_STORAGE_BACKEND`): **ipfs / arweave** (the runner uploads and publishes a durable `ipfs://`/`ar://` locator the resolver 302-redirects to) or **s3** — **NOT** the default `fs` for a hosted setup (a laptop-local store a hosted resolver can't read, so the placeholder never clears). The runner **publishes** each render to the resolver's admin control plane (`POST /admin/render-artifacts`, gated by the SAME `ABX_RESOLVER_ADMIN_TOKEN` — index-control only, never signing), so it does NOT need to share the resolver's disk. Two topologies:
80
+ The host needs read-only RPC access, storage access where applicable, and a stable public URL. Keep
81
+ signing keys off the resolver. Use the project/state API and on-chain reconstruction instead of a
82
+ private source-of-truth database.
78
83
 
79
- - **Locator bridge (default for a hosted drop):** runner on its own host + a public storage home; it publishes locators, the resolver redirects. `deploy-effects` sets this up.
80
- - **Co-located:** runner beside the resolver sharing one backend/volume (no admin token needed both read/write the same store).
84
+ Prefer a creator-controlled domain in the on-chain base URI. Provider-specific hostnames work, but a
85
+ custom domain makes migration a DNS operation rather than a contract operation. Tunnels and localhost
86
+ are preview-only and must never be baked into a launch.
81
87
 
82
- One-shot without a service: **`abx render <addr> --remote <resolver>`** renders locally and publishes to the hosted resolver (re-run to restore a resolver that lost its volume — the publish is idempotent). 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.
88
+ After provisioning:
83
89
 
84
- ## Local vs remote — two SEPARATE projection stores (read before deploying to a hosted resolver)
90
+ 1. Run health/conformance checks.
91
+ 2. Register the contract with its deploy block when known.
92
+ 3. Wait for indexing readiness.
93
+ 4. Fetch contract and token metadata through the public URL.
94
+ 5. Verify byte commitments with `abx verify`.
95
+ 6. Mint only after required token-specific surfaces can become ready.
85
96
 
86
- A project is served only if it's in **the resolver's own** store. `abx deploy`/`abx add` (no `--remote`) index into **this machine's** store; a hosted resolver is a **different store** and doesn't learn about a contract just because you deployed it. A local deploy pointing its `tokenURI` at a remote resolver returns `{"error":"unknown project"}` for everyone — "works on my `serve`, broken for the world." **Bridge it:**
87
- - **Local resolver** (`abx serve` here): a local `deploy`/`add` already indexed it — done.
88
- - **Remote resolver** (baked URL is hosted): after deploy run **`abx add <clone> --remote [url]`** to register + index it on the node (url defaults to `ABX_PUBLIC_BASE_URL`; needs `ABX_RESOLVER_ADMIN_TOKEN` matching the resolver). 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>`.
97
+ ## Choose storage independently
89
98
 
90
- ### Deploying to a hosted resolver pre-warm flow (preferred)
99
+ Storage is stateless configuration selected per command or through environment defaults. Inspect the
100
+ resolved choice with:
91
101
 
92
- 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:
93
102
  ```bash
94
- abx predict # 1. prints predicted address + reserved --salt (+ exact deploy cmd)
95
- abx add <predicted> --remote --from-block <now> # 2. resolver starts WATCHING that address (nothing there yet)
96
- 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!)
97
- abx add <predicted> --remote # 4. one nudge → pulls the deploy events + bridges the image locator
98
- abx refresh <predicted> # 5. marketplaces (already resolvable → they cache the real thing)
103
+ abx storage show --check
99
104
  ```
100
- 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).
101
105
 
102
- 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.)
106
+ | Backend | Good for | Operator responsibility |
107
+ |---|---|---|
108
+ | `fs` | local development | disk durability and no public reach by default |
109
+ | `cloud` | mutable/fast public assets | bucket, auth endpoint, public read base/CDN, retention |
110
+ | `ipfs` | content-addressed distribution | pinning and public gateway availability |
111
+ | `arweave` | permanent external custody | upload identity/credits and propagation |
112
+
113
+ Never silently fall back to `fs` when a selected backend is incomplete. Fix the missing configuration
114
+ or change the plan explicitly.
115
+
116
+ ### Cloud
117
+
118
+ Separate the authenticated S3/R2 API endpoint used for writes from the public HTTP base used by
119
+ collectors. An R2 S3 endpoint is not a marketplace image URL. `--check` performs a real write/read
120
+ round trip through the public base; require it to pass.
121
+
122
+ ### IPFS
123
+
124
+ Pinning success and gateway retrieval are distinct. Use a durable pinning service and public gateway
125
+ for launches. A local kubo node is suitable for development only. The on-chain field may hold the
126
+ bare CID while a collection gateway preference chooses the serving prefix; `abx set-gateway` can
127
+ change that prefix without changing the content.
128
+
129
+ ### Arweave
130
+
131
+ `--backend arweave` (the default `turbo` provider) needs the optional
132
+ `@artblocks/abx-storage-arweave` package installed alongside the CLI — it is not part of the
133
+ default install, deliberately, so a default `abx` install stays free of Turbo's browser
134
+ wallet-connector dependency tree. If it is missing, the CLI names the exact install command
135
+ (`npm install @artblocks/abx-storage-arweave`) rather than failing unhelpfully; run that command
136
+ once, then retry. `--provider http-bundler` needs neither this package nor any of its dependencies.
137
+
138
+ The accepted upload and a retrievable gateway object are separate lifecycle states. Use
139
+ `abx storage status <locator> --json`; wait for `ready` instead of uploading duplicates during
140
+ propagation. Upload deduplication is success, not an instruction to top up or switch backends.
141
+
142
+ Turbo credits attach to the signing identity. `abx storage balance` reports the managed identity and
143
+ wallet-related lanes; choose the intended payer before funding. Back up the managed key using the
144
+ dedicated command and never expose the JWK.
145
+
146
+ ### Publication bridge
147
+
148
+ An effects runner stores rendered bytes, then publishes their locator to the resolver. The resolver
149
+ usually redirects to that public object rather than proxying it. Therefore the storage backend must be
150
+ reachable from both the runner and collectors. A local `fs` output from one machine is orphaned when
151
+ the public resolver runs elsewhere.
103
152
 
104
- **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.
153
+ ## Operate resolver and effects services
105
154
 
106
- ### Local off-chain serve flow (resolver on this machine)
155
+ The resolver handles indexed state and public metadata/live/data routes. The effects service executes
156
+ programs and publishes stills/traits. Deploy them separately so heavy browser work does not destabilize
157
+ metadata reads.
158
+
159
+ For an active JavaScript sale, run continuous effects. For a fixed supply or repair, a one-shot render
160
+ may be sufficient. With an on-chain Solidity image renderer, no effects service is needed for the
161
+ image; do not deploy infrastructure merely because the collection is a code contract.
162
+
163
+ Protect public effects endpoints with the generated token. An unauthenticated force-render endpoint
164
+ can burn compute and storage. Keep resolver admin/effects credentials separate from wallet signing.
165
+
166
+ Verify the operational graph:
167
+
168
+ 1. Resolver can reconstruct chain state from its configured RPC.
169
+ 2. Effects can load the exact live document for a minted id.
170
+ 3. Effects storage produces a publicly retrievable locator.
171
+ 4. Resolver publishes or redirects to that locator.
172
+ 5. Token metadata exposes the resulting image/attributes.
173
+ 6. A PostParam change reaches the watcher and creates the next inputs-hash render.
174
+
175
+ ### SQLite maintenance for a long-running node
176
+
177
+ The self-hosted store is one SQLite file. `abx serve` already reclaims freed pages automatically in
178
+ small bounded passes between chain-watch ticks — never inline with a request, so it never adds
179
+ latency to a metadata read. Nothing to schedule for that half.
180
+
181
+ The other half is explicit and never automatic: a store created before this maintenance shipped
182
+ needs a one-time conversion.
107
183
 
108
184
  ```bash
109
- abx doctor # 1. green light (incl. storage backend)
110
- export ABX_STORAGE_BACKEND=arweave # 2. pick custody (or pass --backend arweave per deploy); Turbo identity auto-managed (back up the key file)
111
- ABX_PUBLIC_BASE_URL=https://my.node abx deploy --image ./art.png --name "Aurora" --no-mint # 3. custody commit deploy (no mint) index
112
- abx serve # 4. serve the resolver at the known address (blocks)
113
- abx mint <address> # 5. issue token #0 once metadata is live
114
- abx refresh <address> # 6. nudge marketplaces to index it
185
+ abx vacuum # status: auto_vacuum mode, page count, freelist size
186
+ abx vacuum convert # one-time full VACUUM only when abx vacuum says the store needs it
187
+ abx vacuum incremental [--pages n] # one bounded reclaim pass on demand (not running abx serve? use this)
115
188
  ```
116
- 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.
117
189
 
118
- ## Token API (the resolver)
190
+ Run `abx vacuum convert` deliberately, not on a schedule: it rewrites the entire file and can briefly
191
+ need up to ~2x its on-disk size. Check `abx vacuum` first; if it already reports `auto_vacuum:
192
+ incremental`, there is nothing to convert.
119
193
 
120
- Default `http://localhost:8787` (or `ABX_PUBLIC_BASE_URL`). Routes carry the **chainId** (the protocol path grammar) so one host can serve many chains:
121
- - `GET /` — read-only index of served contracts · `GET /t/<chainId>/<address>/0` — ERC-721 metadata · `…/0/image` — the image
122
- - `GET /c/<chainId>/<address>` — ERC-7572 collection metadata · `GET /api/project/<address>` — full reconstructed state · `GET /d/<chainId>/<address>` — per-contract read-only dashboard (namespaced so one host serves many contracts). No public action buttons anywhere.
123
- - For IPFS/Arweave the served `image` is the **gateway HTTPS URL** (`https://<gateway>/ipfs/<cid>` / `<gateway>/<txid>`), the form wallets/marketplaces render (raw `ipfs://` doesn't). The on-chain commitment is the **keccak256** (backend-neutral anchor, survives a gateway migration); the CID/txid is just the locator. The locator lives in the **deployer's** local index, so a **remote** resolver emits the gateway URL only once it's bridged (`abx add <clone> --remote`), else `image` falls back to the resolver's own `/…/image` route.
124
- - `POST /api/project/<address>/reindex` (full replay) · `GET /api/project/<address>/verify` — both **admin-only** (bearer `ABX_RESOLVER_ADMIN_TOKEN`), never exposed as public actions. Run from the CLI: `abx index <addr> --remote` / `abx verify <addr>`.
125
- - `POST /admin/projects` `{address, fromBlock?, factory?, description?, externalUrl?, attributes?, contentLocators?}` + `DELETE /admin/projects/<address>` — the **admin control plane** (register/forget which contracts the node indexes, bridge off-chain traits + locators; a remote `add`/`forget`). Bearer-gated, disabled when the var is unset. Indexing control only — never signing.
194
+ ## Use lifecycle states
126
195
 
127
- `/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.
196
+ Prefer status over retries. Typical nonterminal states include deployment accepted, indexing,
197
+ storage propagating, render queued/running, and remote registration pending. Typical terminal faults
198
+ include authentication/authorization failure, invalid contract/chain, interface mismatch, failed
199
+ transaction, integrity mismatch, and unsupported configuration.
128
200
 
129
- **`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.
201
+ Use:
130
202
 
131
- ## Docker / droplet
132
-
133
- 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**:
134
203
  ```bash
135
- cd deploy/vps
136
- docker compose up -d --build # builds the emitted Dockerfile, serves on :8787
204
+ abx status [address] --watch
205
+ abx status <address> --remote <name> --watch
206
+ abx storage status <locator> --json
207
+ abx verify <address> --json
137
208
  ```
138
- 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).
209
+
210
+ Wait on nonterminal states using the command's watcher/backoff. Do not wrap the CLI in a second tight
211
+ poller. Do not retry terminal 4xx responses or reverted transactions unchanged. Read
212
+ [diagnose.md](diagnose.md) for the one-transition recovery model.
213
+
214
+ ## Migrate without losing canonicity
215
+
216
+ Migration changes a public projection, not canonical on-chain history. Use `abx migrate` to copy and
217
+ compare resolver state without cutting over prematurely.
218
+
219
+ 1. Resolve the source and destination descriptors and credentials.
220
+ 2. Confirm both support the active chain and contract type.
221
+ 3. Reconstruct/register the destination from chain using the deploy block.
222
+ 4. Copy only off-chain operator state and published artifact locators that are not derivable from chain.
223
+ 5. Verify contract metadata, representative token metadata, fields, attachments, renders, and byte
224
+ commitments on both sides.
225
+ 6. Cut over via DNS when using a stable creator domain, or change the on-chain URI pointer only after
226
+ parity is proven.
227
+ 7. Re-emit URI/refresh signals as required, then monitor the destination.
228
+ 8. Keep the old service until marketplace and collector paths have converged.
229
+
230
+ Never describe resolver migration as moving the NFT. Ownership, parameters, commitments, and canonical
231
+ events remain on-chain; only the serving/indexing projection changes.
@@ -0,0 +1,220 @@
1
+ # Operating an existing ABX project
2
+
3
+ Use this reference for inspection, minting and sales, transfers, fields, gateways, URI pointers,
4
+ royalties, supply, attachments, refreshes, ownership transfer, and locks.
5
+
6
+ ## Contents
7
+
8
+ - [Read before writing](#read-before-writing)
9
+ - [Mint and run primary sales](#mint-and-run-primary-sales)
10
+ - [Transfer tokens and authority](#transfer-tokens-and-authority)
11
+ - [Operate metadata and data](#operate-metadata-and-data)
12
+ - [Manage economics and supply](#manage-economics-and-supply)
13
+ - [Lock precisely](#lock-precisely)
14
+
15
+ ## Read before writing
16
+
17
+ Never infer contract type or current authority from the original launch notes. Read the contract:
18
+
19
+ ```bash
20
+ abx state <address>
21
+ abx tokens <address> --json
22
+ abx contracturi <address>
23
+ abx tokenuri <address> --token <id>
24
+ abx verify <address> --json
25
+ abx artifacts <address> --token <id> --json
26
+ ```
27
+
28
+ `abx artifacts` reads a token's `artifacts` manifest directly, without fetching and parsing the whole
29
+ served document — entries plus every registered effect row (current and stale, labeled against the
30
+ token's active `inputsHash`). Report which surface answered: this node's local projection, or (with
31
+ `--remote <name|url>`) the hosted resolver that actually owns a hosted project's real artifact set.
32
+ Neither "not registered here" nor "not registered on that remote" is an error — both return a stable
33
+ `{surface, registered, available, reason, entries, effects}` shape. Nothing it reports is onchain data
34
+ itself; every entry is a resolver-published projection.
35
+
36
+ Use `abx status` or its remote form for projection/render lifecycle. Record active chain, detected
37
+ family, owner/admin, supply and pause state, minter/payee, royalty/cap, URI renderer/pointers, code
38
+ dependencies, schemas/hooks, and lock state.
39
+
40
+ Before every write, run its help and dry-run form when available. Confirm sender, target, value,
41
+ token/id/amount, and permanence. Let the CLI validate contract type; do not send raw ABI calls to make
42
+ an operation fit the wrong family.
43
+
44
+ ## Mint and run primary sales
45
+
46
+ `abx mint` follows the detected family:
47
+
48
+ - A 1/1 issues its single token.
49
+ - A Series mints the next id in order; `--count` can mint multiple sequential ids.
50
+ - An edition requires `--token-id` except where the contract has only the single 1/1-edition id, and
51
+ uses `--amount` for copies. `--count` is not an edition synonym.
52
+
53
+ Confirm the recipient. Minting may trigger a transfer hook; a hook that vetoes mint will revert the
54
+ whole transaction.
55
+
56
+ ### Shared fixed-price minter
57
+
58
+ Use the shared minter commands for ordinary ETH primary sales:
59
+
60
+ 1. `abx minter configure <token> …` sets price and allocation. Editions require a token id and use a
61
+ per-id sale configuration.
62
+ 2. `abx set-minter <token> --minter <address>` authorizes the minter.
63
+ 3. `abx set-primary-payee <token> --payee <address>` declares payout.
64
+ 4. `abx unpause <token>` opens minting when the family supports the pause gate.
65
+ 5. `abx minter show <token> …` verifies the public sale state.
66
+ 6. `abx minter buy <token> …` is the public purchase path; editions use quantity and token id.
67
+
68
+ Do not call a sale live until price units, allocation, payee, minter address, pause state, and a
69
+ representative purchase are verified. `abx mint-page` scaffolds a client for the shared minter; it does
70
+ not configure the on-chain sale for you.
71
+
72
+ Use a custom minter for auctions, allowlists, raffles, free claims, ERC-20 pricing, or other issuance
73
+ rules. The custom contract calls the canonical token's mint authority; the token itself remains a
74
+ factory clone. Read [capabilities.md](capabilities.md).
75
+
76
+ ## Transfer tokens and authority
77
+
78
+ `abx transfer` moves token ownership/copies, not contract administration.
79
+
80
+ - A 721 transfer names token id and destination.
81
+ - An edition transfer names token id, amount, destination, and source holder because an id can have
82
+ multiple holders.
83
+ - Creator-token validation or a transfer hook may reject the move. Diagnose the actual revert; do not
84
+ bypass or clear an enforcement mechanism without the owner's explicit intent.
85
+
86
+ `abx set-admin --to <address>` transfers contract authority. Treat it as a high-risk, potentially
87
+ irrecoverable handoff:
88
+
89
+ 1. Verify the recipient is correct for the active chain.
90
+ 2. Inventory unfinished operations and unlocked surfaces.
91
+ 3. State which powers move and which external controllers remain separate.
92
+ 4. Receive exact human confirmation.
93
+ 5. Execute once, then read state from chain to prove the new owner.
94
+
95
+ Never interpret a token transfer as a sale listing. ABX has no secondary order book; a marketplace or
96
+ manual transfer handles secondary exchange.
97
+
98
+ ## Operate metadata and data
99
+
100
+ ### URI pointers and renderers
101
+
102
+ Use `set-token-uri`, `set-contract-uri`, and `set-renderer` only after reading the current configuration
103
+ and verifying the destination. Switching `set-renderer --off` can place an HTTP resolver in front of
104
+ content that remains stored on-chain. Switching to the canonical renderer makes configured on-chain
105
+ fields self-resolving when the contract supports them.
106
+
107
+ Never hand-build a resolver path. Read `tokenURI`/`contractURI`, decode it, and follow what the
108
+ contract actually returns. A correct-looking URL constructed from memory is not evidence.
109
+
110
+ ### Fields
111
+
112
+ `abx set-field` writes one metadata field representation at token or collection scope. Representations
113
+ include inline data, reader/chunk pointers, hashes, public locators, and renderer pointers as supported
114
+ by the contract. Collection-scope values act as shared defaults; token-scope values override them.
115
+
116
+ Use reserved fields such as description, attributes, creator, license, display notes, and links
117
+ according to command help. Verify the resulting provenance in decoded metadata. On-chain-wins means a
118
+ new on-chain value can intentionally supersede a resolver projection.
119
+
120
+ ### Gateways
121
+
122
+ `abx set-gateway` changes the serving prefix for IPFS/Arweave locator representations without moving
123
+ the content. Verify a representative locator through the new gateway before changing it. A gateway
124
+ repoint is not a re-upload and does not change the committed CID/transaction id.
125
+
126
+ ### Attachments versus parameters
127
+
128
+ `abx attach <addr> <key> <locator>` records a named artifact. It prints the CANONICAL fetch URL for
129
+ every key (`{base}/{chainId}/{address}[/<id>]/data/<key>`) directly — never hand-build that route.
130
+ It also distinguishes the on-chain field write from off-chain SERVING: an on-chain write can succeed
131
+ while nothing can serve it. With no resolver base baked in at all, it warns there is no serving path
132
+ whatsoever. With one baked in, it probes whether a resolver actually answers for this token right
133
+ now (before the write, since the key itself doesn't exist yet) and warns if not — "the on-chain
134
+ document carries reserved fields only" is a real gap creators hit; the write landing is not proof
135
+ anything can serve it. Bare on-chain metadata cannot enumerate arbitrary field keys, so consumers
136
+ need a resolver to discover all attachments even when each locator is durable and hash-anchored.
137
+
138
+ PostParams are different: the parameter store enumerates schemas and values on-chain. Read schemas
139
+ with `state` and values with `tokens --json`. Parameters do not require a resolver merely to be
140
+ canonical or enumerable.
141
+
142
+ ### Replacing an unlocked script
143
+
144
+ `abx replace-script <addr> --script <file>` ships a fix to a code project's on-chain program any
145
+ time before `abx lock-script`. It refuses outright — never warns and proceeds — on a locked script
146
+ or a target that isn't SeriesCode/EditionCode. It diffs by content against what's on-chain (an index
147
+ that already matches is never re-sent), folds every write and remove into ONE atomic transaction (so
148
+ a revert can never leave a half-applied script), and reads the completed script back to verify exact
149
+ reassembly before reporting success. Prefer this over hand-encoding `setScriptChunk` calls.
150
+
151
+ ### Refresh and URI events
152
+
153
+ `abx refresh` asks external marketplaces to fetch metadata again; it does not fix the metadata or
154
+ mutate canonical state. First prove that the current URI resolves and returns the intended document.
155
+ For editions, `ping-uri` re-emits native URI events for selected ids after a repoint. Use the smallest
156
+ id set needed and verify the new path before signalling indexers.
157
+
158
+ ## Manage economics and supply
159
+
160
+ Use `set-royalty` to change receiver/rate within the collection's permanent royalty ceiling. Use
161
+ `set-royalty-cap` only to lower that ceiling. State the old and new values and confirm the irreversible
162
+ loss of future headroom.
163
+
164
+ Series supply caps and edition per-id caps are monotonic downward. Read current minted supply before
165
+ lowering a cap; never propose a value below already minted supply. For editions, name the id and
166
+ distinguish its per-id cap from aggregate collection supply.
167
+
168
+ Pause/unpause controls the supported mint gate, not transfers or every external minter behavior.
169
+ Read state after changing it.
170
+
171
+ Primary payee declares sale proceeds; royalty receiver handles secondary royalty reporting. Keep them
172
+ separate. Changing one does not update the other.
173
+
174
+ ## Lock precisely
175
+
176
+ Lock last, after production-path verification. Every lock freezes a different surface:
177
+
178
+ | Lock | Freezes | Does not necessarily freeze |
179
+ |---|---|---|
180
+ | `lock-field` | one token/collection field representation | other fields, URI pointer, program, params |
181
+ | `lock-uri` | URI configuration/pointer at selected scope | program/dependencies/params/hooks |
182
+ | `lock-script` | code-project script chunks | dependencies, metadata, params, external renderer code |
183
+ | `lock-dependencies` | dependency list and registry pointer | bytes returned later by a live registry entry |
184
+ | `lock-param-hooks` | configure/transfer/augment addresses | behavior behind an upgradeable address |
185
+ | governed schema/value lock | writes governed by that schema/key | unrelated or ungoverned params; some inherited defaults |
186
+ | ownership transfer | future owner-only authority at this contract | powers held by external minters/hooks/providers |
187
+
188
+ For a code project, metadata locks alone do not freeze the program. A strong freeze normally requires
189
+ script, dependency, relevant field/URI, hook, and governed-parameter decisions, plus immutable external
190
+ renderer/hook/dependency deployments.
191
+
192
+ Important qualifications:
193
+
194
+ - An ungoverned parameter remains writable despite metadata locks.
195
+ - A schema-welded token value is stronger than a collection default inherited by many tokens; the
196
+ recovery path may still allow a poisoned collection default to be cleared.
197
+ - A registry dependency may return different bytes behind a frozen reference.
198
+ - A locked pointer to an upgradeable proxy fixes the address, not its behavior.
199
+ - A transfer-hook address represents a standing ability to veto mints/transfers. Freezing an empty
200
+ hook set is the proof that this power cannot later be added.
201
+
202
+ Use `abx verify` and direct state reads to enumerate what remains mutable. Describe the guarantee as
203
+ specific stored values and addresses that can no longer change; do not promise immutable output unless
204
+ every live input and external implementation has actually been bounded.
205
+
206
+ `abx state <address> --json` reports every lock above in one `locks` object — token/contract URI,
207
+ script, dependencies, param hooks, and the standard `METADATA_FIELD` set — each as `true` (frozen),
208
+ `false` (open), or `null` (unread; never report an unread lock as off). A project's own custom field
209
+ keys are real and independently lockable but are not enumerable from a bare head read; say so rather
210
+ than implying the field scan is exhaustive. Every lock is independent: never infer one lock's state
211
+ from another's.
212
+
213
+ `abx verify --json` separates two verdicts that must not be conflated: `ok`/`contentIntegrity` is
214
+ content-integrity ONLY (a hash mismatch, or none to check) and is the only field the exit code
215
+ reflects; `availability` is a sibling verdict for render/serve readiness (`available` / `partial` /
216
+ `unavailable` / `unknown`) that a missing render or an un-refetched `ipfs`/`arweave`/`url` locator
217
+ moves, while `ok` stays unaffected by design. Report both, never collapse one into the other.
218
+ `abx verify` also flags whether the project's stored `tokenURIRenderer`/generator pointers are the
219
+ CURRENT canonical singletons — a `false` here means an older-but-working deployment (or a fully
220
+ custom one), never "broken".