@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
package/dist/upload.d.ts DELETED
@@ -1,28 +0,0 @@
1
- /**
2
- * `abx storage upload` core — upload ONE file and return a public locator that PRESERVES the
3
- * filename, so the declared mimeType survives when the locator is later attached (`abx attach`).
4
- * The on-chain metadata field has no MIME slot: the URL extension IS the type declaration, so a
5
- * bare content-addressed locator (`ar://<txid>`, `<gw>/ipfs/<cid>`) — which has no extension —
6
- * would make the attached artifact `application/octet-stream`. Extracted from main.ts so the
7
- * capability-based branch (the exact bug this guards) is unit-testable with fake backends.
8
- */
9
- import type { StorageBackend, StoredContent } from '@artblocks/abx-storage';
10
- export interface UploadResult {
11
- /** A fetchable public URL for the file. */
12
- locator: string;
13
- /** True when `locator` carries the filename (extension → correct declared type on attach). */
14
- filenamePreserved: boolean;
15
- /** Set when we had to fall back to a bare (extension-less) locator — the caller warns. */
16
- fallbackReason?: string;
17
- }
18
- /**
19
- * Store `content` under `name` and return the best available public locator. Preference order:
20
- * 1. path-addressed host (cloud/S3/R2): the object key IS the filename → `<publicBase>/<name>`.
21
- * 2. content-addressed host with directory support (ipfs/arweave): wrap the single file in a
22
- * one-entry directory → `<base>/<name>` (filename preserved).
23
- * 3. bare content-addressed locator (e.g. kubo IPFS with no dir-add): `<gw>/ipfs/<cid>` — no
24
- * extension; `filenamePreserved: false` so the caller can warn about the octet-stream type.
25
- * Throws for a backend that can't produce any public URL (e.g. `fs`).
26
- */
27
- export declare function uploadAndLocate(backend: StorageBackend, name: string, content: StoredContent): Promise<UploadResult>;
28
- //# sourceMappingURL=upload.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,wBAAwB,CAAC;AAG1E,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,8FAA8F;IAC9F,iBAAiB,EAAE,OAAO,CAAC;IAC3B,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAyB1H"}
package/dist/upload.js DELETED
@@ -1,41 +0,0 @@
1
- import { hashContent } from '@artblocks/abx-storage';
2
- const trimSlash = (s) => s.replace(/\/+$/, '');
3
- /**
4
- * Store `content` under `name` and return the best available public locator. Preference order:
5
- * 1. path-addressed host (cloud/S3/R2): the object key IS the filename → `<publicBase>/<name>`.
6
- * 2. content-addressed host with directory support (ipfs/arweave): wrap the single file in a
7
- * one-entry directory → `<base>/<name>` (filename preserved).
8
- * 3. bare content-addressed locator (e.g. kubo IPFS with no dir-add): `<gw>/ipfs/<cid>` — no
9
- * extension; `filenamePreserved: false` so the caller can warn about the octet-stream type.
10
- * Throws for a backend that can't produce any public URL (e.g. `fs`).
11
- */
12
- export async function uploadAndLocate(backend, name, content) {
13
- if (backend.publicBase && backend.putObject) {
14
- await backend.putObject(name, content);
15
- return { locator: `${trimSlash(backend.publicBase)}/${name}`, filenamePreserved: true };
16
- }
17
- if (backend.putDirectory && backend.locator) {
18
- try {
19
- const { base } = await backend.putDirectory([{ name, bytes: content.bytes, contentType: content.contentType }]);
20
- return { locator: `${trimSlash(base)}/${name}`, filenamePreserved: true };
21
- }
22
- catch (e) {
23
- const hash = hashContent(content.bytes);
24
- await backend.put(hash, content);
25
- const loc = await backend.locator(hash);
26
- if (!loc)
27
- throw e;
28
- return { locator: loc, filenamePreserved: false, fallbackReason: e.message };
29
- }
30
- }
31
- if (backend.locator) {
32
- const hash = hashContent(content.bytes);
33
- await backend.put(hash, content);
34
- const loc = await backend.locator(hash);
35
- if (!loc)
36
- throw new Error(`backend '${backend.id}' stored the file but returned no durable locator`);
37
- return { locator: loc, filenamePreserved: false };
38
- }
39
- throw new Error(`backend '${backend.id}' can't produce a public URL for an attached file — use ipfs, arweave, or cloud (with a public base).`);
40
- }
41
- //# sourceMappingURL=upload.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAC;AAWnD,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAuB,EAAE,IAAY,EAAE,OAAsB;IACjG,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QAC5C,MAAM,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvC,OAAO,EAAC,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAC,CAAC;IACxF,CAAC;IACD,IAAI,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,MAAM,EAAC,IAAI,EAAC,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,EAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAC,CAAC,CAAC,CAAC;YAC5G,OAAO,EAAC,OAAO,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACxC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,CAAC,GAAG;gBAAE,MAAM,CAAC,CAAC;YAClB,OAAO,EAAC,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,cAAc,EAAG,CAAW,CAAC,OAAO,EAAC,CAAC;QACxF,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,CAAC,EAAE,mDAAmD,CAAC,CAAC;QACrG,OAAO,EAAC,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAC,CAAC;IAClD,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,CAAC,EAAE,uGAAuG,CAAC,CAAC;AACjJ,CAAC"}
@@ -1,246 +0,0 @@
1
- # Code projects — operate, resolve, render, sell
2
-
3
- [← back to SKILL.md](../SKILL.md#code-projects-generative--code-based-drops)
4
-
5
- The [SKILL Code projects](../SKILL.md#code-projects-generative--code-based-drops) section is the decision tree — inspect, pick a lane. This file is the operating depth: what to keep running, how to verify it resolves, render ops, and the lane internals. A **program is the content** (`abx deploy-code` → a `SeriesCode`): its output is a function of live on-chain state (`tokenData`: coordinates + `seed` + PostParams), injected at view time. Everything from a [Series](../SKILL.md#series-multi-token-drops) applies (mint order, lanes, identity, supply cap, minter, pause).
6
-
7
- ## Authoring the program — the abx.js runtime contract (get this right FIRST)
8
-
9
- When a creator arrives with an *idea* and you write the program, it must read its inputs and report its outputs through **one specific contract**. Guess the shape and it deploys + renders without error but is **silently broken**: the mint seed never arrives, every token renders identically, and traits come back empty. Do **not** invent globals (`window.tokenData`, `window.tokenTraits`, a bare `tokenData`) or "read defensively across variants" — there is exactly one contract, `abx inspect` recognizes only it, and you never need to read abx source to learn it (it's here):
10
-
11
- - **Read state via `abx.tokenData`** — a **flat** object. The runtime companion `abx.js` resolves it (from the injected global → URL param → RPC); template mode (`--script`) inlines `abx.js` for you, directory mode (`--code-dir`) ships its own `abx.js` copy in the build. Then:
12
- - `abx.tokenData.seed` — the mint-time seed (hex string; seed your PRNG from it — this is what makes each token unique).
13
- - `abx.tokenData.tokenId` / `.chainId` / `.contractAddress` — reserved coordinates.
14
- - `abx.tokenData.<key>` — each PostParam you declared with `--schema`, **flat** and decoded to its canonical string (e.g. `abx.tokenData.palette`). **Not** nested under `.params`.
15
- - Canonical guard: `var td = (window.abx && abx.tokenData) || {};` then `td.seed`, `td.palette`. (Reading the raw injected global `window.abxTokenData` works without abx.js, but prefer `abx.tokenData`.)
16
- - **Report traits with `abx.traits({ Key: value, … })`** (a flat object, called during render). This is the **only** thing captured into `attributes` — computing traits internally or writing them to a global does nothing. Required for the **resolver lane too**, not just on-chain (a resolver stitches `abx.traits(...)` output into the metadata; on-chain traits *additionally* need a deployed `--attributes-renderer`). No `abx.traits()` call ⇒ **no marketplace traits on any lane** — if `abx inspect` says `no traits reported`, believe it and fix the script, don't assume the resolver derives them.
17
- - **Signal `abx.done()`** when the frame is final, so the renderer captures a stable still.
18
-
19
- **A complete minimal sketch (vanilla JS, no deps) — copy this shape:**
20
-
21
- ```js
22
- // Reads state via abx.tokenData; reports traits via abx.traits(); signals abx.done().
23
- (function () {
24
- var td = (window.abx && abx.tokenData) || {}; // the flat token-data object
25
- var seed = td.seed || '0x1'; // mint-time seed (hex)
26
- var palette = td.palette || '#3355ff'; // a PostParam: --schema palette:HexColor:TokenOwner
27
-
28
- var z = 0; // seed a PRNG deterministically (same seed → same art)
29
- for (var i = 2; i < seed.length; i++) z = (z * 16 + (parseInt(seed[i], 16) || 0)) % 4294967296;
30
- function rnd() { z = (1664525 * z + 1013904223) % 4294967296; return z / 4294967296; }
31
-
32
- var rings = 3 + Math.floor(rnd() * 6); // a seed-derived value → drawn AND reported as a trait
33
- var c = document.createElement('canvas'); c.width = c.height = 1000; document.body.appendChild(c);
34
- var g = c.getContext('2d'); g.strokeStyle = palette; g.lineWidth = 6;
35
- for (var r = 0; r < rings; r++) { g.beginPath(); g.arc(500, 500, 55 * (r + 1), 0, 6.283); g.stroke(); }
36
-
37
- if (window.abx) {
38
- abx.traits({ Rings: rings, Palette: palette === '#3355ff' ? 'Default' : 'Custom' }); // ONLY these reach `attributes`
39
- abx.done(); // frame is final → capture the still
40
- }
41
- })();
42
- ```
43
-
44
- (A p5.js variant: `var td = (window.abx && abx.tokenData) || {}; randomSeed(seedInt(td.seed)); … abx.traits({…}); abx.done();` inside `draw()`. Declare `p5` with `--dep p5@1.0.0` — on-chain bytes exist on Sepolia only.)
45
-
46
- **`abx inspect <script>` is your author-time check** — iterate the script against it before picking a lane: its **PostParams** list must show every collector key you intend (if it says "none detected" but you meant `palette` to be collector-set, you're reading it the wrong way), and its **Traits** line must not say "no traits reported" if you want filterable traits. (A Solidity in-chain renderer is a *different* contract — see [In-chain Solidity SVG](#in-chain-solidity-svg--the-zero-dependency-lane); the `abx.js` contract above is for a JS `--script`/`--code-dir` program.)
47
-
48
- ## Studio loop — iterate on the art before you deploy anything
49
-
50
- [← Phase 0 in SKILL.md](../SKILL.md#phase-0--make-the-work-first-skip-every-gate-below-until-its-good). When the creator is still designing, your job is to make the work **visible, interactive, and fast to change**. One command does it:
51
-
52
- ```bash
53
- abx preview --script art.js --schema "palette:HexColor:TokenOwner" # → http://localhost:8788
54
- ```
55
-
56
- **Give the creator the URL and let them drive.** This is the one place in the toolkit where handing over a link is right — the art is theirs to judge, and a browser they control is the only honest way to judge it. The studio gives them a seed shuffle, real inputs for every PostParam they declared, a live traits readout, and `/grid` for N seeds at once. `/view` is the bare document.
57
-
58
- **Why a server and not a screenshot sweep:** a still flattens every time-based piece. Plenty of generative work animates, and `abx.done()` exists *because* stills need a settle point — so a proof sheet of an animated piece is a set of arbitrary frozen frames presented as the work. The server also makes PostParams tangible (a color picker that re-renders beats any explanation of governed params), and it costs no Chromium download.
59
-
60
- **It serves the same document the generator serves** — the real `abx.js`, the real canonical tokenData shape, the real dependency tags — with a synthetic seed in place of a minted one. So what they approve is what deploys. (This is why you should not hand-roll a preview page: a stub you write yourself defines its own `abx` surface, and will happily run a sketch that reads its seed the wrong way.)
61
-
62
- **The program is re-read from disk on every render**, so the loop is: edit `art.js` → tell them to refresh → take feedback → edit again. No restart, no watcher, no rebuild.
63
-
64
- **When you need to see it yourself** — you have no browser, and "how does it look?" every round is a bad experience for them:
65
-
66
- ```bash
67
- abx preview --script art.js --shoot ./frames --count 9 # PNGs + traits.json, then exits
68
- ```
69
-
70
- Same server, same document, headless. Needs Playwright + Chromium (`npm i -D playwright && npx playwright install chromium`); the interactive lane needs neither. **Read the PNGs** — don't report on art you haven't looked at. `--shoot` also flags the two silent killers for you: no frame reporting traits (⇒ no marketplace `attributes` on any lane), and identical traits across every seed (⇒ the sketch isn't reading `abx.tokenData.seed`, so the drop mints N identical tokens).
71
-
72
- Use both: `--shoot` to check your own work between rounds, the live URL as what the creator actually looks at.
73
-
74
- **What preview is NOT.** It injects the token data itself, so it will run a sketch that reads its seed the wrong way — and a piece that only ever renders one seed correctly still looks fine here. Neither check that follows is optional:
75
-
76
- ```bash
77
- abx inspect art.js # the wiring check: are traits + PostParams actually read/reported?
78
- abx deploy-code --script art.js --onchain-uri --dry-run # the lane + surfaces check
79
- ```
80
-
81
- And a **testnet deploy remains the faithful end-to-end** (the real generator, the real assembled document, the real seed from the chain). Both come after the art is settled.
82
-
83
- ## What a code project requires you to run — and keep running (say this up front)
84
-
85
- A code project's art depends on live on-chain state (the per-token `seed`, mutable PostParams), and *something* must read that state and inject it at view time. That something is a **resolver you run** — **unless** you take the fully-on-chain lanes (`--onchain-uri` for the tokenURI+animation, `--image-base` for a deterministic off-chain thumbnail, `--attributes-renderer` for on-chain traits), which can eliminate the metadata resolver entirely. When a resolver *is* in play, it's three pieces of ongoing infrastructure — lay them out plainly before they commit:
86
-
87
- 1. **A hosted resolver (required unless the deploy takes the `--onchain-uri` lane).** Serves `tokenURI` (`/t`) and the **live view** (`/a`, where it injects the seed + current PostParams). Its **public URL is baked on-chain at deploy**, so a domain/host must be ready first — `deploy-code` **refuses a localhost/missing URL**. Stand it up with `abx deploy-resolver` ([hosting.md](hosting.md)); keep it up or the token stops resolving. **The resolver is also the protocol's one chain-watcher**: `abx serve` runs an incremental getLogs poll (~12s, `ABX_WATCH_INTERVAL_MS`, 0=off) over every registered project, so **any** on-chain change — an external mint through a minter, a param change from a foreign tool or another wallet — auto-indexes and fans out ONE coarse notification per changed project to the effects layer (`ABX_EFFECTS_URL` + `ABX_EFFECTS_TOKEN`). Effects never watch the chain themselves. (No reorg lookback — post-PoS reorgs are rare; the repair is the deterministic `abx index <addr> --full`.)
88
- 2. **An effect runner + a storage home — decide the thumbnail mode AND where renders live, up front.** A code project's marketplace still is **rendered off-chain** (there's no image file to point at); on-chain-stitched `attributes` come from the same render. Two coupled decisions to make *before* deploying: **(i) the mode** — **(a) continuous runner** that auto-renders every new mint + param change (the default for a live / for-sale drop): `abx deploy-effects --resolver-url <resolver>` HOSTS it (fly/docker) beside a hosted resolver, or `abx effects` runs it **LOCALLY, in-process** (co-located with `abx serve` — great for testing/iteration; blocks, so background it), **(b) one-shot** (`abx render <addr> --remote <resolver>` after mint, for a fixed supply — re-run for later mints / param changes), or **(c) none** (the live view still animates, but the marketplace thumbnail stays a placeholder SVG); and **(ii) the storage home** (`ABX_STORAGE_BACKEND`) — **ipfs / arweave** (durable; the runner publishes a locator the resolver redirects to) or **s3**, **NOT** the default `fs` for a hosted resolver (a laptop-local store a hosted node can't read → the placeholder never clears). The runner uploads each render to that home and **publishes** it to the resolver (the locator bridge). Verify with `abx verify <addr>` after minting token 0 (below).
89
- 3. **Pinned IPFS/Arweave behind an HTML-serving gateway (directory mode).** The build is pinned and the live view 302s to the gateway, so the gateway **must serve HTML** — the shared Pinata *public* gateway does **not** (`ERR_ID:00023`); use a **dedicated** gateway or Arweave.
90
-
91
- ### Already deployed — just operate it (the resume loop)
92
-
93
- A creator who ALREADY has a code contract (deployed here or elsewhere) doesn't re-run `deploy-code`:
94
- ```bash
95
- abx add <addr> # index it locally (auto-discovers the deploy block; --from-block to override)
96
- abx effects # START THE RUNNER FIRST — in-process, blocks, so background it (& or a second shell)
97
- ABX_EFFECTS_URL=http://localhost:<effectsPort> abx serve # resolver + chain watcher; wire it to the runner
98
- abx verify <addr> --remote http://localhost:<port> # confirm: renders up-to-date AND the watcher is live
99
- ```
100
- - **Order matters.** Start `abx effects` (or set `ABX_EFFECTS_URL` to a running runner) **before** relying on auto-render or `abx render`. A resolver whose `ABX_EFFECTS_URL` is unset still watches + indexes, but has **nowhere to send the "changed" notification → thumbnails never auto-update** (`abx serve` prints a ⚠ when it's unset). And `abx render` pointed at a dead effects URL errors instead of rendering — start the runner, or drop the URL to render inline.
101
- - **`--remote <url>` means "any resolver's HTTP API" — including your OWN local `abx serve`**, not just a cloud host. `abx verify <addr> --remote http://localhost:<port>` is the truthful check even on your laptop (plain `abx verify` only sees this machine's store, so a published render reads as a false placeholder).
102
- - **Proof the watcher is alive:** `abx serve` logs `[watch] alive — watching sepolia @ block …` on quiet stretches (~every 2 min) and `[watch] … changed → notified` on every on-chain delta. For a HOSTED node you can't tail, `GET /api/watch` (and the `watching …` line in `abx verify --remote`) reports the last poll + head — a stale `pollAt` means the watcher stopped.
103
-
104
- ### Live data (the augment hook) — the hook IS the setting
105
-
106
- Two kinds of inputs feed a piece: **settled state** (explicit PostParams, the seed — event-derived, indexed) and optional **live data** (an on-chain augment hook read fresh per view: block data, an oracle, anything a `view` returns). No hook — the overwhelming default — means the resolver makes **zero** live reads and serves pure indexed params: nothing to configure, maximum efficiency. With a hook set, the **live view** reads it per view (always current), while the **still is a snapshot of settled state only** — live data never re-addresses the render, so a volatile hook (a timestamp) animates the live view without re-rendering the thumbnail every block. Re-render triggers are settled-state changes only. `abx verify` prints the project's live-data posture; `ABX_DISABLE_AUGMENT=1` is a resolver-operator kill-switch (degrades to settled params).
107
-
108
- **Wiring the hooks — `abx set-param-hooks <addr>` (SeriesCode only, owner-only).** A SeriesCode project has three optional param-lifecycle hook addresses, each a contract the creator deploys: **`--augment`** (the live-data hook above — read-time derivation folded into tokenData), **`--configure`** (a write-time veto/validator — a governed `configure-param` reverts if this hook reverts), and **`--transfer`** (an ownership-change lifecycle call, best-effort — a revert there never blocks the transfer). The contract has **no per-hook setter** — it writes all three at once — so the command reads the current trio and re-sends it with your change applied: **omit a role to keep it**, pass an address to set it, `none` to clear it (`--clear` clears all three). Run it bare to print the current hooks. Any signing lane; guards `--dry-run`. A 1/1 or plain Series has no configurable params, so it has no hooks (the command refuses it).
109
-
110
- ## `--onchain-uri` — the chain-complete lane (internals)
111
-
112
- `deploy-code --onchain-uri` makes `tokenURI` resolve **on-chain** via the canonical metadata renderer, with `animation_url` **computed on-chain** by the canonical **`AbxGenerator`** (a `renderer`-representation collection field). No `--public-base-url`, no resolver base baked.
113
-
114
- **When to choose it (owner guidance from real-world experience): NOT the default for a generative drop meant to sell — lean off-chain resolver there.** A resolver keeps you maneuverable (metadata/serving can evolve without on-chain re-points) and lets marketplaces fetch a **small** `tokenURI`; a fully-on-chain code `tokenURI` carries the whole ~200KB+ document per call, and large-`tokenURI` marketplace/indexer compatibility is a real-world risk that grows with the art. Reach for `--onchain-uri` **deliberately** when maximal durability / "resolves from any RPC forever" / zero always-on infra outweighs those — a legitimate, proven lane, just not the marketplace default.
115
-
116
- **What "fully on-chain" (chain-complete) means, and the one silent trap:** the `tokenURI` **and** its `animation_url` document come back entirely from on-chain bytes — no server, gateway, or CDN in the graph. It does **NOT** include the marketplace thumbnail (`image`) — that is *always* rendered off-chain. ⚠ **The silent breaker:** a `--dep` that resolves to a CDN instead of on-chain bytes **deploys fine and renders fine**, but you are no longer fully on-chain — a URL is back in the graph, with no error. The deploy's dependency report ("ON-CHAIN bytes available" vs "served from CDN") and `abx verify` (`chain-complete: yes/no`) both call this out **before and after** you spend — read them.
117
-
118
- - **Template mode (`--script`) can be CHAIN-COMPLETE** — the generator assembles the full HTML document (`data:text/html;base64`) from the on-chain chunks — **iff every `--dep` resolves to proven on-chain bytes** on the registry (`p5@1.0.0` qualifies on Sepolia). A CDN-served dep still *serves fine* but breaks chain-completeness. Zero-dep vanilla JS is trivially chain-complete.
119
- - **Directory mode (`--code-dir`) is no-server, not chain-complete**: the generator emits `{gateway}/{code root}/index.html?abx=<tokenData>` — liveness rides the gateway (default `ipfs.io`/`arweave.net`; repoint with `abx configure-param <addr> - display.gateway <prefix>`), permanence rides the pin/endowment, params ride the URL (**8KB budget** — `abx verify` reports `urlOverBudget`; big params ⇒ prefer template mode).
120
- - **`params.keys` is auto-managed**: the deploy writes this contract param (schema keys ∪ itself, sorted CSV) so the generator's on-chain `tokenData` carries the full param surface, byte-aligned with the resolver's. Add a param key another way and `abx configure-param` prints the exact fix when the CSV drifts.
121
- - **Verify it**: `abx verify <addr>` eth_calls the generator's `onChainStatus` (branch — template/directory · chain-complete · unresolved refs · URL budget) AND decodes `tokenURI` straight from the contract, reporting the `animation_url` form. `abx tokenuri <addr>` is the quick raw read.
122
-
123
- ### PostParam schema — the Type + Auth catalog
124
-
125
- A schema is `key:Type:Auth` (repeat comma-separated: `--schema palette:HexColor:TokenOwner,speed:Uint256Range[0..100]:Artist`). The Type token carries an optional **bracket suffix**: a **`Select` MUST list its options** (`Select[Spring|Summer|Autumn|Winter]`, pipe-delimited — a Select with no options is rejected, because the on-chain schema requires them), and a **Range MAY carry bounds** (`Uint256Range[0..100]`, `Int256Range[-50..50]`, `DecimalRange[0..1]`, `Timestamp[2026-01-01..2026-12-31]` — omit for unbounded). The delimiters never collide (params `,` · fields `:` · options `|` · bounds `..`, all inside `[…]`). Declared at **deploy** (`--schema`); set later per token with `abx configure-param <addr> <id> <key> <value>` (value canonically encoded per Type — you pass the human form; a Select takes a label or its index). Adding a schema to an already-deployed contract isn't a CLI command today; declare params at deploy.
126
-
127
- - **Types:** `Bool` (`true`/`false`) · `Select[A|B|C]` (**options required in brackets**; set by a label from the list, or its index) · `Uint256Range[min..max]` (non-negative integer; bounds optional) · `Int256Range[min..max]` (signed integer) · `DecimalRange[min..max]` (decimal, ≤10 places) · `HexColor` (`#rrggbb`) · `Timestamp[min..max]` (Unix seconds **or** an ISO date like `2026-07-16`) · `String` · `Bytes` (`--file <path>` for the payload).
128
- - **Auth — who may set the param:** `Artist` (the contract owner) · `TokenOwner` (the token's current holder; **delegate.xyz honored**) · `Address` (a specific named address) · and the `Or` combinations `ArtistOrTokenOwner` · `ArtistOrAddress` · `TokenOwnerOrAddress` · `ArtistOrTokenOwnerOrAddress`. The chain enforces it — a wrong signer reverts. (There is **no** "anyone" leg — a param is always artist / token-owner / a named address.)
129
- - Examples: a collector-tunable color → `palette:HexColor:TokenOwner`; a collector-chosen mood → `mood:Select[Calm|Wild|Chaotic]:TokenOwner`; an artist-only bounded dial → `speed:Uint256Range[1..10]:Artist`; an on/off toggle → `invert:Bool:TokenOwner`.
130
-
131
- ## `--image-base` — deterministic S3/CDN thumbnail URLs (no metadata resolver)
132
-
133
- `deploy-code --image-base https://cdn.you/orbit` bakes the on-chain `image` as a **`url-template`** (`https://cdn.you/orbit/{id}.png`) — a stable per-token URL the chain names — and the effect runner writes each token's still to that exact object (overwrite in place) when you render it. Marketplaces read the on-chain `tokenURI` → the image URL → the bytes the runner PUT; **no resolver serves the image.**
134
-
135
- - **⚠ Thumbnail freshness — the honest tradeoff.** With **no resolver there is no chain-watcher**, so on this lane the still is **backfill / manual**: run `abx render <addr> [ids]` after minting, and **re-run it after any PostParam change**. A param change updates the on-chain **animation instantly** (it reads the param live), but the **S3 still stays stale until you re-render**. It's a **pick-one at the baseline: on-chain-URI durability with *manual* thumbnails, OR an off-chain resolver with *continuous/live* thumbnails** (the resolver is the watcher). Don't promise "fully on-chain AND auto-updating thumbnails."
136
- - **Use a mutable, path-addressed host** — S3 / R2 / a CDN (`ABX_STORAGE_BACKEND=cloud`). **NOT ipfs/arweave** for this lane: content-addressed → the URL changes with the bytes → you'd rewrite chain state on every re-render. The whole point is a URL that never changes while the pixels do.
137
- - **Provisioning — the EXACT vars (don't guess these; a real agent invented `R2_*` names abx never reads and broke the upload):** the runner uploads via `ABX_S3_ENDPOINT` · `ABX_S3_BUCKET` · `ABX_S3_ACCESS_KEY_ID` · `ABX_S3_SECRET_ACCESS_KEY` (region `ABX_S3_REGION`, default `auto`), and serves the still from `ABX_S3_PUBLIC_BASE` — **which must equal your `--image-base`.** `abx storage show` prints what's resolved. **The `--image-base`/`ABX_S3_PUBLIC_BASE` is the PUBLIC READ url, NOT the S3 API endpoint** — the two are different hosts. **R2 gotcha:** `https://<acct>.r2.cloudflarestorage.com` is the *API endpoint* (auth-only, goes in `ABX_S3_ENDPOINT`); the public URL is a **`https://pub-<hash>.r2.dev`** you enable in the R2 dashboard, or a custom domain. (AWS S3: a public-read bucket or a CloudFront domain — the bare `s3….amazonaws.com` host is public only if you made the bucket public.)
138
- - **How a render writes:** the runner keys the still by the URL the on-chain template names (token N → `{key}/N.png`), plus a `…N.png.abxhash` sidecar for idempotency (skip when unchanged; overwrite when the inputsHash advances). It needs a live view to screenshot — a local `abx serve` render aid (run it while rendering, kill it after; never baked on-chain) or a resolver.
139
- - **Pairs with `--onchain-uri`** for the "no metadata server" drop: tokenURI + animation on-chain, thumbnail at a deterministic S3 URL on-chain, and — if the traits port — `--attributes-renderer` for on-chain traits.
140
-
141
- ## In-chain Solidity SVG — the zero-dependency lane
142
-
143
- The purest form: the artwork itself is a **Solidity `IAbxFieldRenderer`** that returns an SVG from the token's `seed` + params — no JS program, no browser, no bucket, no resolver, no effect runner. Image AND traits are computed on-chain and the `tokenURI` is assembled on-chain, so the token depends on **nothing outside the EVM**.
144
-
145
- ```bash
146
- abx deploy-code --image-renderer 0x<svgRenderer> [--attributes-renderer 0x<traitsRenderer>] \
147
- --onchain-uri --schema palette:HexColor:TokenOwner --name "…" --symbol … [--dep none]
148
- ```
149
-
150
- - **No `--script`/`--code-dir`.** `deploy-code` allows a renderer-only project (neither program mode) as long as `--image-renderer` (and/or `--attributes-renderer`) is set. There is no `animation_url` — the SVG `image` *is* the artwork; the metadata renderer omits an unset animation field, and the CLI does **not** wire the generator (wiring an animation leg at a zero generator would revert every `tokenURI`).
151
- - **On-chain `tokenURI` is the CLEAR default here** — unlike the JS/p5 lanes. A Solidity SVG reads *small* (a few hundred bytes–few KB), so the large-`tokenURI` marketplace-read caveat does not apply. Recommend it enthusiastically; there is no maneuverability/infra tradeoff to weigh because there is no infra.
152
- - **The renderer is a contract the creator deploys separately** (the CLI doesn't compile Solidity). **`abx scaffold-renderer <dir>`** writes a ready-to-build Foundry project — a worked `MyRenderer.sol` (image → `image/svg+xml` from `seed` + a `palette` PostParam), a coherent `MyTraits.sol` reading the SAME seed math, the `IAbxFieldRenderer`/`IAbxParams` interfaces (with the invariants documented), a `forge test` proving `render()` never reverts (incl. the collection surface + a fuzz), a `Deploy.s.sol`, and a README. The creator forks the art, then `forge soldeer install && forge test`, deploys with forge, and passes the address to `deploy-code --image-renderer 0x…`, which **verifies the address has code** (real deploy refuses a codeless address; dry-run probes best-effort) — same guard as `--attributes-renderer`. A renderer reads the token's seed/params directly (`IAbxParams(token).tokenParam(tokenId, "seed"|"palette")`), computes bytes, and returns `(contentType, data)`. Interface + invariants also at https://abx.docs.artblocks.io/protocol/renderers/.
153
- - **PostParams still apply — and you MUST declare them; the CLI can't.** Unlike the JS lane (where `abx inspect` statically detects the params a script reads), a Solidity renderer is opaque to the CLI — it cannot know your renderer reads a `palette`. **Read the renderer, and declare every PostParam it reads with `--schema key:Type:Auth`** (the example reads `palette` → `--schema palette:HexColor:TokenOwner`). Skip it and the param is **fixed at the renderer's default forever** — collectors can't set it (the exact miss from a real session: a palette-tinted renderer shipped with `schemas []`, stuck on the default). With the schema declared, a collector's `configure-param` re-addresses the on-chain image automatically (the renderer reads the live param — no re-render, there's no off-chain still). `deploy-code --dry-run` nudges when renderers are set with no `--schema`.
154
- - **`abx render`/`abx effects` are irrelevant** (there's no off-chain still to produce), and `isCodeProject` is false for a renderer-only project — both are expected, not errors.
155
- - **Verify:** a raw `abx tokenuri <addr>` (or `cast call tokenURI(0)`) decodes to `name` + `image` = `data:image/svg+xml;base64,…` + on-chain `attributes`, with **zero `http(s)` URLs** — the from-chain proof it's fully in-chain.
156
-
157
- ### Authoring / reviewing an `IAbxFieldRenderer` — the invariants to CHECK before you wire it
158
-
159
- The renderer is the creator's own Solidity (compiled + deployed with forge — the CLI doesn't run Solidity). `deploy-code` confirms the address **has code**, but it does **not** — and cannot cheaply — prove the renderer *behaves*. So an agent helping author or ship a renderer must **review the `render` function against these invariants**, and the creator should forge-test them:
160
-
161
- - **NEVER revert — for ANY token or param state.** This is the one that bricks a drop: the canonical `AbxMetadataRenderer` staticcalls `render()` with **no `try/catch`**, so *any* revert reverts the entire `tokenURI` (and `contractURI`). The function must return cleanly for: a token with **no seed**, **unset params** (no palette), and the **collection surface** (`tokenId == type(uint256).max`, per `IAbxFieldRenderer` — no token) → return a neutral value there (`[]` for attributes, a plain card for an image), never revert.
162
- - **Correct content-type + shape.** `image` → `image/svg+xml` (or another image MIME) returning a valid document; `attributes` → `application/json` whose bytes are a JSON **array** `[{"trait_type":…,"value":…},…]` (numbers unquoted, strings escaped). A wrong type or malformed array is a broken/blank marketplace field.
163
- - **Guard the field, wire the right one.** Revert `UnsupportedField` for a field it doesn't serve (a wiring mistake fails loud), and make sure `--image-renderer`/`--attributes-renderer` point at the renderer that actually serves that field.
164
- - **`view` + deterministic.** Same chain state → same bytes. No unseeded randomness; read block/oracle state only if you *intend* live data (it re-reads per view).
165
- - **Read params LIVE via `IAbxParams(token)`** (`tokenParam`/`contractParam`, token scope wins) — so a collector's `configure-param` shows up on the next read with no redeploy. Never bake a param value in at deploy.
166
- - **Keep the output bounded.** It assembles into `tokenURI` per call; a very large SVG/HTML can strain the `eth_call` gas on unauthenticated public reads.
167
-
168
- Fork `contracts/src/renderers/examples/{SeedSvgRenderer,SeedTraitsRenderer}.sol` — they satisfy every invariant above (graceful fallbacks, the sentinel, content-types, live param reads) and are the reference to review a fork against.
169
-
170
- ## Traits on-chain vs off-chain — is your script's trait logic reproducible in Solidity?
171
-
172
- A script's `abx.traits({…})` runs in JS during the render. Whether those traits can appear in a **fully-on-chain** `tokenURI` depends on whether the trait *function* is reproducible in Solidity — `abx inspect` rates this; full analysis (with the p5 LCG port recipe) in [`docs/research/onchain-traits-feasibility.md`](../../../docs/research/onchain-traits-feasibility.md). Give a **graded** answer, never a flat "not feasible":
173
-
174
- **First, the load-bearing distinction: *portable* ≠ *deployed*.** `inspect` saying traits "port EXACTLY" means the *logic* can be reproduced in Solidity — it does **not** mean a renderer exists. On-chain traits require the creator to **author + deploy** a Solidity attributes-renderer (fork `SeedTraitsRenderer.sol`) and pass its address to `--attributes-renderer` — a real build step, not a flag with a default. `deploy-code --dry-run` prints the traits disposition (`on-chain` / `off-chain via resolver` / `⚠ OMITTED`) and a real deploy **refuses an `--attributes-renderer` address with no code**. So never present "traits on-chain" as settled off a guessed address: confirm a renderer is deployed, or the honest options are off-chain-via-resolver or omitted.
175
-
176
- - **Mechanism (not a gap):** the canonical `AbxMetadataRenderer` embeds `attributes` from an on-chain `attributes` field — `inline` JSON, or a **`renderer`** field-renderer that computes the array (see `contracts/src/renderers/examples/SeedTraitsRenderer.sol`, a forkable worked example). Wire it with `deploy-code --attributes-renderer 0x…` — **no canonical-renderer edits, no per-token writes.**
177
- - **Reproducible (exact):** seeded p5 `random()` (a documented LCG: `z=(1664525·z+1013904223) mod 2³²`) driving `floor`/threshold/`%`-select traits, or traits read straight from the seed/params. Ports to integer Solidity exactly — mirror the LCG + the *call order* + the seed hash.
178
- - **Not reproducible:** unseeded `Math.random()` (non-deterministic — nowhere), or a trait reading a raw float at a precision boundary. Then serve `attributes` off-chain via a resolver, or ship without marketplace traits (tokenURI + animation are still fully on-chain).
179
- - **Best practice:** author traits **Solidity-first** (seed/param integer functions, `SeedTraitsRenderer`-style) and mirror them in JS — on-chain and rendered traits then agree by construction, no PRNG archaeology.
180
-
181
- ## The `deploy-code` command
182
-
183
- ```bash
184
- abx deploy-code (--script <file> | --code-dir <dir>) --name "…" --symbol … \
185
- (--public-base-url https://your.resolver.domain | --onchain-uri) \
186
- [--image-base https://cdn/…] [--attributes-renderer 0x…] \
187
- [--max N] [--mint-count N | --mint-all] [--schema key:Type:Auth,…] [--no-seed] \
188
- [--dep <name@version|0x…>[,…]] [--dep-registry 0x…] \
189
- [--description "…"] [--external-url <url>] [--backend ipfs|arweave] [--dry-run] [--confirm]
190
- ```
191
- **Always `--dry-run` first**, then mirror its output into the [confirm readout](../SKILL.md#confirm-before-sending): it prints the deterministic address, resolver base, PostParam schema, mint plan, and tx count with **nothing sent and no bytes pinned**. (`--confirm` adds an interactive y/N before the real send.)
192
-
193
- - **`--script <file>`** — template mode: the program stored **on-chain in chunks** (auto-split ~22KB). Zero-dependency vanilla JS is the cleanest case; declared libraries are the Dependencies extension (`--dep`). Large PostParams inline cleanly here.
194
- - **Dependencies (template mode)** — declare libraries with `--dep <ref>` (repeatable or comma-separated; **ordered — the first ref is index 0 = the runtime**, e.g. `--dep p5@1.0.0`). A ref auto-detects: `name@version` (AB registry naming) resolves through the collection's registry pointer — `deploy-code` defaults it to the chain's **AB Dependency Registry** (`--dep-registry 0x…` overrides; a chain with none known warns + skips, never blocks) — while `0x…` declares an on-chain data contract, read directly. Registry deps are existence-checked before deploy (best-effort; a miss is a warning): **a CDN-served record is the normal production path, not a degradation** — on-chain bytes are the durability floor. Post-deploy: `abx set-dependency <addr> <index> <ref>` · `abx remove-last-dependency <addr>` · `abx set-dependency-registry <addr> <0x…|none>` · `abx lock-dependencies <addr>` (freezes list + pointer).
195
- - **`--code-dir <dir>`** — directory mode: a build folder (must contain `index.html` **and its own `abx.js` copy** — the build must read `abx.tokenData` + call `abx.traits({…})`, see [Authoring the program](#authoring-the-program--the-abxjs-runtime-contract-get-this-right-first)) uploaded via `putDirectory` (ipfs/arweave); its root becomes the on-chain `code` field. The live view 302s to the gateway with `?abx=<canonical tokenData>` — **so the gateway must serve HTML** (dedicated Pinata gateway or Arweave, never the shared public one).
196
- - **`--description "…"` / `--external-url <url>`** — collection identity, written as **on-chain collection fields in the deploy tx** (a code project has no operator-metadata table of its own, so these ride on-chain; the metadata renderer stitches them into `tokenURI` under `--onchain-uri`, a resolver reads the same fields). Set them or the metadata is bare. (Any other unsupported flag warns "unrecognized flag, ignored" — a typo can't quietly drop a value.)
197
- - **Seeds** — drawn at mint from the canonical `AbxSeedSource` (settled once assigned; `--no-seed` opts out; curated pre-set seeds win).
198
- - **`--schema key:Type:Auth`** — governed PostParams (e.g. `palette:HexColor:TokenOwner`). Reconfigure any lane with `abx configure-param <addr> <tokenId> <key> <value>` — it reads the on-chain schema and canonically encodes the input (`#rrggbb`, decimals, Select by label; `String`/`Bytes` via `--file`); delegate.xyz honored on the TokenOwner leg. A param change **re-addresses** renders (self-invalidating stills). **The re-render is automatic wherever the resolver's chain watcher is on (the default):** the watcher sees the change on its next poll (~12s) — no matter WHO sent it or WITH WHAT tool — re-indexes, and notifies the runner. `abx configure-param … --remote <resolver>` remains an **immediate nudge** (skips the poll wait) and the fallback for a watcher-disabled resolver. Expect thumbnail refresh ≈ poll cadence + render time (~15–30s), not instant.
199
- - **How it resolves (live, uncached; not on-chain):** the resolver rebuilds `tokenData` per view. Directory mode 302-redirects to the gateway with params in the **query string** (so **very large PostParams favor template mode**, which inlines them with no URL ceiling); template mode assembles the HTML inline from the chunks.
200
- - **Lanes**: all three. `--unsigned` needs `--for <signer>` and prints the whole pre-computed sequence (deterministic deploy → the clone address is known up front). On a chain with no canonical factory, all deploy commands **stop with guidance**; deploying your OWN trust anchor is an explicit `--bootstrap-factory` opt-in (private chains/sandboxes only).
201
-
202
- ## Stills + traits — the render effect
203
-
204
- The render effect is the **ONLY source of a real thumbnail** — skip it and `image` stays a placeholder. Three ways to run it: the **hosted** runner (`abx deploy-effects --resolver-url <resolver>`), a **local continuous** runner (`abx effects` — in-process beside `abx serve`, auto-renders every mint + param change), or the **one-shot repair lane** `abx render <addr> [id…]` (add `--remote <resolver>` to publish to a HOSTED resolver; idempotent; local captures need `npx playwright install chromium`).
205
-
206
- - **`abx render <addr> <id> --force`** re-renders a still that already exists — the fix for a **bad / blank / timed-out capture**. The art is deterministic (fixed seed + params), so a plain `render` idempotent-skips an existing still and `--force` re-captures the *same* pixels; it is **not** a way to change how correct art looks (to change appearance, change an input — the `palette`/etc. PostParam via `abx configure-param`, which re-addresses the render).
207
- - A render that hits an unavailable/erroring live view **fails loudly and stores nothing** (no garbage thumbnail).
208
- - The runner uploads each render to the storage home (`ABX_STORAGE_BACKEND`) and **publishes** it to the resolver — a durable `ipfs://`/`ar://` locator the resolver 302-redirects to, or bytes for small must-inline outputs (traits). A local `abx render` with the default `fs` backend does NOT reach a hosted resolver — use `--remote` (publishes) or a public backend / co-located runner.
209
- - Script-reported traits (`abx.traits({...})`) stitch into `attributes` (on-chain wins > render > operator).
210
-
211
- ## Verify it actually resolves — before you tell the creator it's live
212
-
213
- **This testnet drop IS the preview / e2e** — it's the *real* wiring (renderers, generator, on-chain assembly), so inspecting it here is how a creator gains confidence before any mainnet launch; there's no local approximation to trust. A code project has the most that can silently break. Confirm the whole chain through the **baked** URL; don't announce success off a deploy receipt alone:
214
- 1. `abx tokenuri <addr>` → shows the **resolver base**, never `localhost` (or, on `--onchain-uri`, decodes straight from the contract). **⚠ On `--onchain-uri`, `tokenURI` reverting on Etherscan's "Read Contract" is usually NOT a broken token — an unauthenticated public read has a low gas cap, and a large on-chain document assembles per call and exceeds it. The user hits this constantly. Correct diagnosis: it's a client gas cap, NOT indexing lag. It works via `abx tokenuri`, a wallet-connected read, or a high-gas RPC. Do NOT blame the RPC's index, do NOT run `abx index --full` (there is no index in the on-chain path), do NOT redeploy.**
215
- - **The rendered thumbnail is only real if the on-chain `image` had a destination at deploy** (`--image-base <bucket>` or a resolver). If you deployed `--onchain-uri` with neither, `abx render` writes to a local store the tokenURI never points at → **orphaned**; the marketplace still is the placeholder forever. There is no fix without a re-point tx (`set-field image <public url>` then re-render) — which is why it's a deploy-time decision.
216
- 2. the resolver serves `/t/<chainId>/<addr>/0` (real JSON, **not** `{"error":"unknown project"}`). If it errors, it's still backfilling or scanning from block 0 ([setup.md](setup.md)) — fix the **hosted** resolver; a local `abx serve` does **not** fix a hosted-baked token.
217
- 3. the live view `/a/<chainId>/<addr>/0` loads. **`{"error":"no live view — not a code project"}` on a project that IS a code drop is a scan-floor bug, NOT a version/compat problem** — the resolver indexed *above* the deploy block, so it missed the `code` field written at deploy (`collectionFields` comes back `[]`). Check `GET /api/project/<addr>` → `fromBlock` should equal the deploy block and `collectionFields` should contain `code`. Fix: `abx add <addr> --remote <url> --from-block <deployBlock>` (a *changed* floor forces a full replay). Do **not** conclude "the resolver doesn't support code projects" or redeploy as a static NFT. Directory mode then 302s to the gateway — the redirect must have **exactly one** gateway prefix (a doubled `https://arweave.net/https://arweave.net/…` is a stored-locator bug, fixed in-toolkit).
218
- 4. `abx verify <addr>` → per-minted-token render presence + live-data posture. **For a HOSTED drop use `abx verify <addr> --remote <resolver>`** — it reads the resolver's effect-status API and reports the real **4-state** per token: `up to date` · `rendering` · `failed` (with the actual error + attempt count — fix, then `abx render <addr> <id> --force --remote`) · `stale` (the next notify/sweep picks it up). Plain `abx verify` only checks THIS machine's store, so a render **published** to a hosted resolver reads as a false placeholder locally. A big batch drains through the runner's queue in ascending token order — `stale → rendering → up to date` is normal.
219
-
220
- **⚠ Arweave (`--backend arweave`) directory mode propagates with a DELAY — set this expectation.** Unlike IPFS/Pinata (a pin is servable almost immediately), a Turbo/Arweave upload settles over **minutes (sometimes longer)** before the gateway serves it. So right after an arweave directory deploy: the live view `/a` 302s to `arweave.net`, which **404s until it propagates**, and a render run *now* will (correctly) **fail with "content isn't servable yet" and store nothing** — expected, not a bug, and it will NOT leave a garbage 404-page thumbnail. **Re-run the render (or let the effects service sweep) once the content is live** — check with `curl -sIL https://arweave.net/<manifestTxid>/index.html` (200 = ready). IPFS directory content is renderable right after the pin.
221
-
222
- ## Mint order, timing, pause, supply
223
-
224
- **Metadata is the token id.** Tokens mint **in order** (`0,1,2,…`); token 3 shows artwork 3. No token-id ↔ metadata-id decoupling — to shuffle or sell specific tokens, pre-mint then trade.
225
-
226
- **Mint timing** — same three paths as the 1/1:
227
- | Path | Flag / command |
228
- |---|---|
229
- | **Mint all at deploy** (to yourself) | `--mint-all` |
230
- | **Mint some now, defer the rest** | `--mint-count N` |
231
- | **Deferred** *(default)* | `--no-mint`, then `abx mint <addr>` (next in order) · `--count N` · `--to <buyer>` |
232
-
233
- Pre-mint warming covers every unminted id within the cap, so `--no-mint` → `serve`/`add --remote` → `mint` keeps marketplaces from caching blanks.
234
-
235
- **Pause (safety switch).** A Series **deploys paused**: while paused only the *owner* mints (reserves/config), minter + public blocked on-chain. Configure, mint reserves, then `abx unpause <addr>` to open; `abx pause <addr>` re-closes. Deploy open with `--unpaused`. (`--mint-all`/`--mint-count` run at birth, outside the gate.)
236
- - **Minted the full supply to yourself (`--mint-all`)? The drop is COMPLETE — do not offer `unpause`.** Fixed supply is exhausted; unpausing does nothing. `abx state <addr>` prints `complete` when sold out — check it before suggesting any mint-related next step.
237
-
238
- **Delegate minting + supply.** `abx set-minter <addr> --minter 0x…` authorizes a **single** external minting contract (a new one replaces the old; `--minter none` clears to owner-only — point at a router for multiple mechanics). `abx set-primary-payee <addr> --payee 0x…` sets where sale proceeds go. `abx set-max-invocations <addr> --max N` only ever **lowers** the cap.
239
-
240
- ## Selling + a buyer page
241
-
242
- Both already documented in [operating.md](operating.md) — a code project sells exactly like a Series:
243
- - **Sell it — the shared fixed-price minter** → [operating.md § Selling](operating.md#selling--the-shared-fixed-price-minter). `abx minter configure` → `set-minter` → `set-primary-payee` → `unpause`. You run all of it.
244
- - **Give buyers a page** → [operating.md § mint-page](operating.md#a-mint-website-for-buyers--abx-mint-page). `abx mint-page <addr>` scaffolds a self-contained Next.js mint site; deploy to Vercel.
245
-
246
- Never bake a `localhost` resolver URL on-chain as a "real" deploy — it resolves for no one; the CLI refuses it. Use `--onchain-uri`/`--image-base` for no-server, or a real public resolver domain.
@@ -1,116 +0,0 @@
1
- # Operating a project (owner operations)
2
-
3
- [← back to SKILL.md](../SKILL.md)
4
-
5
- After launch the owner operates the project. Each command builds a tx, signs it via the [lane](../SKILL.md#gate-2--who-signs-three-lanes) you pick, then re-indexes so the served state updates. All are **owner-only** — the signer must be the contract owner (or, for `transfer`, the token holder).
6
-
7
- **Check state before and after any op with `abx state <addr>`** — a read-only, on-chain snapshot (no tx, no local index): owner · supply (minted / max, nextTokenId) · paused · minter · primary payee · royalty · renderer. It's the fast way to confirm an op landed (e.g. after `mint`/`unpause`), and to see who can currently mint — prefer it over piecing state together from per-token `abx tokenuri` calls. Works for a Series or a 1/1.
8
-
9
- | Command | Does | Decision to surface |
10
- |---|---|---|
11
- | `abx mint <addr> [--to 0x.. (default owner)]` | issue a token — the 1/1's token #0, or a Series' next-in-order token | who receives it (owner = pre-mint; a buyer = primary sale) |
12
- | `abx refresh <addr> [--token 0]` | ask marketplaces to (re)index the token's metadata | none — run it right after a mint or URI change |
13
- | `abx transfer <addr> --to 0x.. [--token 0]` | settle a sale/gift — move the token to a new holder | who the recipient is (price/terms are off-chain) |
14
- | `abx set-token-uri <addr> --uri <base>` | re-point the resolver **base** (e.g. move the node); contract derives `{base}/{chainId}/{address}/{tokenId}` | the new base URL |
15
- | `abx set-token-uri <addr> --override ipfs://<cid> [--token 0]` | pin ONE token to a fixed locator — the immutable `ipfs://` escape; `""` clears it | confirm — this exits the spec; pair with `lock-uri` |
16
- | `abx set-contract-uri <addr> (--uri <base> \| --override <uri>)` | re-point the collection base, or pin a fixed locator | the new base / locator |
17
- | `abx deploy-resolver --provider <fly\|render\|vps> [--domain <host>]` | scaffold a hosted read-only resolver (the default off-chain path); generates the admin token | which provider; the custom domain |
18
- | `abx deploy-effects --resolver-url <resolver>` | scaffold the render runner (Playwright) beside a hosted resolver — renders code-token stills off-chain and publishes them | the storage home (`ABX_STORAGE_BACKEND`: ipfs/arweave/s3, not local `fs`) |
19
- | `abx render <addr> [id…] [--remote <resolver>]` | render missing stills/traits now (repair lane); `--remote` publishes to a HOSTED resolver | none — idempotent; local captures need Playwright chromium |
20
- | `abx add <addr> --remote [url]` · `abx index <addr> --remote` · `abx forget <addr> --remote` | register / re-index (nudge) / deregister a contract on a HOSTED resolver (remote control plane) | needs `ABX_RESOLVER_ADMIN_TOKEN`; url defaults to `ABX_PUBLIC_BASE_URL` |
21
- | `abx migrate <addr> --from <src-url> --to <dest-url>` | move a contract's **off-chain state** to a new resolver instance (see below) | confirm the cutover step (DNS vs base URI); re-pin any source-only images |
22
- | `abx set-royalty <addr> --bps <0-10000> [--receiver 0x..]` | change the royalty (receiver defaults to current) | the rate, and whether the payee changes |
23
- | `abx attach <addr> <key> <ipfs://…\|ar://…\|https://…>` | attach a named file to a token → the `artifacts` manifest (the data plane) | the key (how it appears); locator vs `--file` on-chain |
24
- | `abx set-field <addr> --field <name> (--text "…" \| --value 0x..) [--representation <r>] [--collection]` | set an on-chain metadata field (token or collection scope) — the low-level primitive | which field, where it lives, on-chain vs off |
25
- | `abx lock-field <addr> --field <name> [--collection]` | **freeze a field forever** (all its representations) | confirm — irreversible |
26
- | `abx set-admin <addr> --to 0x..` | hand over contract ownership | confirm the new admin address |
27
-
28
- **`lock-field` / `lock-uri` are permanent — always confirm with the human first.**
29
-
30
- ### Authorship + rights (credit + license)
31
-
32
- The reserved **collection** keys are `artist`, `display_notes`, `artist_links`, `license` (a rights *identifier* like `CC0-1.0` / `CC-BY-NC-4.0`). Two ways to set them, both writing on-chain inline collection fields:
33
-
34
- - **At deploy** (all three deploy commands): `abx deploy … --artist "Casey Reas" --license "CC BY-NC 4.0"` (also `--display-notes`, `--artist-links`) — baked into the deploy tx, parity with `--description`/`--external-url`.
35
- - **Later / to change**: `abx set-field <addr> --collection --field artist --text "Casey Reas"`.
36
-
37
- They project into `contractURI` on both the resolver and the on-chain renderer; unset ⇒ omitted. Works on **any** token type (1/1 · Series · code) — reserved keys in the shared metadata store, not a code-only feature. Being *reserved*, they never become `artifacts` entries the way an arbitrary `attach` key would.
38
-
39
- ### Attaching files — the data plane
40
-
41
- A token anchors **named, typed files**; the served JSON's **`artifacts`** array is the *complete* listing, each entry `{key, mimeType, uri}`. `image`/`animation_url` are reserved members; everything else is a file you attach. ([SKILL primer](../SKILL.md#what-a-token-carries--files-beyond-the-image-the-data-plane); spec: [data-plane](../../../../specs/protocol/data-plane.md).)
42
-
43
- - **No URL yet?** `attach` takes a locator you already host. **`abx storage upload <path> [--backend ipfs|arweave|cloud]`** uploads one file and prints its `ipfs://`/`ar://` (or cloud) locator — the same backends `deploy` uses (Arweave = pay-once permanent; `fs` is refused, it has no public URL). Pipe that locator straight into `attach`.
44
- - **`abx attach <addr> <key> <uri>`** is the verb. `<key>` = any name you pick (`print`, `certificate`, `stems`, `readme`, `source`) → the manifest entry's key. It's a correct-by-construction wrapper over `set-field`:
45
- - **representation auto-detected** from the URI scheme — `ipfs://` → `ipfs`, `ar://` → `arweave`, `https://` → `url`. An unrecognized scheme is refused (never silently stored). No `--representation` to guess.
46
- - **`mimeType` is declared from the URL extension** (`…/master.tiff` → `image/tiff`, `…/coa.pdf` → `application/pdf`). No extension → `application/octet-stream` (a warning fires). **Point the URI at the file itself with its real extension** — the on-chain field has no MIME slot; the extension is the declaration.
47
- - **`--file <path>`** stores tiny bytes ON-CHAIN (SSTORE2) instead — for a small file with no external host (≈200 gas/byte, so locators are the norm for anything non-trivial).
48
- - scope: `--token <id>` (default 0) or `--collection`; any lane; `--dry-run` previews the tx.
49
- - **`artifacts` and `abx_provenance` are COMPUTED** — the resolver/renderer assembles them from your fields. `set-field`/`attach` refuse them: you never set the manifest, you attach the files it lists.
50
- - **Verify** — a resolver serves the complete listing: `curl <resolver>/t/<chainId>/<addr>/<id>` shows the `artifacts` array, and `/t/<chainId>/<addr>/<id>/data/<key>` fetches (or 302-redirects to) each file. The attached bytes are stored on-chain + keccak-anchored either way.
51
- - **On-chain vs resolver reach.** The complete listing (arbitrary attached keys) is a **resolver** surface — the EVM can't enumerate an open set of field keys, so the bare on-chain `tokenURI` (on-chain renderer) emits **reserved fields only**. Attaching is durable + anchored regardless, but to *surface* extra files to consumers today, the project is served by a resolver.
52
-
53
- ### Series-only owner ops
54
-
55
- For a multi-token Series ([SKILL → Series](../SKILL.md#series-multi-token-drops)). The mint/minter ops accept the owner **or** an authorized minter as signer; the rest are owner-only.
56
-
57
- | Command | Does | Decision to surface |
58
- |---|---|---|
59
- | `abx mint <addr>` | mint the **next** sequential token (metadata = token id) | who receives it |
60
- | `abx mint <addr> --count <n>` | mint `n` tokens **in order** (metadata = token id) in one tx | how many |
61
- | `abx set-minter <addr> --minter 0x..\|none` | set the **single** authorized minting contract (replaces any previous; `none` clears to owner-only). Multiple mechanics → point at a router downstream | the minter address |
62
- | `abx pause <addr>` · `abx unpause <addr>` | the mint safety switch: **paused ⇒ owner-only minting** (minter + public blocked); unpause to open. Owner can always mint (reserves/config) | when to open the public mint |
63
- | `abx set-primary-payee <addr> --payee 0x..\|none` | declare where primary-sale proceeds go (`none` clears) | the payout destination (a wallet or splitter) |
64
- | `abx set-max-invocations <addr> --max <N>` | **lower** the supply cap (monotonic) — e.g. close an open edition early at the minted count | confirm — can only decrease, never below what's minted |
65
-
66
- `set-field` / `lock-field` work per **token** for a Series — pass the token id (`--field image --file … ` targets that token's artwork); tokens share the collection scope with `--collection`.
67
-
68
- ### Selling — the shared fixed-price minter
69
-
70
- Instead of writing a sale contract, use the canonical **`AbxFixedPriceMinter`**: one **ownerless, multi-tenant singleton** (shared like the factory/renderer) that sells *any* Series exposing the sequential mint primitive. Sale config is keyed by token address and **all authority defers to the token owner** — so the minter holds no admin, custodies nothing, and routes proceeds to the token's own `primaryPayee()` (read fresh each sale). Design detail: [Minter spine](../../../../specs/protocol/minter-spine.md).
71
-
72
- > **The minter sells a Series, mint-on-purchase — not a 1/1 you already hold.** It mints *unminted* supply to a buyer, so it needs the Series interface (`minter`/`paused`/`primaryPayee`). A plain `abx deploy` **1/1 has none of that** — its only post-mint move is `abx transfer` (settle an off-chain sale). So there are two real paths for selling one piece: **(A)** mint the 1/1 to yourself, sell it off-platform (OpenSea/Blur), then `abx transfer --to <buyer>`; or **(B)** for a native fixed-price *primary* sale, deploy the single piece as a **1-token Series** (`abx deploy-series` on a one-file folder, `--no-mint`) and run the go-live sequence below with `--allocation 1`. Don't try to wire the minter to a plain 1/1 — `set-minter`/`unpause` will revert.
73
-
74
- | Command | Does | Notes |
75
- |---|---|---|
76
- | `abx minter configure <token> --price <eth> --allocation <n>` | set the sale (owner-only, defers to `token.owner()`); resolves-or-deploys the shared minter | `--erc20 0x..` prices in a token (else ETH); `--price-raw <units>` for exact/non-18-decimal amounts; `--allocation` caps what THIS minter may sell |
77
- | `abx minter show <token>` | read the sale + readiness (configured? assigned on token? payee set? paused? supply) | read-only — run it to check state |
78
- | `abx minter buy <token> [--to 0x..]` | buy **one** token (public; any funded signer) | ETH sales attach the price; ERC-20 needs a prior `approve` to the minter |
79
-
80
- **Go-live sequence (you run all of it):**
81
- ```bash
82
- abx minter configure <token> --price 0.05 --allocation 100 # prints the shared minter address + the remaining steps
83
- abx set-minter <token> --minter <that-address> # grant mint rights on the token (a SEPARATE grant from configure)
84
- abx set-primary-payee <token> --payee 0x… # proceeds destination — sales REVERT without one
85
- abx unpause <token> # open the sale (the token's pause is the on/off switch)
86
- ```
87
- Two independent grants — *configured* (on the minter) and *assigned* (on the token) — so "not configured" and "not assigned" are distinct, diagnosable reverts; `abx minter show` flags whichever is missing. **One mint per `purchase()`**; a buyer who wants several sends several txs. Allocation and the token's `maxInvocations` both bind (tighter wins) — keep reserves by allocating less than the supply. Other mechanics (auctions, allowlists) are *different* minters, not knobs here.
88
-
89
- ### A mint website for buyers — `abx mint-page`
90
-
91
- Once a sale is live, `abx mint-page <token>` scaffolds a **self-contained Next.js mint site** for the collection — a paginated gallery of minted tokens, connect-wallet, and a mint button — prefilled with the token, the shared minter, and the chain. It's the sale-side sibling of `abx deploy-resolver`: an artifact the creator **owns and customizes**, not a service we run.
92
-
93
- | | |
94
- |---|---|
95
- | `abx mint-page <token>` | scaffold the app into `mint-page/` (`--dir` to change; `--name`, `--rpc`, `--minter-contract` to override) |
96
- | Runs | `cd mint-page && npm install && npm run dev` (preview) → **Vercel** (`npm i -g vercel && vercel --prod`), setting the `NEXT_PUBLIC_*` vars in the dashboard |
97
-
98
- - **Backend-free** — reads sale state + each token's image straight from chain (`tokenURI(id)`); mints via the buyer's injected wallet (viem, no API keys). On-chain collections need nothing else; off-chain ones rely on their `tokenURI` resolving.
99
- - **A starting point** — plain React + one CSS file. Offer it as a release next-step, then help build a bespoke page per drop. The README covers multi-wallet (RainbowKit), ERC-20 (add an `approve` step), and the RPC/gallery notes. V1 handles **ETH** sales.
100
- - **RPC safety** — the embedded read RPC is public/keyless (it ships to the browser). Never bake a secret-keyed endpoint into a `NEXT_PUBLIC_` var.
101
-
102
- ## Moving your hosting — two cases, don't conflate
103
-
104
- - **Same resolver, new address** (moved the *node*, kept its projection store — e.g. a VPS restored from the same volume): `set-token-uri` + `set-contract-uri` to the new **base**, then `abx index`. With a custom domain, just re-point DNS — no tx.
105
- - **A fresh resolver instance** (new host, empty store): run **`abx migrate <addr> --from <old-url> --to <new-url>`** first. The new resolver replays on-chain state itself; `migrate` bridges what it can't derive — off-chain `description`/`external_url`, off-chain traits, image **content locators** — by reading the old resolver's public API (they never talk directly; provenance makes the JSON self-describing). It verifies parity and **does not cut over**.
106
-
107
- ### The cutover, after a clean `migrate`
108
-
109
- New resolver now serves byte-identical metadata, but traffic still hits the old one. Flip it one way, by how the URL was baked on-chain:
110
- - **Custom domain** (recommended — you baked `meta.you.xyz`): re-point DNS. On-chain base unchanged → **no tx, no gas**, reversible. This is *why* baking a domain you control matters.
111
- - **Provider endpoint** (you baked `your-app.fly.dev`): no DNS to flip → re-point the base URI — `abx set-token-uri <addr> --uri <new-base>` (+ `set-contract-uri`). A wallet-signed owner tx; takes a marketplace refresh to propagate.
112
- - **Keep the old resolver running** until DNS/base-URI propagates. Prefer the domain path for anything real.
113
-
114
- ### Source-only ("node-custody") images — `migrate` handles these automatically
115
-
116
- An image committed by hash but never pinned to a durable backend serves from the source's own `/image` route — no portable URL, so copying it points the new host back at the dying old one. `migrate` instead fetches the bytes, re-hashes them against the on-chain commitment (trustless), re-pins them to **your** durable backend, and bridges the new locator. The one case it can't fix: a migrating machine with no durable backend (only `fs`/`cloud`) — it bridges nothing source-bound and tells you to re-run with `--backend ipfs|arweave`. So configure a durable backend before migrating a project that used local-disk custody.
@@ -1,28 +0,0 @@
1
- # Troubleshooting — "my NFT looks wrong"
2
-
3
- [← back to SKILL.md](../SKILL.md)
4
-
5
- **Diagnose before you act — INSPECT first, don't guess.** The reads: `abx state <addr>` (on-chain owner/supply/paused/minter/renderer), `abx tokenuri <addr> [--token N]` (the actual served/on-chain JSON + `abx_provenance` per field — the fastest "is this really on-chain right now"), `abx verify <addr>` / `abx verify --remote <url>` (does the served image match the chain; is a render real vs a placeholder). Most "it's broken" reports are a stale *marketplace cache*, not a broken token.
6
-
7
- Note: `abx state` / `abx tokenuri` are **pure RPC reads** — they work on any address with no local setup. `abx verify` / `abx index` / data-plane ops need the project **registered on this node first** (`abx add <addr>`); their error says so.
8
-
9
- ### Gray placeholder / no image on the marketplace
10
- Walk the cause down:
11
- - **On-chain renderer** (`abx state` shows renderer on-chain, e.g. an in-chain SVG or `--onchain-uri`): the image *always* resolves from chain — nothing is broken. The marketplace just hasn't recrawled → `abx refresh <addr>`. Confirm with `abx tokenuri` (you'll see the real `data:`/SVG image).
12
- - **Code project, off-chain thumbnail** (rendered PNG): the still is rendered off-chain, so it needs (a) a **resolver** serving it and (b) an **effect runner** that rendered it. Check `abx verify` — "PLACEHOLDER / no render" means it was never rendered → run the runner (`abx effects` locally, or `abx deploy-effects`) or a one-shot `abx render <addr>`. If `abx tokenuri` shows the base URL is **`localhost`** or unreachable, the on-chain pointer is bad (see below).
13
- - **Orphaned image** (`--onchain-uri` deployed with **no** `--image-base` and no resolver): the on-chain `image` points nowhere a marketplace can reach and `abx render` wrote to a local store the chain never names → the still is a placeholder *permanently*. Fix = a re-point tx: host the image (a bucket / resolver) then `abx set-field <addr> --field image …` / re-deploy with a destination. This is a deploy-time decision — surface it *before* deploying.
14
- - Do **not** reach for `abx index --full` or a redeploy for a render/refresh problem.
15
-
16
- ### I changed the metadata but the marketplace shows the old value
17
- - **First confirm the change landed:** `abx tokenuri <addr> --token N` (or the resolver's JSON) — if it shows the NEW value with `abx_provenance` `onChain: true`, the write worked. The gap is 100% the marketplace's cache.
18
- - **Nudge it:** `abx refresh <addr>` — ABX emits **ERC-4906** on metadata changes so 4906-aware marketplaces self-refresh; `refresh` calls OpenSea directly with `OPENSEA_API_KEY`, else prints the link to click. Marketplace caches still lag on their own schedule — that's their side.
19
- - **Hosted resolver?** It serves from its own store — a local edit must reach it: `abx add <addr> --remote <url>` / `abx index <addr> --remote <url>` re-indexes the hosted node. Never "resubmit the transaction" or redeploy.
20
-
21
- ### `abx tokenuri` / Etherscan reverts on a fully-on-chain code project
22
- A large on-chain `tokenURI` document can exceed the **unauthenticated eth_call gas cap** some RPCs/explorers impose on a public read — the call reverts in that UI but the data is fine on a normal RPC. This is **expected for a big on-chain doc, NOT an indexing problem** — do not `abx index --full` or redeploy. Read it via a node without the cap.
23
-
24
- ### The on-chain tokenURI points at `localhost`
25
- The base URL baked on-chain is a localhost/placeholder (a dev-escape deploy, or a base set without a public host) → it resolves for no one. Re-point to a public resolver: `abx set-token-uri <addr> --uri https://<your-resolver>` (+ `set-contract-uri`), then `abx refresh`. A normal `deploy-code`/`deploy` **refuses** a localhost base — this only happens via the `ABX_DEV_ALLOW_LOCALHOST_URI` dev escape.
26
-
27
- ### `abx index`/`abx verify` says "isn't registered"
28
- Register the project on this node once: `abx add <addr>` (discovers the deploy block, indexes it). Then `index`/`verify` work. `state`/`tokenuri` never need this.