@artblocks/abx-sdk 0.1.0-alpha.4 → 0.1.0-alpha.40
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 +64 -0
- package/README.md +130 -0
- package/dist/abi/generated.d.ts +8502 -129
- package/dist/abi/generated.d.ts.map +1 -1
- package/dist/abi/generated.js +40 -22
- package/dist/abi/generated.js.map +1 -1
- package/dist/abi/index.d.ts +2645 -168
- package/dist/abi/index.d.ts.map +1 -1
- package/dist/abi/index.js +34 -7
- package/dist/abi/index.js.map +1 -1
- package/dist/anchors.d.ts +325 -0
- package/dist/anchors.d.ts.map +1 -0
- package/dist/anchors.js +740 -0
- package/dist/anchors.js.map +1 -0
- package/dist/chain-support.d.ts +28 -0
- package/dist/chain-support.d.ts.map +1 -0
- package/dist/chain-support.js +65 -0
- package/dist/chain-support.js.map +1 -0
- package/dist/chain-support.json +60 -0
- package/dist/chains.d.ts +24 -10
- package/dist/chains.d.ts.map +1 -1
- package/dist/chains.js +51 -17
- package/dist/chains.js.map +1 -1
- package/dist/chunks.d.ts +162 -31
- package/dist/chunks.d.ts.map +1 -1
- package/dist/chunks.js +179 -14
- package/dist/chunks.js.map +1 -1
- package/dist/clients.d.ts +23 -11
- package/dist/clients.d.ts.map +1 -1
- package/dist/clients.js +17 -18
- package/dist/clients.js.map +1 -1
- package/dist/contract-generations.d.ts +58 -0
- package/dist/contract-generations.d.ts.map +1 -0
- package/dist/contract-generations.js +109 -0
- package/dist/contract-generations.js.map +1 -0
- package/dist/contract-generations.json +48 -0
- package/dist/create2.d.ts +84 -1
- package/dist/create2.d.ts.map +1 -1
- package/dist/create2.js +158 -2
- package/dist/create2.js.map +1 -1
- package/dist/creator-token.d.ts +132 -0
- package/dist/creator-token.d.ts.map +1 -0
- package/dist/creator-token.js +183 -0
- package/dist/creator-token.js.map +1 -0
- package/dist/deploy.d.ts +234 -14
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +289 -111
- package/dist/deploy.js.map +1 -1
- package/dist/deployments.d.ts +38 -5
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +156 -30
- package/dist/deployments.js.map +1 -1
- package/dist/deps.d.ts +123 -6
- package/dist/deps.d.ts.map +1 -1
- package/dist/deps.js +249 -7
- package/dist/deps.js.map +1 -1
- package/dist/env.d.ts +5 -4
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +20 -5
- package/dist/env.js.map +1 -1
- package/dist/errors.d.ts +109 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +148 -0
- package/dist/errors.js.map +1 -0
- package/dist/execute.d.ts +118 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +159 -0
- package/dist/execute.js.map +1 -0
- package/dist/gateways.d.ts +79 -0
- package/dist/gateways.d.ts.map +1 -0
- package/dist/gateways.js +156 -0
- package/dist/gateways.js.map +1 -0
- package/dist/generator-document.d.ts +57 -0
- package/dist/generator-document.d.ts.map +1 -0
- package/dist/generator-document.js +123 -0
- package/dist/generator-document.js.map +1 -0
- package/dist/generator.d.ts +44 -2
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +29 -0
- package/dist/generator.js.map +1 -1
- package/dist/index.d.ts +19 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +48 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +295 -0
- package/dist/inspect.js.map +1 -0
- package/dist/migrate.d.ts +41 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +142 -0
- package/dist/migrate.js.map +1 -0
- package/dist/mime.d.ts +11 -0
- package/dist/mime.d.ts.map +1 -0
- package/dist/mime.js +40 -0
- package/dist/mime.js.map +1 -0
- package/dist/node.d.ts +69 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +92 -0
- package/dist/node.js.map +1 -0
- package/dist/onchain-uri.d.ts +110 -0
- package/dist/onchain-uri.d.ts.map +1 -0
- package/dist/onchain-uri.js +276 -0
- package/dist/onchain-uri.js.map +1 -0
- package/dist/ops.d.ts +507 -14
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +850 -34
- package/dist/ops.js.map +1 -1
- package/dist/policy.d.ts +57 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +35 -0
- package/dist/policy.js.map +1 -0
- package/dist/probe.d.ts +58 -0
- package/dist/probe.d.ts.map +1 -1
- package/dist/probe.js +190 -9
- package/dist/probe.js.map +1 -1
- package/dist/reconstruct.d.ts +141 -3
- package/dist/reconstruct.d.ts.map +1 -1
- package/dist/reconstruct.js +479 -100
- package/dist/reconstruct.js.map +1 -1
- package/dist/resume.d.ts +147 -0
- package/dist/resume.d.ts.map +1 -0
- package/dist/resume.js +146 -0
- package/dist/resume.js.map +1 -0
- package/dist/script-chunks.d.ts +105 -0
- package/dist/script-chunks.d.ts.map +1 -0
- package/dist/script-chunks.js +158 -0
- package/dist/script-chunks.js.map +1 -0
- package/dist/service.d.ts +155 -9
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +103 -11
- package/dist/service.js.map +1 -1
- package/dist/spine.d.ts +47 -4
- package/dist/spine.d.ts.map +1 -1
- package/dist/spine.js +0 -0
- package/dist/spine.js.map +1 -1
- package/dist/staging.d.ts +139 -0
- package/dist/staging.d.ts.map +1 -0
- package/dist/staging.js +139 -0
- package/dist/staging.js.map +1 -0
- package/dist/token.d.ts +11 -1
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +32 -1
- package/dist/token.js.map +1 -1
- package/dist/tokendata.d.ts +63 -3
- package/dist/tokendata.d.ts.map +1 -1
- package/dist/tokendata.js +81 -19
- package/dist/tokendata.js.map +1 -1
- package/dist/tokens.d.ts +119 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +317 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +107 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/util.d.ts +74 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +106 -0
- package/dist/util.js.map +1 -0
- package/package.json +15 -6
package/dist/anchors.js
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { oneOfOneImageAbi, oneOfOneImageFactoryAbi, seriesImageAbi, seriesImageFactoryAbi, abxMetadataRendererAbi, oneOfOneEditionAbi, oneOfOneEditionFactoryAbi, editionImageAbi, editionImageFactoryAbi, } from './abi/index.js';
|
|
2
|
+
import { ANCHOR_GENERATIONS, currentAnchorGeneration, findAnchorGenerationByFactory, resolveFactory, resolveSeriesFactory, resolveRenderer, resolveSeriesCodeFactory, resolveSeedSource, resolveOneOfOneEditionFactory, resolveEditionFactory, resolveEditionCodeFactory, } from './deployments.js';
|
|
3
|
+
import { predictRenderer, predictSeedSource, predictFactory, predictSeriesFactory, predictOneOfOneEditionFactory, predictEditionFactory, predictSeriesCodeFactory, predictEditionCodeFactory, } from './create2.js';
|
|
4
|
+
import { deployFactory, deploySeriesFactory, deployRenderer, deploySeriesCodeFactory, deploySeedSource, deployOneOfOneEditionFactory, deployEditionFactory, deployEditionCodeFactory, } from './deploy.js';
|
|
5
|
+
import { discoverDeployBlock } from './reconstruct.js';
|
|
6
|
+
import { AnchorUnavailableError } from './errors.js';
|
|
7
|
+
// ── the canonical 1/1 clone factory ───────────────────────────────────────────
|
|
8
|
+
/** Is the deployed factory this build's version? Reads its implementation and probes the
|
|
9
|
+
* NEWEST capability the current implementation exposes — `totalSupply()` (the live-count
|
|
10
|
+
* read) — so an older factory whose impl predates it is detected and a fresh, source-current
|
|
11
|
+
* (and thus Etherscan-verifiable) one deployed. A stale impl reverts the probe. (Bump this to
|
|
12
|
+
* the newest capability with each impl change.) */
|
|
13
|
+
/** `AbxVersion.CORE_VERSION` in `contracts/src/libraries/AbxVersion.sol`. MUST move in lockstep with
|
|
14
|
+
* it — the Solidity constant's own NatSpec says so, and `test/anchor-version.test.ts` pins the two
|
|
15
|
+
* together so a bump on one side cannot ship alone. */
|
|
16
|
+
export const ABX_CORE_VERSION = 3n;
|
|
17
|
+
/** Is this implementation built from the current core?
|
|
18
|
+
*
|
|
19
|
+
* A VERSION gate, deliberately, replacing the capability probes that used to stand in for one.
|
|
20
|
+
* Those asked "does the implementation expose feature X" — a question older builds
|
|
21
|
+
* also answers yes to — and carried a comment instructing the next author to hand-bump the probed
|
|
22
|
+
* capability on every implementation change. That convention can report an older factory as
|
|
23
|
+
* `resolved` and keep stamping clones on an outdated implementation. `abxVersion()` has been on
|
|
24
|
+
* every token the whole time; nothing read it. */
|
|
25
|
+
async function implCoreVersionIsCurrent(publicClient, impl) {
|
|
26
|
+
try {
|
|
27
|
+
const v = (await publicClient.readContract({
|
|
28
|
+
address: impl,
|
|
29
|
+
abi: oneOfOneImageAbi, // `abxVersion()` is on AbxBeaconCore — identical on all six token types
|
|
30
|
+
functionName: 'abxVersion',
|
|
31
|
+
}));
|
|
32
|
+
return BigInt(v) === ABX_CORE_VERSION;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false; // no `abxVersion()` at all ⇒ far older than the current core
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function isCurrentFactory(publicClient, factory) {
|
|
39
|
+
try {
|
|
40
|
+
const impl = (await publicClient.readContract({
|
|
41
|
+
address: factory,
|
|
42
|
+
abi: oneOfOneImageFactoryAbi,
|
|
43
|
+
functionName: 'implementation',
|
|
44
|
+
}));
|
|
45
|
+
// The core-version gate runs FIRST: it is the question that actually distinguishes builds. The
|
|
46
|
+
// capability probe below stays as a cheap sanity check on the implementation's shape.
|
|
47
|
+
if (!(await implCoreVersionIsCurrent(publicClient, impl)))
|
|
48
|
+
return false;
|
|
49
|
+
// `totalSupply()` is a view → readContract; it reverts on an impl that predates it.
|
|
50
|
+
await publicClient.readContract({ address: impl, abi: oneOfOneImageAbi, functionName: 'totalSupply' });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get (or deploy) the chain's canonical 1/1 clone factory — the trust anchor: a factory is THE
|
|
59
|
+
* address platforms allowlist by. Resolves the manifest (override → env → manifest); on a chain
|
|
60
|
+
* with no current entry, deploys a fresh one when `allowBootstrap` is set (else throws
|
|
61
|
+
* {@link AnchorUnavailableError} so the caller can refuse with guidance instead of silently
|
|
62
|
+
* fragmenting trust across duplicate "canonical" factories).
|
|
63
|
+
*/
|
|
64
|
+
export async function ensureFactory(publicClient, send, opts) {
|
|
65
|
+
const notify = opts.onEvent ?? (() => { });
|
|
66
|
+
// Resolve the canonical factory from the shipped manifest (flag → env → manifest); on a chain
|
|
67
|
+
// with no entry, deploy a fresh trust anchor and tell the operator how to reuse it.
|
|
68
|
+
const known = resolveFactory(opts.chainId, opts.override);
|
|
69
|
+
if (known) {
|
|
70
|
+
// sanity: is there code there, and is it *this* version of the factory?
|
|
71
|
+
const code = await publicClient.getCode({ address: known });
|
|
72
|
+
if (code && code !== '0x') {
|
|
73
|
+
if (await isCurrentFactory(publicClient, known)) {
|
|
74
|
+
notify({ anchor: 'factory', kind: 'resolved', address: known });
|
|
75
|
+
return known;
|
|
76
|
+
}
|
|
77
|
+
if (!opts.allowBootstrap)
|
|
78
|
+
throw new AnchorUnavailableError('factory', 'stale-version', known);
|
|
79
|
+
notify({ anchor: 'factory', kind: 'stale', address: known, reason: 'stale-version' });
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
if (!opts.allowBootstrap)
|
|
83
|
+
throw new AnchorUnavailableError('factory', 'no-code', known);
|
|
84
|
+
notify({ anchor: 'factory', kind: 'stale', address: known, reason: 'no-code' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// A CREATE2 address is bytecode-bound to the build that predicted it, so this build's OneOfOneImageFactory
|
|
88
|
+
// sitting at its canonical address is already the right anchor — reuse it instead of deploying a
|
|
89
|
+
// duplicate. This is what makes a chain missing from the manifest a non-event.
|
|
90
|
+
const predicted = predictFactory();
|
|
91
|
+
if (!known || known.toLowerCase() !== predicted.toLowerCase()) {
|
|
92
|
+
const pcode = await publicClient.getCode({ address: predicted });
|
|
93
|
+
if (pcode && pcode !== '0x' && (await isCurrentFactory(publicClient, predicted))) {
|
|
94
|
+
notify({ anchor: 'factory', kind: 'canonical', address: predicted });
|
|
95
|
+
return predicted;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!known && !opts.allowBootstrap)
|
|
99
|
+
throw new AnchorUnavailableError('factory', 'not-configured');
|
|
100
|
+
notify({ anchor: 'factory', kind: 'deploying' });
|
|
101
|
+
const { factory, implementation, metadataLib, txHash } = await deployFactory(send, publicClient);
|
|
102
|
+
notify({ anchor: 'factory', kind: 'deployed', address: factory, implementation, metadataLib, txHash, inManifest: false });
|
|
103
|
+
return factory;
|
|
104
|
+
}
|
|
105
|
+
// ── the canonical Series trust anchor ─────────────────────────────────────────
|
|
106
|
+
/** Is the deployed Series factory this build's version? Probes the impl for `nextTokenId()`
|
|
107
|
+
* (the Series-specific issuance cursor), which a stale/incompatible impl won't expose. */
|
|
108
|
+
export async function isCurrentSeriesFactory(publicClient, factory) {
|
|
109
|
+
try {
|
|
110
|
+
const impl = (await publicClient.readContract({
|
|
111
|
+
address: factory,
|
|
112
|
+
abi: seriesImageFactoryAbi,
|
|
113
|
+
functionName: 'implementation',
|
|
114
|
+
}));
|
|
115
|
+
// The core-version gate runs FIRST: it is the question that actually distinguishes builds. The
|
|
116
|
+
// capability probe below stays as a cheap sanity check on the implementation's shape.
|
|
117
|
+
if (!(await implCoreVersionIsCurrent(publicClient, impl)))
|
|
118
|
+
return false;
|
|
119
|
+
// Probe the NEWEST capability the current implementation exposes — ERC-165 support for the
|
|
120
|
+
// IAbxSequentialMint primitive (mint(address) → uint256, the fixed-price-minter target).
|
|
121
|
+
// A pre-minter-spine impl (mint returns void, id not advertised) returns false → a fresh,
|
|
122
|
+
// source-current factory is deployed. (Bump this to the newest capability with each impl change.)
|
|
123
|
+
return (await publicClient.readContract({
|
|
124
|
+
address: impl,
|
|
125
|
+
abi: seriesImageAbi,
|
|
126
|
+
functionName: 'supportsInterface',
|
|
127
|
+
args: ['0x6a627842'], // type(IAbxSequentialMint).interfaceId = bytes4(keccak256("mint(address)"))
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Get (or deploy) the chain's `SeriesImageFactory` — the multi-token trust anchor, the sibling
|
|
135
|
+
* of {@link ensureFactory}. Ownerless + immutable; one deployment serves every Series. */
|
|
136
|
+
export async function ensureSeriesFactory(publicClient, send, opts) {
|
|
137
|
+
const notify = opts.onEvent ?? (() => { });
|
|
138
|
+
const known = resolveSeriesFactory(opts.chainId, opts.override);
|
|
139
|
+
if (known) {
|
|
140
|
+
const code = await publicClient.getCode({ address: known });
|
|
141
|
+
if (code && code !== '0x' && (await isCurrentSeriesFactory(publicClient, known))) {
|
|
142
|
+
notify({ anchor: 'series-factory', kind: 'resolved', address: known });
|
|
143
|
+
return known;
|
|
144
|
+
}
|
|
145
|
+
if (!opts.allowBootstrap)
|
|
146
|
+
throw new AnchorUnavailableError('series-factory', 'stale-version', known);
|
|
147
|
+
notify({ anchor: 'series-factory', kind: 'stale', address: known, reason: 'stale-version' });
|
|
148
|
+
}
|
|
149
|
+
// A CREATE2 address is bytecode-bound to the build that predicted it, so this build's SeriesImageFactory
|
|
150
|
+
// sitting at its canonical address is already the right anchor — reuse it instead of deploying a
|
|
151
|
+
// duplicate. This is what makes a chain missing from the manifest a non-event.
|
|
152
|
+
const predicted = predictSeriesFactory();
|
|
153
|
+
if (!known || known.toLowerCase() !== predicted.toLowerCase()) {
|
|
154
|
+
const pcode = await publicClient.getCode({ address: predicted });
|
|
155
|
+
if (pcode && pcode !== '0x' && (await isCurrentSeriesFactory(publicClient, predicted))) {
|
|
156
|
+
notify({ anchor: 'series-factory', kind: 'canonical', address: predicted });
|
|
157
|
+
return predicted;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!known && !opts.allowBootstrap)
|
|
161
|
+
throw new AnchorUnavailableError('series-factory', 'not-configured');
|
|
162
|
+
notify({ anchor: 'series-factory', kind: 'deploying' });
|
|
163
|
+
const { factory, implementation, metadataLib, txHash } = await deploySeriesFactory(send, publicClient);
|
|
164
|
+
notify({ anchor: 'series-factory', kind: 'deployed', address: factory, implementation, metadataLib, txHash, inManifest: false });
|
|
165
|
+
return factory;
|
|
166
|
+
}
|
|
167
|
+
// ── the canonical on-chain renderer ────────────────────────────────────────────
|
|
168
|
+
/** Is the configured renderer the spec version this build expects (**v11**)? A behind-spec renderer
|
|
169
|
+
* is treated as stale so {@link ensureRenderer} deploys a fresh one (greenfield redeploy-freely:
|
|
170
|
+
* this number is what tells the CLI a deployed renderer predates behavior the current build emits).
|
|
171
|
+
* Bump in lockstep with `AbxMetadataRenderer.SPEC_VERSION`.
|
|
172
|
+
*
|
|
173
|
+
* **This is a security floor, not just a feature floor**, which is why it has moved twice for
|
|
174
|
+
* reasons unrelated to the params projection:
|
|
175
|
+
* - v4 → v5: v4 left the `abx_provenance` `note` unescaped, and that note carries owner-chosen
|
|
176
|
+
* bytes — enough to flip `onChain` to true, shadow the real `image`, or make `tokenURI`
|
|
177
|
+
* unparseable. A v4 renderer does not merely lag a projection; it will lie about provenance.
|
|
178
|
+
* - v5 → v6: removed two on-chain checks in favour of stated trust assumptions
|
|
179
|
+
* and closed an empty-render case that produced an unparseable document. Rendered output differs,
|
|
180
|
+
* so the version has to.
|
|
181
|
+
* - v6 → v7: the `artist` collection field became `creator`.
|
|
182
|
+
* - v7 → v8: fixed a computed `image` locator being data-wrapped, so it
|
|
183
|
+
* was not dereferenceable at all; `abx_provenance` lost `onChain` and `verifiedAgainstChain`
|
|
184
|
+
* (the first was wrong in both directions, the second always null); and `abx_params` left
|
|
185
|
+
* `tokenURI` entirely — params enumerate from the contract, which is canonical, and nothing read
|
|
186
|
+
* the projection back.
|
|
187
|
+
* - v8 → v9: a computed `image` was emitted twice — once as `image`, once again inside `artifacts`
|
|
188
|
+
* — nearly doubling the inner payload of the on-chain-SVG lane's `tokenURI`. The data-plane spec
|
|
189
|
+
* always allowed an on-chain renderer to omit reserved-key duplicates; v9 takes it, so
|
|
190
|
+
* `artifacts` no longer appears on the on-chain lanes at all. Rendered output differs, so the
|
|
191
|
+
* version has to.
|
|
192
|
+
* - v9 → v10: four reserved keys the spec had always listed were projected by neither plane on
|
|
193
|
+
* chain — `background_color`, `youtube_url`, `banner_image`, `featured_image`. A documented
|
|
194
|
+
* reserved key that nothing emits is a protocol hole, so the renderer now emits all four when
|
|
195
|
+
* their representation is chain-reachable. A v9 renderer silently drops them.
|
|
196
|
+
* - v10 → v11: `ipfs` and `arweave` — the two representations that are content-addressed, the two
|
|
197
|
+
* the protocol tells creators to prefer — used to fall back rather than resolve, so
|
|
198
|
+
* `--onchain-uri --backend ipfs` had to bake a gateway HOST into a `url` field and report
|
|
199
|
+
* `source: url` for bytes that live on IPFS. v11 projects them through the collection's
|
|
200
|
+
* preferred gateway (`abx_gateway_ipfs` / `abx_gateway_arweave`, public floors otherwise). A v10
|
|
201
|
+
* renderer serves the placeholder SVG for the very same chain state.
|
|
202
|
+
*
|
|
203
|
+
* Read `specVersion()` rather than probing for a feature: a capability probe answers yes for every
|
|
204
|
+
* build that has the feature, which is exactly how a stale factory once reported as current. */
|
|
205
|
+
export async function isCurrentRenderer(publicClient, renderer) {
|
|
206
|
+
try {
|
|
207
|
+
const v = (await publicClient.readContract({
|
|
208
|
+
address: renderer,
|
|
209
|
+
abi: abxMetadataRendererAbi,
|
|
210
|
+
functionName: 'specVersion',
|
|
211
|
+
}));
|
|
212
|
+
return v === 11n;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** Get (or deploy) the chain's shared `AbxMetadataRenderer` — the on-chain metadata
|
|
219
|
+
* renderer that fully-on-chain tokens point at. Stateless + shared, so one deployment
|
|
220
|
+
* serves every project; redeployed only if missing or a stale spec version. */
|
|
221
|
+
export async function ensureRenderer(publicClient, send, opts) {
|
|
222
|
+
const notify = opts.onEvent ?? (() => { });
|
|
223
|
+
const known = resolveRenderer(opts.chainId, opts.override);
|
|
224
|
+
if (known) {
|
|
225
|
+
const code = await publicClient.getCode({ address: known });
|
|
226
|
+
if (code && code !== '0x' && (await isCurrentRenderer(publicClient, known))) {
|
|
227
|
+
notify({ anchor: 'renderer', kind: 'resolved', address: known });
|
|
228
|
+
return known;
|
|
229
|
+
}
|
|
230
|
+
notify({ anchor: 'renderer', kind: 'stale', address: known, reason: 'stale-version' });
|
|
231
|
+
}
|
|
232
|
+
// The current renderer is CREATE2-deterministic, so it may already exist at its predicted address
|
|
233
|
+
// (deployed by the forge script, or a prior lazy deploy, with the manifest not yet updated). Check
|
|
234
|
+
// there before deploying — self-healing, and never a duplicate. `deployRenderer` lands here too.
|
|
235
|
+
const predicted = predictRenderer();
|
|
236
|
+
if (!known || known.toLowerCase() !== predicted.toLowerCase()) {
|
|
237
|
+
const pcode = await publicClient.getCode({ address: predicted });
|
|
238
|
+
if (pcode && pcode !== '0x' && (await isCurrentRenderer(publicClient, predicted))) {
|
|
239
|
+
notify({ anchor: 'renderer', kind: 'canonical', address: predicted });
|
|
240
|
+
return predicted;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
notify({ anchor: 'renderer', kind: 'deploying' });
|
|
244
|
+
const { renderer, specVersion, txHash } = await deployRenderer(send, publicClient);
|
|
245
|
+
notify({ anchor: 'renderer', kind: 'deployed', address: renderer, specVersion, txHash, inManifest: false });
|
|
246
|
+
return renderer;
|
|
247
|
+
}
|
|
248
|
+
// ── the code-project (SeriesCode) trust anchor ────────────────────────────────
|
|
249
|
+
/** The code-project trust anchor: use the canonical factory, else the CREATE2-deterministic address,
|
|
250
|
+
* else deploy one (a sandbox / fresh chain — libraries linked in-flight; see {@link
|
|
251
|
+
* deploySeriesCodeFactory}). Unlike the 1/1 and Series factories there is no version probe (no
|
|
252
|
+
* `isCurrentSeriesCodeFactory` — a configured factory with code is always accepted); it needs none
|
|
253
|
+
* at the deterministic address, because a CREATE2 address is bytecode-bound — code sitting at
|
|
254
|
+
* `predictSeriesCodeFactory()` is necessarily THIS build, linked against these libraries. */
|
|
255
|
+
export async function ensureSeriesCodeFactory(publicClient, send, opts) {
|
|
256
|
+
const notify = opts.onEvent ?? (() => { });
|
|
257
|
+
const known = resolveSeriesCodeFactory(opts.chainId, opts.override);
|
|
258
|
+
if (known) {
|
|
259
|
+
const code = await publicClient.getCode({ address: known });
|
|
260
|
+
if (code && code !== '0x') {
|
|
261
|
+
notify({ anchor: 'series-code-factory', kind: 'resolved', address: known });
|
|
262
|
+
return known;
|
|
263
|
+
}
|
|
264
|
+
if (!opts.allowBootstrap)
|
|
265
|
+
throw new AnchorUnavailableError('series-code-factory', 'no-code', known);
|
|
266
|
+
notify({ anchor: 'series-code-factory', kind: 'stale', address: known, reason: 'no-code' });
|
|
267
|
+
}
|
|
268
|
+
// Self-heal: the forge script (or an earlier bootstrap) may have put this build's factory at its
|
|
269
|
+
// deterministic address without the manifest catching up. Check there before spending a deploy.
|
|
270
|
+
const predicted = predictSeriesCodeFactory();
|
|
271
|
+
if (!known || known.toLowerCase() !== predicted.toLowerCase()) {
|
|
272
|
+
const pcode = await publicClient.getCode({ address: predicted });
|
|
273
|
+
if (pcode && pcode !== '0x') {
|
|
274
|
+
notify({ anchor: 'series-code-factory', kind: 'canonical', address: predicted });
|
|
275
|
+
return predicted;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!known && !opts.allowBootstrap)
|
|
279
|
+
throw new AnchorUnavailableError('series-code-factory', 'not-configured');
|
|
280
|
+
notify({ anchor: 'series-code-factory', kind: 'deploying' });
|
|
281
|
+
const r = await deploySeriesCodeFactory(send, publicClient);
|
|
282
|
+
notify({
|
|
283
|
+
anchor: 'series-code-factory',
|
|
284
|
+
kind: 'deployed',
|
|
285
|
+
address: r.factory,
|
|
286
|
+
implementation: r.implementation,
|
|
287
|
+
metadataLib: r.metadataLib,
|
|
288
|
+
paramsLib: r.paramsLib,
|
|
289
|
+
codeLib: r.codeLib,
|
|
290
|
+
txHash: r.txHash,
|
|
291
|
+
inManifest: false,
|
|
292
|
+
});
|
|
293
|
+
return r.factory;
|
|
294
|
+
}
|
|
295
|
+
// ── ERC-1155 editions: the three edition trust anchors ────────────────────────
|
|
296
|
+
//
|
|
297
|
+
// `IAbxEditionMint`'s selector-as-interfaceId (`mint(address,uint256,uint256)`, a
|
|
298
|
+
// single-function interface, per ERC-165 convention) — verified via `cast sig
|
|
299
|
+
// "mint(address,uint256,uint256)"` → `0x156e29f6`. Every current-build edition implementation
|
|
300
|
+
// advertises both this AND ERC-1155 itself (`0xd9b67a26`) via `supportsInterface`.
|
|
301
|
+
const EDITION_MINT_INTERFACE_ID = '0x156e29f6';
|
|
302
|
+
const ERC1155_INTERFACE_ID = '0xd9b67a26';
|
|
303
|
+
/** Is the deployed 1/1-edition factory this build's version? Reads its implementation and probes
|
|
304
|
+
* the newest capabilities the current implementation exposes: ERC-165 support for ERC-1155
|
|
305
|
+
* itself AND the edition mint primitive (`IAbxEditionMint`). A stale/incompatible impl fails
|
|
306
|
+
* either probe. (Bump these to the newest capability with each impl change — mirrors {@link
|
|
307
|
+
* isCurrentFactory}'s style.) */
|
|
308
|
+
export async function isCurrentOneOfOneEditionFactory(publicClient, factory) {
|
|
309
|
+
try {
|
|
310
|
+
const impl = (await publicClient.readContract({
|
|
311
|
+
address: factory,
|
|
312
|
+
abi: oneOfOneEditionFactoryAbi,
|
|
313
|
+
functionName: 'implementation',
|
|
314
|
+
}));
|
|
315
|
+
// The core-version gate runs FIRST: it is the question that actually distinguishes builds. The
|
|
316
|
+
// capability probe below stays as a cheap sanity check on the implementation's shape.
|
|
317
|
+
if (!(await implCoreVersionIsCurrent(publicClient, impl)))
|
|
318
|
+
return false;
|
|
319
|
+
const [isErc1155, isEditionMint] = await Promise.all([
|
|
320
|
+
publicClient.readContract({ address: impl, abi: oneOfOneEditionAbi, functionName: 'supportsInterface', args: [ERC1155_INTERFACE_ID] }),
|
|
321
|
+
publicClient.readContract({ address: impl, abi: oneOfOneEditionAbi, functionName: 'supportsInterface', args: [EDITION_MINT_INTERFACE_ID] }),
|
|
322
|
+
]);
|
|
323
|
+
return Boolean(isErc1155) && Boolean(isEditionMint);
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get (or deploy) the chain's canonical 1/1-edition clone factory — the trust anchor for a
|
|
331
|
+
* priced open/limited edition of a single work. The edition twin of {@link ensureFactory},
|
|
332
|
+
* but — because `OneOfOneEditionFactory` is CREATE2-deterministic (see `create2.ts`'s
|
|
333
|
+
* class-level dev note), unlike the 721 1/1 factory's own bootstrap path — this one ALSO
|
|
334
|
+
* self-heals to the predicted address, mirroring {@link ensureRenderer}'s style. The bootstrap
|
|
335
|
+
* leg deploys `AbxParamsLib` + `AbxEditionLib` first (reusing whatever is already on-chain) and
|
|
336
|
+
* links the factory against them — see {@link deployOneOfOneEditionFactory}.
|
|
337
|
+
*/
|
|
338
|
+
export async function ensureOneOfOneEditionFactory(publicClient, send, opts) {
|
|
339
|
+
const notify = opts.onEvent ?? (() => { });
|
|
340
|
+
const known = resolveOneOfOneEditionFactory(opts.chainId, opts.override);
|
|
341
|
+
if (known) {
|
|
342
|
+
const code = await publicClient.getCode({ address: known });
|
|
343
|
+
if (code && code !== '0x') {
|
|
344
|
+
if (await isCurrentOneOfOneEditionFactory(publicClient, known)) {
|
|
345
|
+
notify({ anchor: 'one-of-one-edition-factory', kind: 'resolved', address: known });
|
|
346
|
+
return known;
|
|
347
|
+
}
|
|
348
|
+
if (!opts.allowBootstrap)
|
|
349
|
+
throw new AnchorUnavailableError('one-of-one-edition-factory', 'stale-version', known);
|
|
350
|
+
notify({ anchor: 'one-of-one-edition-factory', kind: 'stale', address: known, reason: 'stale-version' });
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
if (!opts.allowBootstrap)
|
|
354
|
+
throw new AnchorUnavailableError('one-of-one-edition-factory', 'no-code', known);
|
|
355
|
+
notify({ anchor: 'one-of-one-edition-factory', kind: 'stale', address: known, reason: 'no-code' });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const predicted = predictOneOfOneEditionFactory();
|
|
359
|
+
if (!known || known.toLowerCase() !== predicted.toLowerCase()) {
|
|
360
|
+
const pcode = await publicClient.getCode({ address: predicted });
|
|
361
|
+
if (pcode && pcode !== '0x' && (await isCurrentOneOfOneEditionFactory(publicClient, predicted))) {
|
|
362
|
+
notify({ anchor: 'one-of-one-edition-factory', kind: 'canonical', address: predicted });
|
|
363
|
+
return predicted;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (!known && !opts.allowBootstrap)
|
|
367
|
+
throw new AnchorUnavailableError('one-of-one-edition-factory', 'not-configured');
|
|
368
|
+
notify({ anchor: 'one-of-one-edition-factory', kind: 'deploying' });
|
|
369
|
+
const { factory, implementation, metadataLib, paramsLib, editionLib, txHash } = await deployOneOfOneEditionFactory(send, publicClient);
|
|
370
|
+
notify({
|
|
371
|
+
anchor: 'one-of-one-edition-factory',
|
|
372
|
+
kind: 'deployed',
|
|
373
|
+
address: factory,
|
|
374
|
+
implementation,
|
|
375
|
+
metadataLib,
|
|
376
|
+
paramsLib,
|
|
377
|
+
editionLib,
|
|
378
|
+
txHash,
|
|
379
|
+
inManifest: false,
|
|
380
|
+
});
|
|
381
|
+
return factory;
|
|
382
|
+
}
|
|
383
|
+
/** Is the deployed edition-image factory this build's version? Same two-probe shape as {@link
|
|
384
|
+
* isCurrentOneOfOneEditionFactory} (ERC-1155 + `IAbxEditionMint`) — `MaxInvocations`'s id-space
|
|
385
|
+
* cap isn't a distinguishing probe here since it's shared, unchanged, with the 721 Series
|
|
386
|
+
* factory (see `reconstruct.ts`'s `contractType` fold for that same reasoning). */
|
|
387
|
+
export async function isCurrentEditionFactory(publicClient, factory) {
|
|
388
|
+
try {
|
|
389
|
+
const impl = (await publicClient.readContract({
|
|
390
|
+
address: factory,
|
|
391
|
+
abi: editionImageFactoryAbi,
|
|
392
|
+
functionName: 'implementation',
|
|
393
|
+
}));
|
|
394
|
+
// The core-version gate runs FIRST: it is the question that actually distinguishes builds. The
|
|
395
|
+
// capability probe below stays as a cheap sanity check on the implementation's shape.
|
|
396
|
+
if (!(await implCoreVersionIsCurrent(publicClient, impl)))
|
|
397
|
+
return false;
|
|
398
|
+
const [isErc1155, isEditionMint] = await Promise.all([
|
|
399
|
+
publicClient.readContract({ address: impl, abi: editionImageAbi, functionName: 'supportsInterface', args: [ERC1155_INTERFACE_ID] }),
|
|
400
|
+
publicClient.readContract({ address: impl, abi: editionImageAbi, functionName: 'supportsInterface', args: [EDITION_MINT_INTERFACE_ID] }),
|
|
401
|
+
]);
|
|
402
|
+
return Boolean(isErc1155) && Boolean(isEditionMint);
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/** Get (or deploy) the chain's `EditionImageFactory` — the multi-work edition trust anchor,
|
|
409
|
+
* the sibling of {@link ensureSeriesFactory}. CREATE2-deterministic, so it self-heals to its
|
|
410
|
+
* predicted address exactly like {@link ensureOneOfOneEditionFactory} — and, like it, links
|
|
411
|
+
* `AbxEditionLib` (+ that library's own `AbxParamsLib`) on the bootstrap leg. */
|
|
412
|
+
export async function ensureEditionFactory(publicClient, send, opts) {
|
|
413
|
+
const notify = opts.onEvent ?? (() => { });
|
|
414
|
+
const known = resolveEditionFactory(opts.chainId, opts.override);
|
|
415
|
+
if (known) {
|
|
416
|
+
const code = await publicClient.getCode({ address: known });
|
|
417
|
+
if (code && code !== '0x') {
|
|
418
|
+
if (await isCurrentEditionFactory(publicClient, known)) {
|
|
419
|
+
notify({ anchor: 'edition-factory', kind: 'resolved', address: known });
|
|
420
|
+
return known;
|
|
421
|
+
}
|
|
422
|
+
if (!opts.allowBootstrap)
|
|
423
|
+
throw new AnchorUnavailableError('edition-factory', 'stale-version', known);
|
|
424
|
+
notify({ anchor: 'edition-factory', kind: 'stale', address: known, reason: 'stale-version' });
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
if (!opts.allowBootstrap)
|
|
428
|
+
throw new AnchorUnavailableError('edition-factory', 'no-code', known);
|
|
429
|
+
notify({ anchor: 'edition-factory', kind: 'stale', address: known, reason: 'no-code' });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const predicted = predictEditionFactory();
|
|
433
|
+
if (!known || known.toLowerCase() !== predicted.toLowerCase()) {
|
|
434
|
+
const pcode = await publicClient.getCode({ address: predicted });
|
|
435
|
+
if (pcode && pcode !== '0x' && (await isCurrentEditionFactory(publicClient, predicted))) {
|
|
436
|
+
notify({ anchor: 'edition-factory', kind: 'canonical', address: predicted });
|
|
437
|
+
return predicted;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (!known && !opts.allowBootstrap)
|
|
441
|
+
throw new AnchorUnavailableError('edition-factory', 'not-configured');
|
|
442
|
+
notify({ anchor: 'edition-factory', kind: 'deploying' });
|
|
443
|
+
const { factory, implementation, metadataLib, paramsLib, editionLib, txHash } = await deployEditionFactory(send, publicClient);
|
|
444
|
+
notify({
|
|
445
|
+
anchor: 'edition-factory',
|
|
446
|
+
kind: 'deployed',
|
|
447
|
+
address: factory,
|
|
448
|
+
implementation,
|
|
449
|
+
metadataLib,
|
|
450
|
+
paramsLib,
|
|
451
|
+
editionLib,
|
|
452
|
+
txHash,
|
|
453
|
+
inManifest: false,
|
|
454
|
+
});
|
|
455
|
+
return factory;
|
|
456
|
+
}
|
|
457
|
+
/** The code-project EDITION trust anchor: use the canonical factory, else the CREATE2-deterministic
|
|
458
|
+
* address, else deploy one (a sandbox / fresh chain — three libraries linked in-flight; see {@link
|
|
459
|
+
* deployEditionCodeFactory}). Exactly {@link ensureSeriesCodeFactory}'s stance, extended to a third
|
|
460
|
+
* library: no version probe (a configured factory with code is always accepted), and the same
|
|
461
|
+
* bytecode-bound self-heal at `predictEditionCodeFactory()`. */
|
|
462
|
+
export async function ensureEditionCodeFactory(publicClient, send, opts) {
|
|
463
|
+
const notify = opts.onEvent ?? (() => { });
|
|
464
|
+
const known = resolveEditionCodeFactory(opts.chainId, opts.override);
|
|
465
|
+
if (known) {
|
|
466
|
+
const code = await publicClient.getCode({ address: known });
|
|
467
|
+
if (code && code !== '0x') {
|
|
468
|
+
notify({ anchor: 'edition-code-factory', kind: 'resolved', address: known });
|
|
469
|
+
return known;
|
|
470
|
+
}
|
|
471
|
+
if (!opts.allowBootstrap)
|
|
472
|
+
throw new AnchorUnavailableError('edition-code-factory', 'no-code', known);
|
|
473
|
+
notify({ anchor: 'edition-code-factory', kind: 'stale', address: known, reason: 'no-code' });
|
|
474
|
+
}
|
|
475
|
+
const predicted = predictEditionCodeFactory();
|
|
476
|
+
if (!known || known.toLowerCase() !== predicted.toLowerCase()) {
|
|
477
|
+
const pcode = await publicClient.getCode({ address: predicted });
|
|
478
|
+
if (pcode && pcode !== '0x') {
|
|
479
|
+
notify({ anchor: 'edition-code-factory', kind: 'canonical', address: predicted });
|
|
480
|
+
return predicted;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (!known && !opts.allowBootstrap)
|
|
484
|
+
throw new AnchorUnavailableError('edition-code-factory', 'not-configured');
|
|
485
|
+
notify({ anchor: 'edition-code-factory', kind: 'deploying' });
|
|
486
|
+
const r = await deployEditionCodeFactory(send, publicClient);
|
|
487
|
+
notify({
|
|
488
|
+
anchor: 'edition-code-factory',
|
|
489
|
+
kind: 'deployed',
|
|
490
|
+
address: r.factory,
|
|
491
|
+
implementation: r.implementation,
|
|
492
|
+
metadataLib: r.metadataLib,
|
|
493
|
+
paramsLib: r.paramsLib,
|
|
494
|
+
codeLib: r.codeLib,
|
|
495
|
+
editionLib: r.editionLib,
|
|
496
|
+
txHash: r.txHash,
|
|
497
|
+
inManifest: false,
|
|
498
|
+
});
|
|
499
|
+
return r.factory;
|
|
500
|
+
}
|
|
501
|
+
// ── the canonical seed source ─────────────────────────────────────────────────
|
|
502
|
+
/** The canonical seed source — a clean keyless singleton (like the renderer / chunk store / minter):
|
|
503
|
+
* single-profile, so CREATE2 lands at THE canonical address on any chain. Resolves from the manifest,
|
|
504
|
+
* self-heals to the predicted address (deployed but not yet repointed), else auto-deploys via CREATE2. */
|
|
505
|
+
export async function ensureSeedSource(publicClient, send, opts) {
|
|
506
|
+
const notify = opts.onEvent ?? (() => { });
|
|
507
|
+
const known = resolveSeedSource(opts.chainId);
|
|
508
|
+
if (known) {
|
|
509
|
+
const code = await publicClient.getCode({ address: known });
|
|
510
|
+
if (code && code !== '0x') {
|
|
511
|
+
notify({ anchor: 'seed-source', kind: 'resolved', address: known });
|
|
512
|
+
return known;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const predicted = predictSeedSource();
|
|
516
|
+
if (!known || known.toLowerCase() !== predicted.toLowerCase()) {
|
|
517
|
+
const pcode = await publicClient.getCode({ address: predicted });
|
|
518
|
+
if (pcode && pcode !== '0x') {
|
|
519
|
+
notify({ anchor: 'seed-source', kind: 'canonical', address: predicted });
|
|
520
|
+
return predicted;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const { seedSource, txHash } = await deploySeedSource(send, publicClient);
|
|
524
|
+
notify({ anchor: 'seed-source', kind: 'deployed', address: seedSource, txHash, inManifest: false });
|
|
525
|
+
return seedSource;
|
|
526
|
+
}
|
|
527
|
+
// ── protocol reads: which factory, and from when ──────────────────────────────
|
|
528
|
+
/**
|
|
529
|
+
* **The** enumeration of a chain's trust anchors: the three ERC-721 factories (1/1, Series,
|
|
530
|
+
* SeriesCode) and the three ERC-1155 edition twins (OneOfOneEdition, EditionImage, EditionCode).
|
|
531
|
+
* Deduplicated, env overrides honoured, missing entries dropped.
|
|
532
|
+
*
|
|
533
|
+
* Exported because it is the set a platform allowlists, and because it existed twice inside this
|
|
534
|
+
* file — once in {@link detectCanonicalFactory} and once in {@link verifyCanonical} — which is one
|
|
535
|
+
* copy too many for a list whose completeness IS the trust model. When the edition anchors were
|
|
536
|
+
* missing from one of those copies, every canonical edition read `canonical: NO`: not "unverified",
|
|
537
|
+
* a confident wrong answer on the one signal platforms gate on. **Any new token family is added
|
|
538
|
+
* here, once.**
|
|
539
|
+
*/
|
|
540
|
+
export function canonicalFactories(chainId, opts = {}) {
|
|
541
|
+
if (opts.override)
|
|
542
|
+
return [opts.override];
|
|
543
|
+
return [
|
|
544
|
+
...new Set([
|
|
545
|
+
resolveSeriesCodeFactory(chainId),
|
|
546
|
+
resolveSeriesFactory(chainId),
|
|
547
|
+
resolveFactory(chainId),
|
|
548
|
+
resolveEditionCodeFactory(chainId),
|
|
549
|
+
resolveEditionFactory(chainId),
|
|
550
|
+
resolveOneOfOneEditionFactory(chainId),
|
|
551
|
+
].filter((f) => !!f)),
|
|
552
|
+
];
|
|
553
|
+
}
|
|
554
|
+
/** Ask a set of factories, in one multicall, which (if any) claims `address` as its clone.
|
|
555
|
+
* Returns the winner plus how many anchors actually ANSWERED — the number that separates a
|
|
556
|
+
* coverage gap from evidence, and the reason a caller can distinguish `false` from `null`. */
|
|
557
|
+
async function askAnchors(publicClient, address, candidates) {
|
|
558
|
+
if (!candidates.length)
|
|
559
|
+
return { answered: 0 };
|
|
560
|
+
try {
|
|
561
|
+
const results = (await publicClient.multicall({
|
|
562
|
+
contracts: candidates.map((f) => ({
|
|
563
|
+
address: f,
|
|
564
|
+
abi: oneOfOneImageFactoryAbi,
|
|
565
|
+
functionName: 'isAbxClone',
|
|
566
|
+
args: [address],
|
|
567
|
+
})),
|
|
568
|
+
allowFailure: true,
|
|
569
|
+
}));
|
|
570
|
+
const answered = results.filter((r) => r?.status === 'success').length;
|
|
571
|
+
const hit = candidates.find((_, i) => results[i]?.status === 'success' && results[i].result === true);
|
|
572
|
+
return { hit, answered };
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
return { answered: 0 }; // RPC hiccup — no anchor answered, which is an unknown, never a "no"
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Pick the canonical factory for a clone being added. An explicit override or a stored value
|
|
580
|
+
* wins; otherwise probe ALL SIX of the chain's known factories — the three 721 anchors (SeriesCode,
|
|
581
|
+
* Series, 1/1) and the three ERC-1155 edition anchors (EditionCode, EditionImage, OneOfOneEdition) —
|
|
582
|
+
* for the ONE whose `isAbxClone` confirms this address, so `abx add` of any project records the RIGHT
|
|
583
|
+
* factory and canonicity actually verifies. Without this, `add` defaulted to the 1/1 factory, so a
|
|
584
|
+
* code/Series clone read `canonical: unverified` (the check ran against the wrong factory). One
|
|
585
|
+
* multicall, only when the factory isn't already known; falls back to the 1/1 default on any miss/RPC
|
|
586
|
+
* error (`isAbxClone` shares a selector across all factory ABIs, so one ABI probes them all).
|
|
587
|
+
*
|
|
588
|
+
* The edition anchors were missing here when the 1155 lane shipped, and the failure was worse than
|
|
589
|
+
* "unverified": the 1/1 fallback answers `isAbxClone` = **false**, so every genuinely-canonical
|
|
590
|
+
* edition read `canonical: NO` — a confident wrong answer about the trust anchor, on the one signal
|
|
591
|
+
* platforms allowlist against. Any new token family MUST be added to this list in the same change.
|
|
592
|
+
*/
|
|
593
|
+
export async function detectCanonicalFactory(publicClient, address, opts) {
|
|
594
|
+
if (opts.override)
|
|
595
|
+
return opts.override;
|
|
596
|
+
if (opts.stored)
|
|
597
|
+
return opts.stored;
|
|
598
|
+
const candidates = canonicalFactories(opts.chainId);
|
|
599
|
+
if (candidates.length <= 1)
|
|
600
|
+
return candidates[0]; // nothing to disambiguate
|
|
601
|
+
// An RPC hiccup falls through to the default, so canonicity shows unverified — never wrong.
|
|
602
|
+
const { hit } = await askAnchors(publicClient, address, candidates);
|
|
603
|
+
return hit ?? resolveFactory(opts.chainId) ?? undefined;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Is this address a clone of one of the chain's trust anchors? A tri-state, read straight from chain
|
|
607
|
+
* — no projection, no indexer, no prior `abx add`.
|
|
608
|
+
*
|
|
609
|
+
* `true` = some anchor's `isAbxClone` confirms it. `false` = every anchor we know answered and none
|
|
610
|
+
* claimed it: a superseded factory, or a contract deployed outside the toolkit entirely. `null` = the
|
|
611
|
+
* check could not run (no anchors configured for this chain, or the RPC failed) — which is NOT the
|
|
612
|
+
* same answer and must never be shown as one.
|
|
613
|
+
*
|
|
614
|
+
* Exists because `abx state` — the command anyone reaches for to ask "what IS this contract?" — read
|
|
615
|
+
* owner, supply, royalty and renderer off any ERC-721-shaped address and said nothing about whether it
|
|
616
|
+
* was canonical. An agent that built its own contracts instead of using the factory therefore left
|
|
617
|
+
* nothing behind that the creator could discover, from the one tool whose job is to tell them.
|
|
618
|
+
* Canonicity is the single signal platforms allowlist against, and it cannot be added later.
|
|
619
|
+
*/
|
|
620
|
+
export async function verifyCanonical(publicClient, address, chainId, opts = {}) {
|
|
621
|
+
// `opts.factories` REPLACES the manifest set; it never appends to it. That direction is the whole
|
|
622
|
+
// point for a multi-tenant host: an operator who pinned a trust set expects a gate to be exactly
|
|
623
|
+
// as permissive as they asked, and appending ours would silently widen it.
|
|
624
|
+
const candidates = opts.factories ?? canonicalFactories(chainId);
|
|
625
|
+
if (!candidates.length)
|
|
626
|
+
return null; // nothing to check against — not a verdict
|
|
627
|
+
const { hit, answered } = await askAnchors(publicClient, address, candidates);
|
|
628
|
+
if (hit)
|
|
629
|
+
return true;
|
|
630
|
+
// Only a verdict if at least one anchor actually answered; all-failures is an unknown, not a "no".
|
|
631
|
+
return answered > 0 ? false : null;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Canonicity **plus provenance**: not just "is this ours" but "which generation of ours", so a
|
|
635
|
+
* consumer can tell *"deployed by an ABX factory that has since been replaced"* from *"deployed
|
|
636
|
+
* outside the toolkit entirely"*. Today those are the same `false` from {@link verifyCanonical} —
|
|
637
|
+
* which its own docstring admits ("a superseded factory, **or** a contract deployed outside the
|
|
638
|
+
* toolkit") — and they are very different things to show a collector.
|
|
639
|
+
*
|
|
640
|
+
* Checks the live anchors first, then each retired generation in {@link ANCHOR_GENERATIONS}. A
|
|
641
|
+
* retired generation NEVER makes `canonical` mean "trusted": it is labelled `generation: 'prior'`
|
|
642
|
+
* precisely so a caller has to decide, and `verifyCanonical` — the gate — does not consult these
|
|
643
|
+
* anchors at all.
|
|
644
|
+
*
|
|
645
|
+
* Prior testnet generations are not backfilled, so today this answers `'current'` or nothing. From the
|
|
646
|
+
* first batch that retires an anchor set, the answer exists rather than needing a protocol change at
|
|
647
|
+
* the moment it is first asked for.
|
|
648
|
+
*/
|
|
649
|
+
export async function verifyProvenance(publicClient, address, chainId, opts = {}) {
|
|
650
|
+
const live = opts.factories ?? canonicalFactories(chainId);
|
|
651
|
+
// Generation addresses are cross-chain deterministic, but that does not mean ABX deployed them
|
|
652
|
+
// on every EVM chain. The live manifest is the chain-coverage boundary; without it, probing prior
|
|
653
|
+
// addresses on an unshipped chain can turn "nothing configured" into a false verdict.
|
|
654
|
+
if (!opts.factories && live.length === 0) {
|
|
655
|
+
return {
|
|
656
|
+
canonical: null,
|
|
657
|
+
generation: null,
|
|
658
|
+
generationId: null,
|
|
659
|
+
generationLifecycle: null,
|
|
660
|
+
support: null,
|
|
661
|
+
coreVersion: null,
|
|
662
|
+
anchorsAnswered: 0,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const current = await askAnchors(publicClient, address, live);
|
|
666
|
+
if (current.hit) {
|
|
667
|
+
// A caller-supplied trust set may contain non-ABX factories. It can establish trust for that
|
|
668
|
+
// caller, but it cannot manufacture ABX generation provenance.
|
|
669
|
+
const generation = opts.factories
|
|
670
|
+
? findAnchorGenerationByFactory(current.hit)
|
|
671
|
+
: currentAnchorGeneration();
|
|
672
|
+
return {
|
|
673
|
+
canonical: true,
|
|
674
|
+
generation: generation ? (generation.lifecycle === 'current' ? 'current' : 'prior') : null,
|
|
675
|
+
generationId: generation?.id ?? null,
|
|
676
|
+
generationLifecycle: generation?.lifecycle ?? null,
|
|
677
|
+
support: generation?.support ?? null,
|
|
678
|
+
coreVersion: generation?.coreVersion ?? null,
|
|
679
|
+
factory: current.hit,
|
|
680
|
+
anchorsAnswered: current.answered,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
// An explicit trust set is exactly that: don't quietly widen the search to prior generations.
|
|
684
|
+
const prior = opts.factories ? [] : ANCHOR_GENERATIONS.filter((g) => g.lifecycle !== 'current');
|
|
685
|
+
let answered = current.answered;
|
|
686
|
+
for (const gen of prior) {
|
|
687
|
+
const res = await askAnchors(publicClient, address, Object.values(gen.factories));
|
|
688
|
+
answered += res.answered;
|
|
689
|
+
if (res.hit) {
|
|
690
|
+
return {
|
|
691
|
+
canonical: true,
|
|
692
|
+
generation: 'prior',
|
|
693
|
+
generationId: gen.id,
|
|
694
|
+
generationLifecycle: gen.lifecycle,
|
|
695
|
+
support: gen.support,
|
|
696
|
+
coreVersion: gen.coreVersion,
|
|
697
|
+
factory: res.hit,
|
|
698
|
+
anchorsAnswered: answered,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
canonical: answered > 0 ? false : null,
|
|
704
|
+
generation: null,
|
|
705
|
+
generationId: null,
|
|
706
|
+
generationLifecycle: null,
|
|
707
|
+
support: null,
|
|
708
|
+
coreVersion: null,
|
|
709
|
+
anchorsAnswered: answered,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Resolve the scan floor (fromBlock) for an add/index: an explicit override wins, else the deploy
|
|
714
|
+
* block the local deploy already stored, else discover it from chain (getCode binary search). It
|
|
715
|
+
* NEVER silently defaults to genesis — a from-0 full scan of a live chain must be an explicit
|
|
716
|
+
* opt-in, because on a range-capped RPC it's the difference between an instant index and grinding
|
|
717
|
+
* millions of blocks (the #1 reason a hosted resolver appears "not to index"). Returns a decimal
|
|
718
|
+
* block string. Only touches RPC in the discovery fallback (rare — a foreign contract we didn't
|
|
719
|
+
* deploy here), so the common post-deploy path makes no extra call.
|
|
720
|
+
*/
|
|
721
|
+
export async function resolveScanFloor(publicClient, address, opts = {}) {
|
|
722
|
+
const notify = opts.onEvent ?? (() => { });
|
|
723
|
+
if (opts.explicit !== undefined) {
|
|
724
|
+
if (/^0+$/.test(opts.explicit.trim()))
|
|
725
|
+
notify({ kind: 'genesis-warning' });
|
|
726
|
+
return opts.explicit;
|
|
727
|
+
}
|
|
728
|
+
if (opts.localFromBlock !== undefined)
|
|
729
|
+
return opts.localFromBlock; // the deploy stored the exact block
|
|
730
|
+
const discovered = await discoverDeployBlock(publicClient, address);
|
|
731
|
+
if (discovered !== null) {
|
|
732
|
+
notify({ kind: 'discovered', block: discovered });
|
|
733
|
+
return discovered.toString();
|
|
734
|
+
}
|
|
735
|
+
throw new Error(`couldn't determine ${address}'s deploy block (no local record, and on-chain discovery failed). ` +
|
|
736
|
+
`That can be a non-archive RPC, a rate-limited endpoint, or a node that refuses historical getCode — ` +
|
|
737
|
+
`not a diagnosis \`abx doctor\` already settled. Pass --from-block <deployBlock> so indexing starts there ` +
|
|
738
|
+
`instead of sweeping from genesis. (--from-block 0 forces a full-chain scan if you really want it.)`);
|
|
739
|
+
}
|
|
740
|
+
//# sourceMappingURL=anchors.js.map
|