@artblocks/abx-cli 0.1.0-alpha.29 → 0.1.0-alpha.30
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 +121 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +132 -34
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +46 -2
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/main.js +5 -2
- package/dist/main.js.map +1 -1
- package/dist/ownerops.d.ts +12 -0
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +153 -3
- package/dist/ownerops.js.map +1 -1
- package/dist/riskgate.d.ts +10 -0
- package/dist/riskgate.d.ts.map +1 -1
- package/dist/riskgate.js +81 -4
- package/dist/riskgate.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +13 -6
- package/skill/reference/code-projects.md +13 -6
package/dist/riskgate.js
CHANGED
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
* free just by routing through here — there is no owner-op-specific reason they lacked it; it was
|
|
16
16
|
* simply never wired.
|
|
17
17
|
*/
|
|
18
|
-
import { zeroAddress } from 'viem';
|
|
18
|
+
import { decodeErrorResult, zeroAddress } from 'viem';
|
|
19
19
|
import { createInterface } from 'node:readline';
|
|
20
|
-
import { assertChainId, envSigningKey } from '@artblocks/abx-sdk';
|
|
20
|
+
import { assertChainId, envSigningKey, makePublicClient, seriesCodeAbi } from '@artblocks/abx-sdk';
|
|
21
21
|
import { signTx } from './signer.js';
|
|
22
22
|
import { isDryRun } from './flags.js';
|
|
23
23
|
// ── tiny ANSI (kept local — every module here stands alone; see signer.ts/ownerops.ts) ───────
|
|
24
|
-
const C = { reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m' };
|
|
24
|
+
const C = { reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m', green: '\x1b[38;5;115m', orange: '\x1b[38;5;215m' };
|
|
25
25
|
const dim = (s) => `${C.dim}${s}${C.reset}`;
|
|
26
26
|
const bold = (s) => `${C.bold}${s}${C.reset}`;
|
|
27
27
|
/** Resolve the signing lane from flags: `--unsigned` (cold) · `--sign` (wallet) · default hot. The
|
|
@@ -103,7 +103,82 @@ function printDryRunPreview(prepared, flags, expectedSigner) {
|
|
|
103
103
|
console.log(` ${dim('to'.padEnd(12))} ${prepared.to ?? dim('(contract deploy)')}`);
|
|
104
104
|
if (expectedSigner)
|
|
105
105
|
console.log(` ${dim('owner'.padEnd(12))} ${expectedSigner}`);
|
|
106
|
-
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A revert's own name, when the payload carries one — `RoyaltyTooHigh()` instead of a raw hex dump
|
|
109
|
+
* or "reverted for an unknown reason" (backlog B48 nit a).
|
|
110
|
+
*
|
|
111
|
+
* Whether we get one is the NODE's choice, not ours: many public endpoints (measured:
|
|
112
|
+
* `base-sepolia-rpc.publicnode.com` and `sepolia.base.org`) answer a failed `eth_call` with a bare
|
|
113
|
+
* `execution reverted` and no data at all. So the no-data case says *that*, rather than implying we
|
|
114
|
+
* looked at a reason and couldn't read it.
|
|
115
|
+
*/
|
|
116
|
+
export function revertReason(err) {
|
|
117
|
+
const data = err?.data ?? err?.cause?.data;
|
|
118
|
+
const hex = typeof data === 'string' ? data : data?.data;
|
|
119
|
+
if (typeof hex === 'string' && hex.length >= 10) {
|
|
120
|
+
try {
|
|
121
|
+
const decoded = decodeErrorResult({ abi: seriesCodeAbi, data: hex });
|
|
122
|
+
return `${decoded.errorName}(${(decoded.args ?? []).join(', ')})`;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
/* not one of ours — fall through to the message */
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const short = err?.shortMessage ?? err?.message ?? 'reverted';
|
|
129
|
+
// viem's phrasing for "the node sent no revert data" reads like OUR failure to decode. Say what
|
|
130
|
+
// actually happened, and what it almost always means.
|
|
131
|
+
if (/unknown reason/i.test(short)) {
|
|
132
|
+
return 'reverted (this RPC returns no revert data) — usually a precondition the contract enforces: a lock, a cap, or the wrong signer';
|
|
133
|
+
}
|
|
134
|
+
return short;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* `eth_call` the prepared transaction against current state, and say what would happen.
|
|
138
|
+
*
|
|
139
|
+
* This is what makes `--dry-run` an *answer* rather than an echo of its own input. It matters most
|
|
140
|
+
* for a **multicall** — `attach`'s batched pairs, `deploy-code`'s setup — because a multicall is one
|
|
141
|
+
* transaction, so simulating it exercises the whole sequence atomically, against real state, with no
|
|
142
|
+
* fork and no new surface. That covers the interaction case a 2026-08-24 field report asked for a
|
|
143
|
+
* fork-rehearsal mode to reach.
|
|
144
|
+
*
|
|
145
|
+
* Reports **unknown**, never a false green, whenever it cannot actually prove anything: no signer to
|
|
146
|
+
* simulate as (an owner-gated call from nobody reverts for the wrong reason), a contract deploy, a
|
|
147
|
+
* target that does not exist yet, or an unreachable node.
|
|
148
|
+
*/
|
|
149
|
+
async function simulateDryRun(prepared, chainKey, expectedSigner) {
|
|
150
|
+
const label = 'simulation'.padEnd(12);
|
|
151
|
+
const unknown = (why) => console.log(` ${dim(label)} ${dim(`unknown — ${why}`)}`);
|
|
152
|
+
if (!prepared.to)
|
|
153
|
+
return unknown('this creates a contract; there is nothing to call yet');
|
|
154
|
+
if (!expectedSigner || expectedSigner === zeroAddress) {
|
|
155
|
+
return unknown('no signer known — pass --for 0x.. (or set a signing key) to simulate as the caller');
|
|
156
|
+
}
|
|
157
|
+
// Everything below is best-effort: a dry run must work OFFLINE and against an unknown chain (the
|
|
158
|
+
// preview is the product; the simulation is a bonus). So even constructing the client is guarded —
|
|
159
|
+
// `resolveChain` throws on a chain key it doesn't know, and that must degrade to `unknown`, never
|
|
160
|
+
// turn a preview into a failure.
|
|
161
|
+
try {
|
|
162
|
+
const client = makePublicClient({ chainKey });
|
|
163
|
+
const code = await client.getCode({ address: prepared.to });
|
|
164
|
+
if (!code || code === '0x')
|
|
165
|
+
return unknown(`${prepared.to} has no code yet on this chain`);
|
|
166
|
+
await client.call({
|
|
167
|
+
account: expectedSigner,
|
|
168
|
+
to: prepared.to,
|
|
169
|
+
data: prepared.data,
|
|
170
|
+
...(prepared.value && prepared.value !== '0x0' ? { value: BigInt(prepared.value) } : {}),
|
|
171
|
+
});
|
|
172
|
+
console.log(` ${dim(label)} ${C.green}✓${C.reset} would succeed ${dim('(eth_call against current state)')}`);
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
// A node that cannot answer is not a failing transaction — do not report one as the other.
|
|
176
|
+
const reason = revertReason(err);
|
|
177
|
+
if (/fetch|network|timeout|ECONN/i.test(reason))
|
|
178
|
+
return unknown(`could not reach the node (${reason})`);
|
|
179
|
+
console.log(` ${dim(label)} ${C.orange}✗ would REVERT${C.reset} — ${reason}`);
|
|
180
|
+
console.log(` ${dim(''.padEnd(12))} ${dim('sending this now would burn gas and change nothing.')}`);
|
|
181
|
+
}
|
|
107
182
|
}
|
|
108
183
|
/**
|
|
109
184
|
* The one send choke point for a SINGLE already-prepared write: `--dry-run` preview (nothing sent,
|
|
@@ -118,6 +193,8 @@ export async function gatedSend(provider, flags, opts) {
|
|
|
118
193
|
if (isDryRun(flags)) {
|
|
119
194
|
const prepared = await resolveProvider(provider, opts.expectedSigner ?? zeroAddress);
|
|
120
195
|
printDryRunPreview(prepared, flags, opts.expectedSigner);
|
|
196
|
+
await simulateDryRun(prepared, opts.chainKey, opts.expectedSigner);
|
|
197
|
+
console.log(dim(`\n Re-run without --dry-run to send (lane: ${laneFromFlags(flags)}).\n`));
|
|
121
198
|
return null;
|
|
122
199
|
}
|
|
123
200
|
await assertChainId(opts.chainKey); // verify the RPC really is the target chain before any irreversible write
|
package/dist/riskgate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"riskgate.js","sourceRoot":"","sources":["../src/riskgate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAC,WAAW,EAAC,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"riskgate.js","sourceRoot":"","sources":["../src/riskgate.ts"],"names":[],"mappings":"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"}
|
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.30",
|
|
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",
|
|
@@ -39,13 +39,13 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"viem": "^2.21.0",
|
|
42
|
-
"@artblocks/abx-
|
|
43
|
-
"@artblocks/abx-
|
|
44
|
-
"@artblocks/abx-
|
|
45
|
-
"@artblocks/abx-token-api": "0.1.0-alpha.
|
|
42
|
+
"@artblocks/abx-sdk": "0.1.0-alpha.22",
|
|
43
|
+
"@artblocks/abx-indexer": "0.1.0-alpha.23",
|
|
44
|
+
"@artblocks/abx-storage": "0.1.0-alpha.22",
|
|
45
|
+
"@artblocks/abx-token-api": "0.1.0-alpha.25"
|
|
46
46
|
},
|
|
47
47
|
"optionalDependencies": {
|
|
48
|
-
"@artblocks/abx-effects": "0.1.0-alpha.
|
|
48
|
+
"@artblocks/abx-effects": "0.1.0-alpha.22"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
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 projects, collector-configurable on-chain parameters (PostParams — typed, auth-gated, settable by creator/token-owner/address), primary sales via the shared fixed-price minter, owner ops (transfer, refresh, re-point URIs, royalties, pause/unpause, supply cap, delegate minting, and the one-way locks: fields, URI config, script, dependencies, param hooks), and optionally listing a deployed collection in the ABX App Store (`abx submit-app`, never folded into deploy). 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 projects (art, collectibles, game assets, or anything else), add collector-settable parameters/traits, mint or run a primary sale, refresh a listing, lock down what a project stores or freeze its param hooks, operate a project they launched, list an ABX app in the App Store, 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.30"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# ABX Self-Host Toolkit (`abx`)
|
|
@@ -70,11 +70,18 @@ L3 agentic surface: image → live self-hosted NFT the creator owns — a **1/1*
|
|
|
70
70
|
allowlists would be different *minters*, not flags) · no post-deploy script replace · and `abx` never
|
|
71
71
|
compiles or deploys a token contract (`scaffold-renderer` writes a renderer, not a collection). If the
|
|
72
72
|
ask needs one of these, say so plainly instead of building around it.
|
|
73
|
-
- **
|
|
74
|
-
point at **your own minter** (`abx set-minter`), and a code project can arm **param
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
- **A custom mechanic almost never needs a custom token — reach for the extension points FIRST.** A
|
|
74
|
+
collection can point at **your own minter** (`abx set-minter`), and a code project can arm **param
|
|
75
|
+
hooks** (`abx set-param-hooks`); one contract can be both, and the token stays a factory clone the
|
|
76
|
+
buyer can verify. Escrow/vault, soulbind-once, a monotonic ratchet, equip-and-validate, burn-to-combine
|
|
77
|
+
— every mechanic built on this toolkit so far is *a controller plus one or two hooks*, and agents
|
|
78
|
+
still reach for "write a token contract" first. Named patterns, working Solidity, and the
|
|
79
|
+
`abx predict` recipe that breaks the controller/schema chicken-and-egg:
|
|
80
|
+
https://abx.docs.artblocks.io/using-abx/guides/custom-mechanics/ — read it before designing one.
|
|
81
|
+
Two caveats: neither point is scaffolded (real engineering), and a **`--transfer` hook is a VETO, not
|
|
82
|
+
a trigger** — it can refuse a transfer but never *cause* one, and a reverting hook stops transfers
|
|
83
|
+
**and mints** for every token. It *does* see burns (`to == 0x0`) on a `--burnable` collection, which is
|
|
84
|
+
how burn-to-combine and redemption settle.
|
|
78
85
|
- **Two gates decide everything: (1) demo or real? (2) who signs?** Settle both first — *once there's something to launch*.
|
|
79
86
|
- **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`.
|
|
80
87
|
- **Never hand-build a service URL — ask the chain.** `abx tokenuri <addr> --fetch` and
|
|
@@ -137,7 +137,7 @@ Two kinds of inputs feed a piece: **settled state** (explicit PostParams, the se
|
|
|
137
137
|
|
|
138
138
|
**Wiring the hooks — `abx set-param-hooks <addr>` (SeriesCode/EditionCode only, owner-only).** A code 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 call that is **also a veto**: see below). 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).
|
|
139
139
|
|
|
140
|
-
**The transfer hook is a VETO — tell the creator before they arm one, and tell a buyer it exists.** The token calls it plainly, so **its revert fails the transfer**, and because a mint is a transfer from `0x0` a reverting hook also **stops minting** for that project, including through the shared minter (the same is true of a burn — `to == 0x0` —
|
|
140
|
+
**The transfer hook is a VETO — tell the creator before they arm one, and tell a buyer it exists.** The token calls it plainly, so **its revert fails the transfer**, and because a mint is a transfer from `0x0` a reverting hook also **stops minting** for that project, including through the shared minter (the same is true of a burn — `to == 0x0` — on a `--burnable` collection, which is exactly the seam burn-to-combine and redemption settle on). This is not a bug to design around: it's how a piece can react to ownership at all, and the hook is always the creator's own contract. (It differs from the 721C/1155C **transfer validator**, which never sees mint/burn precisely so a third party's policy contract cannot brick issuance.) An earlier version of the protocol swallowed the hook's revert and promised the lifecycle could never block a transfer; that promise was withdrawn rather than restated, because the receiver acceptance check runs *after* the hook, so on `safeTransferFrom` — what a marketplace fill wraps — a hook cheap at gas-estimation time and expensive at execution starved it regardless of any cap.
|
|
141
141
|
|
|
142
142
|
**Writing a hook? The interface, and the one trap on an edition.** `IAbxTransferHook.onTokenTransfer(uint256 tokenId, address from, address to, address operator, uint256 amount)` — `operator` is whoever initiated the move (the holder, an approved operator, or a minter; on ERC-1155 an approved marketplace moves a holder's copies, so it is NOT redundant with `from`), and `amount` is copies moved (always 1 on ERC-721). The token refuses to call the hook at all on a **zero-amount** entry or a **self-transfer** — that closes a spoof an independent audit reproduced, where a stranger holding no copy could fire the lifecycle for any id via `safeTransferFrom(from, to, id, 0, "")`. **Think hard before a hook stores per-id state on a multi-copy edition:** params are per id and therefore SHARED, so a hook writing "the current owner" is really writing "whoever moved most recently" and it changes the work for every co-holder. Aggregate or monotonic state (transfer counts, "has ever been held by") is coherent there; a single-owner notion is not, unless the edition size is 1.
|
|
143
143
|
|
|
@@ -186,7 +186,9 @@ Both are fine when *intended*: a 1-copy edition behaves like a 721, and the shar
|
|
|
186
186
|
- **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).
|
|
187
187
|
- **Auth — who may set the param:** `Creator` (the contract owner) · `TokenOwner` (the token's current holder; **delegate.xyz honored**) · `Address(0x…)` (a specific named writer — **name it inline**, e.g. `board:Bytes:Address(0xabc…)`) · and the `Or` combinations `CreatorOrTokenOwner` · `CreatorOrAddress` · `TokenOwnerOrAddress` · `CreatorOrTokenOwnerOrAddress`. The chain enforces it — a wrong signer reverts. There is **no "anyone" leg**, but the `Address` leg is a plain `msg.sender` check with no EOA restriction, so **a contract may hold it** — that is how open / multi-party participation is built (a controller contract applies its own rules and forwards the write). If a creator wants a communal canvas or open entry, that is the shape to describe, not a missing feature.
|
|
188
188
|
- **`:lock=<when>` — an optional 4th field** that freezes the param after a time (`palette:HexColor:TokenOwner:lock=2026-12-31`; ISO date, unix seconds, or `now`). A lock already in the past is permanent, which is the supported way to **retire** a param: `abx retire-param <addr> <key>`. Past the deadline the chain welds **both halves**: every `configure-param` reverts `ParamLockExpired`, and so does any further `set-schema` on that key — so the Type, Auth, bounds and `Select` options are frozen too (otherwise a locked Select's options could be swapped and a collector's chosen "Ember" would re-render as "Frost"). The deadline is **monotonic** — a later `set-schema` may only move it *earlier*, never later and never back to open (`ParamLockNotExtendable`), so a weld can't be undone. It does **not** remove the key (a governed key stays governed) and does **not** erase a value already stored — that value keeps serving. Never describe retiring as deleting. **The one thing the weld does not cover: a contract-scope DEFAULT on that key can still be cleared.** `clearContractParam` is deliberately outside the schema guard (it is the only recovery path from a value poisoned before the schema existed), so an owner can leave a collection-wide default in place, weld the key, sell, and later delete the default — changing every token that never wrote its own value. Clearing can only *remove* a fallback: it cannot forge a value, bypass an auth rule, or touch a token-scope value already written. If a collection-wide value must be frozen, write it **per token** through the governed path rather than relying on the inherited default.
|
|
189
|
+
- **⚠ A zero-length `String`/`Bytes` write is REFUSED on chain** (`InvalidParamValue`, before any hook runs, whatever the auth) — the blob path's `dataLength` is a hook's scalar-vs-blob discriminator, so zero has to be impossible there. This bites any list-like or optional payload: "unequip everything", "clear my inscription", an empty selection. Design the empty state in: a **sentinel byte** the renderer recognizes (`0x00` = empty), or a scalar companion key holding the count. A key that was *never* written reads as unset — that is the only genuinely empty state, and it is not reachable again once written.
|
|
189
190
|
- Examples: a collector-tunable color → `palette:HexColor:TokenOwner`; a collector-chosen mood → `mood:Select[Calm|Wild|Chaotic]:TokenOwner`; a creator-only bounded dial → `speed:Uint256Range[1..10]:Creator`; an on/off toggle → `invert:Bool:TokenOwner`.
|
|
191
|
+
- **Cost, so a minter that configures params can budget gas:** a **cold** scalar write (first ever for that key on that token) ≈ **153k**; a **warm** overwrite ≈ **17k**. A `Bytes`/`String` blob: cold 32 B ≈ **209k**, warm 32 B ≈ **57k**, ~**203 gas per payload byte** on top. Cold-vs-warm dominates, not type — a mint that configures three fresh params pays ~3 cold writes (≈ 426k for two scalars + a 64 B blob), and the same three writes later cost a tenth of that. Budget for cold. Details: https://abx.docs.artblocks.io/protocol/params#what-a-write-costs
|
|
190
192
|
|
|
191
193
|
## `--image-base` — deterministic S3/CDN thumbnail URLs (no metadata resolver)
|
|
192
194
|
|
|
@@ -225,6 +227,8 @@ The renderer is the creator's own Solidity (compiled + deployed with forge — t
|
|
|
225
227
|
- **`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).
|
|
226
228
|
- **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.
|
|
227
229
|
- **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.
|
|
230
|
+
- **A `Select` param stores an INDEX — read its label from the chain, never hardcode the list.** `IAbxConfigurableParams(token).selectOption(key, index) → string` returns one declared option (`paramSchema(key)` returns the whole `string[]`). A renderer that keeps its own parallel `string[]` of names is a second copy with nothing keeping the two in step, so a later `set-schema --force` that reorders or renames an option silently changes what every token *means* without changing what it says. Both getters are on every deployed ABX code project — check before you write the constant.
|
|
231
|
+
- **An augment hook does NOT reach your renderer.** `render(token, tokenId, field)` takes no `tokenData`, so a hook wired on the project never fires for it. The hook is a compute seam over `tokenData`, and the two things that assemble `tokenData` are the canonical `AbxGenerator` (on-chain, for `animation_url`) and the off-chain resolver — a renderer you wrote assembles nothing. Do NOT tell a creator "augment hooks are a resolver concept"; that is wrong (the generator calls one on-chain). The accurate line: **whoever builds tokenData calls the hook, and in your renderer that is you** — so read the live state directly, or read `paramHooks()` and call `augmentTokenParams` yourself (wrapped, so a broken hook can't make `render()` revert).
|
|
228
232
|
|
|
229
233
|
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.
|
|
230
234
|
|
|
@@ -289,27 +293,30 @@ Two things the CLI still does **not** do: it does not write or audit the source
|
|
|
289
293
|
|
|
290
294
|
Be exact about what that gives a collector, because the intuitive word for it is wrong. The governed path is `configureTokenParam(tokenId, "seed", value)`: **the caller supplies the value.** Nothing is re-randomized and the seed source is never called again — with an unbounded `Uint256Range` the authorized party may set literally any 32-byte value, and set it again until they like the output. So describe it as "pick your seed" / "set your own seed", never as a re-roll: a re-roll implies a fresh random draw a collector could reasonably expect to be fair, and there is no draw.
|
|
291
295
|
|
|
292
|
-
The schema can only be declared **before the collection's first seed exists**; afterwards the contract reverts `SeedSettled`. So decide
|
|
296
|
+
The schema can only be declared **before the collection's first seed exists**; afterwards the contract reverts `SeedSettled`. That boundary is the first seed, not the sale opening, and the gap between them is owner-reachable: declaring the schema is an ordinary transaction, so an owner can land it ahead of a buyer's already-signed, already-broadcast first-mint transaction, and the mint still succeeds normally — after which every token the collection ever mints (not just that one) is reassignable. So decide this with the creator, one way or the other, **before opening any sale or granting any minter** — not merely "before minting anything" — and never tell a creator or a buyer that `paramSchema("seed")` reading empty is a forward guarantee once a sale might be live: it only describes the block it was read in. Never promise reassignment as something you'd add later, either. Treat it as advanced and prove it on testnet first: the type has to be a **literal**/scalar one (a `String`/`Bytes` schema routes writes to the data path, which a seed refuses outright). A buyer reads the answer on-chain with `paramSchema("seed")` — or `abx state <addr>`, whose **PostParams** block lists every declared schema: a `seed` row there means the value can be set (and says by whom), no row means final as of that read.
|
|
293
297
|
|
|
294
298
|
## `--copies` — a generative drop sold as an EDITION (EditionCode), and what v1 gives up
|
|
295
299
|
|
|
296
300
|
`abx deploy-code --copies <n|open>` swaps the 721 **SeriesCode** for its ERC-1155 twin **EditionCode**: N ids, each a distinct work/seed, **× `--copies` copies of each**. `--mint-count`/`--mint-all` keep their meaning (which ids premint) and `--mint-amount <n>` sets how many copies of each. Per-id sales, per-id supply caps → [operating.md](operating.md#edition-owner-ops).
|
|
297
301
|
|
|
298
|
-
**Source is `--script`
|
|
302
|
+
**Source is `--script` or a Solidity renderer; on-chain dependencies DO work. The CLI refuses the rest rather than silently dropping them:**
|
|
299
303
|
|
|
300
304
|
| Flag | With `--copies` |
|
|
301
305
|
|---|---|
|
|
302
|
-
| `--script <file>` | ✅
|
|
306
|
+
| `--script <file>` | ✅ a JS program stored on-chain in chunks |
|
|
307
|
+
| `--image-renderer` / `--attributes-renderer` | ✅ **works** — the in-chain Solidity lane, same as the 721 twin |
|
|
303
308
|
| `--dep <ref>` / `--dep-registry` | ✅ **works** — same ordered refs as the 721 lane (index 0 = the runtime) |
|
|
304
309
|
| `--code-dir <dir>` | ❌ refused — *"use `--script` (on-chain chunks) instead"* |
|
|
305
|
-
| `--image-
|
|
310
|
+
| `--image-base` | ❌ refused — it needs the effect runner to write a still **per id**, which this lane doesn't drive |
|
|
311
|
+
|
|
312
|
+
**A TRUE fully-on-chain ERC-1155 exists — say yes to it.** `--copies open --image-renderer 0x… [--attributes-renderer 0x…] --onchain-uri` is a real, permissionless, ongoing 1155 whose image and traits are computed in Solidity and whose `uri(id)` assembles on-chain: **no resolver, no render runner, no bucket, nothing to keep running.** `--script` is *optional* here — a renderer-only edition stores no program and has no `animation_url` (the same shape the 721 lane calls renderer-only), and script + renderers together is the both-worlds composition. This was refused by the CLI until 2026-08-24 and a field report read the refusal as a protocol limit, shipping a 721 "signature + prints" workaround for what should have been an edition. Don't repeat that: the answer to *"can I get a real on-chain-rendered 1155 with no server?"* is **yes**.
|
|
306
313
|
|
|
307
314
|
So **"an edition of my p5 sketch, with p5 coming from the chain" is a real lane**: `abx deploy-code --script art.js --copies 100 --dep p5@1.0.0 --onchain-uri`, on **Sepolia** (`ABX_CHAIN=sepolia` — the AB dependency registry does not exist on Base Sepolia, where the pointer leg is skipped with a warning and the drop is NOT chain-complete). Confirm it with `abx verify <addr>`, which should say *"chain-complete — every dependency resolves to on-chain bytes; no server, gateway, or CDN in the graph"* (a claim about provenance, not immutability — see freezing, above).
|
|
308
315
|
|
|
309
316
|
Two things still to watch on this lane:
|
|
310
317
|
|
|
311
318
|
1. **A self-contained script needs no `--dep` at all** — but if your sketch calls p5 globals (`createCanvas`, `randomSeed`) and you *don't* declare the dependency, it **deploys fine and renders blank**. `abx inspect <script>` reports the libraries it detects; the edition dry-run does not cross-check that for you, so check it yourself before shipping.
|
|
312
|
-
2. **
|
|
319
|
+
2. **On the `--script` lane a thumbnail is still rendered off-chain** by the effect runner, so a JS code edition needs a public home for its stills even when `uri()` is fully on-chain, and `abx verify` says the image is a placeholder until you render one. **This does not apply with `--image-renderer`** — there the image IS the on-chain SVG, there is no still, and nothing to render or host.
|
|
313
320
|
|
|
314
321
|
Everything else about a code project is unchanged by `--copies`.
|
|
315
322
|
|