@artblocks/abx-cli 0.1.0-alpha.15 → 0.1.0-alpha.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1476 -0
- package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +17 -0
- package/assets/renderer-scaffold/test/MyRenderer.t.sol +51 -2
- package/dist/flags.d.ts +7 -0
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +19 -0
- package/dist/flags.js.map +1 -1
- package/dist/jsonout.d.ts +37 -0
- package/dist/jsonout.d.ts.map +1 -0
- package/dist/jsonout.js +68 -0
- package/dist/jsonout.js.map +1 -0
- package/dist/main.js +791 -42
- package/dist/main.js.map +1 -1
- package/dist/ownerops.d.ts +42 -1
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +238 -53
- package/dist/ownerops.js.map +1 -1
- package/dist/resume.d.ts +96 -0
- package/dist/resume.d.ts.map +1 -0
- package/dist/resume.js +95 -0
- package/dist/resume.js.map +1 -0
- package/dist/scaffold.d.ts +10 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +52 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/served.d.ts +46 -0
- package/dist/served.d.ts.map +1 -0
- package/dist/served.js +65 -0
- package/dist/served.js.map +1 -0
- package/package.json +7 -6
- package/skill/SKILL.md +7 -4
- package/skill/reference/code-projects.md +8 -6
- package/skill/reference/operating.md +2 -0
- package/skill/reference/setup.md +2 -1
- package/skill/reference/troubleshooting.md +13 -0
package/dist/served.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a URL-valued `tokenURI` **actually returns** — the served answer, not the pointer.
|
|
3
|
+
*
|
|
4
|
+
* Backlog B14. `abx tokenuri` read the chain, `abx verify` re-hashed bytes, `abx status` reported the
|
|
5
|
+
* indexing lifecycle — each a different slice, none of them the served JSON body. An agent in a cold
|
|
6
|
+
* sweep fell back to raw `curl` for exactly this, which is the tell that a command was missing.
|
|
7
|
+
*
|
|
8
|
+
* It also answers the question a *warning* couldn't. "Nothing tells you the provider you registered
|
|
9
|
+
* with isn't the base baked on-chain" is real, but the obvious check — compare the remote's base URL
|
|
10
|
+
* to the on-chain one — false-positives on the common custom-domain case (a baked `meta.artist.xyz`
|
|
11
|
+
* fronting `api.provider.xyz`), and a warning that cries wolf on the correct setup is worse than none.
|
|
12
|
+
* Fetching what the **baked** base serves makes the mismatch self-evident instead: you see the other
|
|
13
|
+
* provider's 404, or another project's document.
|
|
14
|
+
*
|
|
15
|
+
* Lives in its own module because `main.ts` exports nothing (it runs `main()` unconditionally), so
|
|
16
|
+
* anything that needs a test seam has to be extracted — the same reason `scaffold.ts` exists.
|
|
17
|
+
*/
|
|
18
|
+
export interface ServedTokenUri {
|
|
19
|
+
/** The URL followed, verbatim from the contract. `null` when the tokenURI isn't fetchable. */
|
|
20
|
+
url: string | null;
|
|
21
|
+
/** Why there was nothing to fetch, when there was nothing to fetch. */
|
|
22
|
+
skipped: string | null;
|
|
23
|
+
status: number | null;
|
|
24
|
+
contentType: string | null;
|
|
25
|
+
/** The response body as text, untruncated. `null` when the request never completed. */
|
|
26
|
+
body: string | null;
|
|
27
|
+
/** Transport-level failure (DNS, refused, timeout) — distinct from a served error status. */
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface FetchServedOptions {
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
fetchFn?: typeof fetch;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Follow a `tokenURI` and return what came back.
|
|
36
|
+
*
|
|
37
|
+
* A `data:` URI is **not** an error here — it IS the document, and a fully-on-chain project's whole
|
|
38
|
+
* point is that there is no server to ask. Saying so plainly is the honest answer; reporting it as a
|
|
39
|
+
* failure would punish the strongest configuration the protocol offers.
|
|
40
|
+
*/
|
|
41
|
+
export declare function fetchServedTokenUri(uri: string, opts?: FetchServedOptions): Promise<ServedTokenUri>;
|
|
42
|
+
/** Whether the served answer is one a marketplace could actually use. */
|
|
43
|
+
export declare function servedOk(served: ServedTokenUri): boolean;
|
|
44
|
+
/** Pretty-print a served body when it is JSON, else return it unchanged. Never throws. */
|
|
45
|
+
export declare function prettyBody(body: string | null): string;
|
|
46
|
+
//# sourceMappingURL=served.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"served.d.ts","sourceRoot":"","sources":["../src/served.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,cAAc;IAC7B,8FAA8F;IAC9F,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,uEAAuE;IACvE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uFAAuF;IACvF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAID;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,cAAc,CAAC,CAsC7G;AAED,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAExD;AAED,0FAA0F;AAC1F,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAOtD"}
|
package/dist/served.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
2
|
+
/**
|
|
3
|
+
* Follow a `tokenURI` and return what came back.
|
|
4
|
+
*
|
|
5
|
+
* A `data:` URI is **not** an error here — it IS the document, and a fully-on-chain project's whole
|
|
6
|
+
* point is that there is no server to ask. Saying so plainly is the honest answer; reporting it as a
|
|
7
|
+
* failure would punish the strongest configuration the protocol offers.
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchServedTokenUri(uri, opts = {}) {
|
|
10
|
+
if (!/^https?:\/\//i.test(uri)) {
|
|
11
|
+
return {
|
|
12
|
+
url: null,
|
|
13
|
+
skipped: uri.startsWith('data:')
|
|
14
|
+
? 'the tokenURI IS the document (a data: URI) — it resolves on-chain, so there is no server to ask'
|
|
15
|
+
: `the tokenURI is not an http(s) URL (${uri.slice(0, 40)}…) — nothing to fetch`,
|
|
16
|
+
status: null,
|
|
17
|
+
contentType: null,
|
|
18
|
+
body: null,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const doFetch = opts.fetchFn ?? fetch;
|
|
22
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
23
|
+
const ctrl = new AbortController();
|
|
24
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
25
|
+
try {
|
|
26
|
+
const res = await doFetch(uri, { signal: ctrl.signal, redirect: 'follow' });
|
|
27
|
+
return {
|
|
28
|
+
url: uri,
|
|
29
|
+
skipped: null,
|
|
30
|
+
status: res.status,
|
|
31
|
+
contentType: res.headers.get('content-type'),
|
|
32
|
+
body: await res.text(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const aborted = err.name === 'AbortError' || ctrl.signal.aborted;
|
|
37
|
+
return {
|
|
38
|
+
url: uri,
|
|
39
|
+
skipped: null,
|
|
40
|
+
status: null,
|
|
41
|
+
contentType: null,
|
|
42
|
+
body: null,
|
|
43
|
+
error: aborted ? `no response in ${timeoutMs}ms` : err.message,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Whether the served answer is one a marketplace could actually use. */
|
|
51
|
+
export function servedOk(served) {
|
|
52
|
+
return served.status !== null && served.status >= 200 && served.status < 300;
|
|
53
|
+
}
|
|
54
|
+
/** Pretty-print a served body when it is JSON, else return it unchanged. Never throws. */
|
|
55
|
+
export function prettyBody(body) {
|
|
56
|
+
if (!body)
|
|
57
|
+
return '';
|
|
58
|
+
try {
|
|
59
|
+
return JSON.stringify(JSON.parse(body), null, 2);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return body; // not JSON — a provider's HTML error page is still worth seeing verbatim
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=served.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"served.js","sourceRoot":"","sources":["../src/served.ts"],"names":[],"mappings":"AAmCA,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAW,EAAE,OAA2B,EAAE;IAClF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO;YACL,GAAG,EAAE,IAAI;YACT,OAAO,EAAE,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC;gBAC9B,CAAC,CAAC,iGAAiG;gBACnG,CAAC,CAAC,uCAAuC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,uBAAuB;YAClF,MAAM,EAAE,IAAI;YACZ,WAAW,EAAE,IAAI;YACjB,IAAI,EAAE,IAAI;SACX,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACvD,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAC,CAAC,CAAC;QAC1E,OAAO;YACL,GAAG,EAAE,GAAG;YACR,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;YAC5C,IAAI,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE;SACvB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAI,GAAa,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;QAC5E,OAAO;YACL,GAAG,EAAE,GAAG;YACR,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,IAAI;YACZ,WAAW,EAAE,IAAI;YACjB,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB,SAAS,IAAI,CAAC,CAAC,CAAE,GAAa,CAAC,OAAO;SAC1E,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,QAAQ,CAAC,MAAsB;IAC7C,OAAO,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC;AAC/E,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,UAAU,CAAC,IAAmB;IAC5C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,yEAAyE;IACxF,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artblocks/abx-cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.17",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "ABX CLI ('abx') — the agentic UX surface of the Self-Host Toolkit (Layer 3). Deploy, index, serve, and demo a self-hosted ABX project end to end. Wraps the SDK; runs a different implementation and the protocol works identically.",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"dist",
|
|
13
13
|
"skill",
|
|
14
14
|
"assets",
|
|
15
|
+
"CHANGELOG.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
17
18
|
"engines": {
|
|
@@ -38,13 +39,13 @@
|
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"viem": "^2.21.0",
|
|
41
|
-
"@artblocks/abx-sdk": "0.1.0-alpha.
|
|
42
|
-
"@artblocks/abx-
|
|
43
|
-
"@artblocks/abx-
|
|
44
|
-
"@artblocks/abx-
|
|
42
|
+
"@artblocks/abx-sdk": "0.1.0-alpha.9",
|
|
43
|
+
"@artblocks/abx-storage": "0.1.0-alpha.9",
|
|
44
|
+
"@artblocks/abx-token-api": "0.1.0-alpha.12",
|
|
45
|
+
"@artblocks/abx-indexer": "0.1.0-alpha.10"
|
|
45
46
|
},
|
|
46
47
|
"optionalDependencies": {
|
|
47
|
-
"@artblocks/abx-effects": "0.1.0-alpha.
|
|
48
|
+
"@artblocks/abx-effects": "0.1.0-alpha.9"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"playwright": "1.61.1"
|
package/skill/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: abx-self-host
|
|
|
3
3
|
description: Launch and operate a self-hosted ABX NFT end to end with the ABX CLI (`abx`) on testnet — a 1/1 (`abx deploy`), a multi-token Series from a folder of media (`abx deploy-series`), or a generative/code drop (`abx deploy-code`). Covers on-chain vs off-chain metadata, storage custody (local disk, S3/R2, IPFS, Arweave), deploy + mint (now or pre-warmed at a predicted address), rendered thumbnails and on-chain traits for code art, primary sales via the shared fixed-price minter, and owner ops (transfer, refresh, re-point URIs, royalties, lock fields, pause/unpause, supply cap, delegate minting). Use when the user wants to self-host an ABX project, take an image to an NFT on testnet, deploy a collection from a folder of images, launch generative/code art, mint or run a primary sale, refresh a listing, operate a project they launched, choose a storage backend, stand up hosting they own, or point a project at a hosted/managed metadata provider with an API key.
|
|
4
4
|
compatibility: Drives the abx CLI (@artblocks/abx-cli). Co-versioned with it — install/refresh with `abx skill install` so this skill matches the CLI's `abx version`. Requires Node 22.5+.
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.1.0-alpha.
|
|
6
|
+
version: "0.1.0-alpha.17"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# ABX Self-Host Toolkit (`abx`)
|
|
@@ -26,7 +26,7 @@ L3 agentic surface: image → live self-hosted NFT the creator owns — a **1/1*
|
|
|
26
26
|
- **Is the work finished yet?** If the creator is still *making* the piece, you're in **[Phase 0](#phase-0--make-the-work-first-skip-every-gate-below-until-its-good)** — iterate on the art and keep every deploy question off the table until they say ship. The gates below apply to launching something that already exists.
|
|
27
27
|
- **Two gates decide everything: (1) demo or real? (2) who signs?** Settle both first — *once there's something to launch*.
|
|
28
28
|
- **Confirm the full config before any on-chain write** ([readout](#confirm-before-sending)); wait for go-ahead. Never invent a field silently (name/symbol from filename, an auto description) — show it, flag it `inferred`.
|
|
29
|
-
- **Never hand-build a service URL — ask the chain, then check the reference.** A contract commits its own metadata URL on-chain, so `abx tokenuri <addr
|
|
29
|
+
- **Never hand-build a service URL — ask the chain, then check the reference.** A contract commits its own metadata URL on-chain, so `abx tokenuri <addr> --fetch` (token) and `abx contracturi <addr>` (ERC-7572 collection) give you the answer *and* follow it, printing **what is actually served** — no route grammar to remember, no curl. (Bare `abx tokenuri` reads the chain only; `--fetch` is what GETs the URL the contract names.) **A 404/error on a URL you constructed is evidence about your URL, never about the service.** Don't infer a path from a similar-looking one (dropping the token id off `/t/<chain>/<addr>/<id>` does **not** give collection metadata — that's `/c/<chain>/<addr>`); look it up in [hosting.md](reference/hosting.md#token-api-the-resolver). Before telling anyone a service is broken, reproduce it with a **CLI command** — a real service miss says which of three things it is in a machine `code` (`invalid_request` = your path shape · `unknown_route` = no such route here · `not_registered` = this node doesn't index that contract), and none of those mean "down".
|
|
30
30
|
- **Safe to explore:** `abx <cmd> --help` and `abx deploy --dry-run` never send. You never run a real write just to learn flags.
|
|
31
31
|
- **`deploy` returns; `demo`/`serve`/`preview` block** (they serve) — background them or warn. Background `abx preview` and relay its URL, then keep working while the creator looks; `--shoot` is the one preview mode that exits on its own.
|
|
32
32
|
|
|
@@ -180,7 +180,10 @@ A **program is the content** (`abx deploy-code` → a `SeriesCode`): output is a
|
|
|
180
180
|
| Script + a library on-chain (e.g. `p5@1.0.0`, **Sepolia only** — needs the AB dependency registry, absent on Base Sepolia) | `--script f.js --onchain-uri --dep p5@1.0.0` | **fully on-chain** — *iff the dep report says “ON-CHAIN bytes available”* |
|
|
181
181
|
| Script + a CDN-only library, or very large params | `--script f.js --public-base-url https://…` | **resolver lane** — you host a resolver; live metadata |
|
|
182
182
|
| A pre-built app (webpack/vite output) | `--code-dir build/` (+ `--onchain-uri` or `--public-base-url`) | **no server to run**, gateway-served → **never** chain-complete (8KB param budget) |
|
|
183
|
-
| A **Solidity renderer** (SVG on-chain, from seed+params) | `--image-renderer 0x<svg> [--attributes-renderer 0x<traits>] --onchain-uri` (
|
|
183
|
+
| A **Solidity renderer** (SVG on-chain, from seed+params) | `--image-renderer 0x<svg> [--attributes-renderer 0x<traits>] --onchain-uri` (no `--script`) | **truly in-chain** — image + traits computed on-chain, tokenURI on-chain, **zero infra**; no `animation_url` (no program) |
|
|
184
|
+
| **A JS program AND on-chain thumbnail/traits** — the both-worlds shape | `--script f.js --image-renderer 0x<svg> --attributes-renderer 0x<traits> --onchain-uri` | **every surface in-chain, zero infra** — `animation_url` assembles from the script chunks, `image`+`attributes` computed by Solidity. **Nothing to render or host.** |
|
|
185
|
+
|
|
186
|
+
**`--script` and `--image-renderer` are NOT an either-or.** Passing both is the strongest shape available and the most-missed one: the program keeps driving `animation_url` on-chain while the Solidity renderers supply `image` and `attributes`, so an interactive generative piece gets a real marketplace thumbnail and real traits with **no resolver, no runner, no bucket**. Reach for it whenever the creator can supply (or fork) a Solidity renderer. The dry run's `Surfaces` block confirms each surface before gas is spent.
|
|
184
187
|
|
|
185
188
|
**⭐ In-chain Solidity lane — recommend on-chain `tokenURI` without hesitation.** The artwork is a Solidity `IAbxFieldRenderer` returning an SVG from seed+params (not a JS program), wired `--image-renderer 0x… [--attributes-renderer 0x…] --onchain-uri` with **no `--script`/`--code-dir`**: image AND traits computed on-chain, `tokenURI` assembled on-chain — **genuinely nothing to run** (a small SVG, so the large-`tokenURI` caveat doesn't apply). **NOT no-code, though**: the renderer is a contract the creator compiles + deploys with Foundry. **`abx scaffold-renderer <dir>`** writes a ready-to-build project (worked renderer + coherent traits + the interfaces with invariants + never-revert tests + deploy script); they `forge test`, deploy, and hand the address to `--image-renderer 0x…` (which verifies code-at-address, never compiles/deploys Solidity). Not comfortable with Solidity? Steer to the resolver or (tiny static SVG) `--onchain-image`. Depth → [reference/code-projects.md](reference/code-projects.md#in-chain-solidity-svg--the-zero-dependency-lane) · interface/invariants → https://abx.docs.artblocks.io/protocol/renderers/.
|
|
186
189
|
|
|
@@ -284,7 +287,7 @@ A token is **not "just a picture."** It anchors **named, typed files** ("artifac
|
|
|
284
287
|
- **PostParams are the exception — they need no resolver.** Params enumerate on-chain, so a bare `tokenURI` already carries every set value under **`abx_params`**. The line to give a creator: *attachments always need a resolver; params never do.*
|
|
285
288
|
- **Effect outputs are artifacts too.** A code project's effect runner publishes `render/image`, `render/traits`, and any extra declared output (e.g. a hi-res `render/print`) into the same manifest automatically, at the current settled state — files appear as tokens are minted and params change (see [code-projects](reference/code-projects.md)).
|
|
286
289
|
- **Not the same as a Series.** `deploy-series` makes **N separate tokens, one file each**. The data plane is how **one** token holds several named files. Depth (representations, verify, reserved keys, on-chain-vs-resolver) → [operating.md](reference/operating.md#attaching-files--the-data-plane).
|
|
287
|
-
- **Set expectations honestly (say it up front).** No mainstream marketplace (OpenSea/Blur) shows a "files" tab **today** — they render `image`/`animation_url` only. Attached files are a durable, cryptographically-anchored part of the token *now*, read by **data-plane-aware tools and any resolver**; broad marketplace display is future adoption. So a creator verifies an attach
|
|
290
|
+
- **Set expectations honestly (say it up front).** No mainstream marketplace (OpenSea/Blur) shows a "files" tab **today** — they render `image`/`animation_url` only. Attached files are a durable, cryptographically-anchored part of the token *now*, read by **data-plane-aware tools and any resolver**; broad marketplace display is future adoption. So a creator verifies an attach with **`abx tokenuri <addr> --fetch`** — the served document carries the `artifacts` manifest — not by refreshing OpenSea (which won't show it).
|
|
288
291
|
|
|
289
292
|
## After launch — tell the creator (durability + owner care)
|
|
290
293
|
|
|
@@ -184,7 +184,8 @@ abx deploy-code --image-renderer 0x<svgRenderer> [--attributes-renderer 0x<trait
|
|
|
184
184
|
--onchain-uri --schema palette:HexColor:TokenOwner --name "…" --symbol … [--dep none]
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
-
- **No `--script`/`--code-dir
|
|
187
|
+
- **No `--script`/`--code-dir` is ALLOWED, not required.** `deploy-code` accepts a renderer-only project (neither program mode) as long as `--image-renderer` (and/or `--attributes-renderer`) is set. Renderer-only has 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`).
|
|
188
|
+
- **But a script AND renderers together is the both-worlds shape — verified, and the most-missed option.** `--script f.js --image-renderer 0x… --attributes-renderer 0x… --onchain-uri` gives `animation_url` assembled on-chain from the script chunks *and* `image`/`attributes` computed by Solidity: every marketplace surface has an on-chain home, and there is **nothing to render, host, or refresh** (no runner, no bucket, no resolver). The fields are independent in the metadata renderer, so they compose. Prefer this over renderer-only whenever the piece is actually a program.
|
|
188
189
|
- **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.
|
|
189
190
|
- **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/.
|
|
190
191
|
- **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`.
|
|
@@ -199,7 +200,7 @@ The renderer is the creator's own Solidity (compiled + deployed with forge — t
|
|
|
199
200
|
- **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.
|
|
200
201
|
- **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.
|
|
201
202
|
- **`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).
|
|
202
|
-
- **Read params LIVE via `IAbxParams(token)`** (`tokenParam`/`contractParam`, token scope wins; `tokenParamKeys`/`contractParamKeys` if the renderer must handle keys it wasn't written to name) — so a collector's `configure-param` shows up on the next read with no redeploy. Never bake a param value in at deploy.
|
|
203
|
+
- **Read params LIVE via `IAbxParams(token)`** (`tokenParam`/`contractParam`, token scope wins; `tokenParamKeys`/`contractParamKeys` if the renderer must handle keys it wasn't written to name). **`Bytes`/`String` params need the OTHER reader:** their `bytes32` is a keccak commitment (`valueIsHash == true`) and the content comes from `tokenParamData(tokenId, key)` / `contractParamData(key)` (~24KB per key — the two types that can carry an actual payload). Reading a `Bytes` param through `tokenParam` hands the renderer a hash and draws garbage with nothing failing anywhere; empty returned bytes are the "use a default" signal — so a collector's `configure-param` shows up on the next read with no redeploy. Never bake a param value in at deploy.
|
|
203
204
|
- **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.
|
|
204
205
|
|
|
205
206
|
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.
|
|
@@ -232,7 +233,7 @@ abx deploy-code (--script <file> | --code-dir <dir>) --name "…" --symbol … \
|
|
|
232
233
|
- **`--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).
|
|
233
234
|
- **`--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.)
|
|
234
235
|
- **Seeds** — drawn at mint from the canonical `AbxSeedSource` (settled once assigned; `--no-seed` opts out; curated pre-set seeds win).
|
|
235
|
-
- **`--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
|
|
236
|
+
- **`--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). **Payload types are explicit: `String` takes literal text, `Bytes` takes `0x`-prefixed hex or `--file <path>` — a bare string on a `Bytes` key is REFUSED**, because storing those characters as bytes can't be told apart from meaning them literally; 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.
|
|
236
237
|
- **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.
|
|
237
238
|
- **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).
|
|
238
239
|
|
|
@@ -251,10 +252,11 @@ The render effect is the **ONLY source of a real thumbnail** — skip it and `im
|
|
|
251
252
|
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.**
|
|
252
253
|
- **The rendered thumbnail is only real if the on-chain `image` had a destination at deploy** (`--image-base <bucket>` or a resolver). If you deployed `--onchain-uri` with neither, `abx render` writes to a local store the tokenURI never points at → **orphaned**; the marketplace still is the placeholder forever. There is no fix without a re-point tx (`set-field image <public url>` then re-render) — which is why it's a deploy-time decision.
|
|
253
254
|
2. the resolver serves `/t/<chainId>/<addr>/0` (real JSON, **not** `{"error":"unknown project"}`). If it errors, it's still backfilling or scanning from block 0 ([setup.md](setup.md)) — fix the **hosted** resolver; a local `abx serve` does **not** fix a hosted-baked token.
|
|
254
|
-
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).
|
|
255
|
-
4. `abx
|
|
255
|
+
3. the live view `/a/<chainId>/<addr>/0` loads. A **`503`** ("its on-chain code has not been folded into the projection yet") is the retry-able answer — the resolver knows it's a code project but hasn't read the code yet; wait a beat and re-ask. A **`404`** is the terminal one, and the split is the diagnosis: **`404 {"error":"no live view — not a code project"}` on a project that IS a code drop is a scan-floor bug, NOT a version/compat problem** — the resolver indexed *above* the deploy block, so it missed the `code` field written at deploy (`collectionFields` comes back `[]`). Check `GET /api/project/<addr>` → `fromBlock` should equal the deploy block and `collectionFields` should contain `code`. Fix: `abx add <addr> --remote <url> --from-block <deployBlock>` (a *changed* floor forces a full replay). Do **not** conclude "the resolver doesn't support code projects" or redeploy as a static NFT. Directory mode then 302s to the gateway — the redirect must have **exactly one** gateway prefix (a doubled `https://arweave.net/https://arweave.net/…` is a stored-locator bug, fixed in-toolkit).
|
|
256
|
+
4. `abx tokens <addr>` → **every** token's owner, seed, and params in one read, straight from the contract (no indexer, no resolver, no server). This is the "what did the seeds actually deal?" answer, and for a generative collection **the seed list IS the collection** — the natural input to any distribution check before a real launch. `--json` for the machine form (`{tokenId, owner, seed, params}` per token). Note what it is *not*: traits come from running the script against the seed, so a trait spread comes from `abx render` / the effects runner, never from this command. Do **not** hand-roll the old workaround (serve → GET the project API → base64-decode `tokenURI` → base64-decode the `animation_url` inside it → regex the seed out of the HTML); every value is a plain contract read.
|
|
257
|
+
5. `abx verify <addr>` → per-minted-token render presence + live-data posture. **For a HOSTED drop use `abx verify <addr> --remote <resolver>`** — it reads the resolver's effect-status API and reports the real **4-state** per token: `up to date` · `rendering` · `failed` (with the actual error + attempt count — fix, then `abx render <addr> <id> --force --remote`) · `stale` (the next notify/sweep picks it up). Plain `abx verify` only checks THIS machine's store, so a render **published** to a hosted resolver reads as a false placeholder locally. A big batch drains through the runner's queue in ascending token order — `stale → rendering → up to date` is normal.
|
|
256
258
|
|
|
257
|
-
**⚠ Arweave (`--backend arweave`) directory mode propagates with a DELAY — set this expectation.** Unlike IPFS/Pinata (a pin is servable almost immediately), a Turbo/Arweave upload settles over **minutes (sometimes longer)** before the gateway serves it. So right after an arweave directory deploy: the live view `/a` 302s to `arweave.net`, which **404s until it propagates**, and a render run *now* will (correctly) **fail with "content isn't servable yet" and store nothing** — expected, not a bug, and it will NOT leave a garbage 404-page thumbnail. **Re-run the render (or let the effects service sweep) once the content is live** — check with
|
|
259
|
+
**⚠ Arweave (`--backend arweave`) directory mode propagates with a DELAY — set this expectation.** Unlike IPFS/Pinata (a pin is servable almost immediately), a Turbo/Arweave upload settles over **minutes (sometimes longer)** before the gateway serves it. So right after an arweave directory deploy: the live view `/a` 302s to `arweave.net`, which **404s until it propagates**, and a render run *now* will (correctly) **fail with "content isn't servable yet" and store nothing** — expected, not a bug, and it will NOT leave a garbage 404-page thumbnail. **Re-run the render (or let the effects service sweep) once the content is live** — check with **`abx storage status ar://<manifestTxid>/index.html`** (`ready` vs `propagating` vs `unreachable`; exits non-zero until it serves, so `until abx storage status <loc>; do sleep 10; done` is the whole wait). Do NOT reach for `curl` here — the command also probes other gateways, which is what distinguishes "yours is behind" from "the locator is wrong", and it tells you plainly not to re-upload. IPFS directory content is renderable right after the pin.
|
|
258
260
|
|
|
259
261
|
## Mint order, timing, pause, supply
|
|
260
262
|
|
|
@@ -28,6 +28,8 @@ After launch the owner operates the project. Each command builds a tx, signs it
|
|
|
28
28
|
|
|
29
29
|
**`lock-field` / `lock-uri` are permanent — always confirm with the human first.**
|
|
30
30
|
|
|
31
|
+
**Welding a PARAM is a different verb.** Fields and [PostParams](code-projects.md#postparams) are separate namespaces that can share a name, and `lock-field` freezes the *field*. To make a param's value permanent, lock its schema: `abx set-schema <addr> --schema <key>:<Type>:<Auth>:lock=now` — after which every `configure-param` on that key reverts. (`lock-field` refuses a declared param key rather than letting you lock the wrong thing.)
|
|
32
|
+
|
|
31
33
|
### Authorship + rights (credit + license)
|
|
32
34
|
|
|
33
35
|
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:
|
package/skill/reference/setup.md
CHANGED
|
@@ -26,7 +26,8 @@ When `abx doctor` flags a tight range, or a reconstruction would be large/slow
|
|
|
26
26
|
- **`serve`/resolver returns `{"error":"unknown project"}`** → the resolver you hit doesn't have that contract in *its own* projection store **yet**. Two distinct situations — diagnose which, and NEVER default to "RPC limit":
|
|
27
27
|
- **A remote resolver you just registered** (`abx add … --remote`): it may still be **backfilling** — hit `GET /` (or `abx remote <name|url>`, which lists the projects the token sees) to see if it's appearing. If it's slow or stuck, the cause is almost always a **from-genesis scan (from-block=0)**, not the RPC tier — a fixed `abx add` forwards the deploy block, so re-run it and confirm the floor. (Fixed in-toolkit: a first remote add now forwards/derives the deploy block and refuses a genesis default.)
|
|
28
28
|
- **A local `abx serve`**: a **store/port** problem. Usual causes, in order: (1) a **stale/duplicate `abx serve` from an old session** holds the port and serves a *different* store — hit `GET /` and see what it lists; (2) you're serving a different store directory than the deploy indexed into; (3) the contract was never registered there. Fix the server/port/registration.
|
|
29
|
-
- **`/a/…` returns `
|
|
29
|
+
- **`/a/…` returns `503` with "its on-chain code has not been folded into the projection yet"** → exactly what it says: the resolver knows this is a code project (from the deployed extensions) but hasn't read its code yet. **Retry** — this is normal right after a register. It only means trouble if it persists, and then it's the scan-floor bug below.
|
|
30
|
+
- **`/a/…` returns `404 {"error":"no live view — not a code project"}` on a project that IS a code drop** → the resolver indexed *above* the deploy block, so it never saw the deploy at all — a scan-floor bug, **not** a resolver version/compat gap (do NOT redeploy as a static NFT). The 404-vs-503 split is the diagnosis: 404 on a real code drop means the fold never saw this contract's deploy, which is the scan-floor signature. This is the *opposite* of the genesis bug: the floor is too **high**, not too low. It happened when a `--sign` code deploy spanned blocks (deploy at N, mint at N+2) and the mint block was recorded as the floor. Confirm: `GET /api/project/<addr>` → `collectionFields` is `[]` and `fromBlock` sits above the deploy block. Fix: `abx add <addr> --remote <url> --from-block <deployBlock>` — a *changed* floor forces a full replay that picks up the `code` field. Find the true deploy block with `abx add <addr>` locally (it prints "deploy block N (discovered on-chain)"). (Fixed in-toolkit: `deploy-code` now records the clone-CREATION block, and discovers it on-chain rather than trusting the last-tx receipt.)
|
|
30
31
|
- **directory-mode live view 302s to a doubled URL** (`https://arweave.net/https://arweave.net/<txid>/index.html`) → the `code` locator was stored as a full gateway URL and the gateway got prefixed again. Fixed in-toolkit (deploy stores the bare txid/CID; the resolver serves an already-absolute locator verbatim). A resolver image built before the fix still doubles — redeploy it to pick up the resolver-side tolerance.
|
|
31
32
|
- **the thumbnail stays a placeholder on a HOSTED resolver even after `abx render` reports `ran=1`** → the render bytes landed in a store the hosted resolver can't read. A local `abx render` with the default `fs` backend writes to your laptop; `ipfs`/`arweave` write a LOCAL key→CID index the resolver doesn't have. Fix: render **to** the resolver — `abx render <addr> --remote <resolver>` (uploads to your `ABX_STORAGE_BACKEND` home and **publishes** a locator/bytes the resolver serves), or stand up the runner beside it (`abx deploy-effects --resolver-url <resolver>`). Only a SHARED `s3`/`cloud` bucket makes a bare local render visible to a hosted resolver. Confirm with `abx verify <addr>`.
|
|
32
33
|
- **resolver won't start / errors about `ABX_PUBLIC_BASE_URL`** → it refuses a placeholder `.example` base (a scaffold leftover) or a `localhost` base in a hosted image (`ABX_HOSTED=1`), because it bakes that base into every image/animation URL it serves — a bad one serves dead links, so a loud fail beats silent breakage. Set `ABX_PUBLIC_BASE_URL` to the resolver's real public URL (`fly secrets set ABX_PUBLIC_BASE_URL=https://<app>.fly.dev`, or your custom domain). The current scaffold bakes the real platform hostname by default, so this only bites a hand-edited/old artifact or a stripped env.
|
|
@@ -31,5 +31,18 @@ A large on-chain `tokenURI` document can exceed the **unauthenticated eth_call g
|
|
|
31
31
|
### The on-chain tokenURI points at `localhost`
|
|
32
32
|
The base URL baked on-chain is a localhost/placeholder (a dev-escape deploy, or a base set without a public host) → it resolves for no one. Re-point to a public resolver: `abx set-token-uri <addr> --uri https://<your-resolver>` (+ `set-contract-uri`), then `abx refresh`. A normal `deploy-code`/`deploy` **refuses** a localhost base — this only happens via the `ABX_DEV_ALLOW_LOCALHOST_URI` dev escape.
|
|
33
33
|
|
|
34
|
+
### `deploy-code` deployed the contract but the SETUP transaction failed
|
|
35
|
+
A code deploy is **two** transactions: create the clone, then one atomic setup `multicall` (script chunks + schemas + dependencies + the on-chain-URI legs + any reserve mints). When the second fails you own a contract that exists but has no program — `abx verify` reports no code, the live view 404s — and the CREATE2 salt for that address is **spent**, so the dry run's pinned-salt re-run command lands somewhere else now.
|
|
36
|
+
|
|
37
|
+
**The contract is recoverable. Do NOT redeploy, and do NOT hand-assemble a multicall with `cast`.** Finish it:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
abx deploy-code --resume <address> <the SAME content flags the original deploy used> # add --dry-run first
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
It reads what is already on-chain and sends only what is missing, in one transaction — so it is safe to run twice, and if nothing is missing it sends nothing and tells you so. Chunks are compared by **content** (a partial hand repair is respected), a schema that already exists is left alone, and reserve mints are a **shortfall** against current supply, never a re-send. You must pass the script/`--code-dir` again: those bytes are not recoverable from a failed transaction. `--salt`, `--721c`, `--bootstrap-factory` and `--mint-all` are refused — they describe how a contract is *created*, and 721C enrollment in particular can never be added after deploy.
|
|
44
|
+
|
|
45
|
+
If the DEPLOY (first) transaction is what failed, there is nothing to resume — no contract exists. Run a normal deploy.
|
|
46
|
+
|
|
34
47
|
### `abx index`/`abx verify` says "isn't registered"
|
|
35
48
|
Register the project on this node once: `abx add <addr>` (discovers the deploy block, indexes it). Then `index`/`verify` work. `state`/`tokenuri` never need this.
|