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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +130 -0
  3. package/dist/abi/generated.d.ts +8502 -129
  4. package/dist/abi/generated.d.ts.map +1 -1
  5. package/dist/abi/generated.js +40 -22
  6. package/dist/abi/generated.js.map +1 -1
  7. package/dist/abi/index.d.ts +2645 -168
  8. package/dist/abi/index.d.ts.map +1 -1
  9. package/dist/abi/index.js +34 -7
  10. package/dist/abi/index.js.map +1 -1
  11. package/dist/anchors.d.ts +325 -0
  12. package/dist/anchors.d.ts.map +1 -0
  13. package/dist/anchors.js +740 -0
  14. package/dist/anchors.js.map +1 -0
  15. package/dist/chain-support.d.ts +28 -0
  16. package/dist/chain-support.d.ts.map +1 -0
  17. package/dist/chain-support.js +65 -0
  18. package/dist/chain-support.js.map +1 -0
  19. package/dist/chain-support.json +60 -0
  20. package/dist/chains.d.ts +24 -10
  21. package/dist/chains.d.ts.map +1 -1
  22. package/dist/chains.js +51 -17
  23. package/dist/chains.js.map +1 -1
  24. package/dist/chunks.d.ts +162 -31
  25. package/dist/chunks.d.ts.map +1 -1
  26. package/dist/chunks.js +179 -14
  27. package/dist/chunks.js.map +1 -1
  28. package/dist/clients.d.ts +23 -11
  29. package/dist/clients.d.ts.map +1 -1
  30. package/dist/clients.js +17 -18
  31. package/dist/clients.js.map +1 -1
  32. package/dist/contract-generations.d.ts +58 -0
  33. package/dist/contract-generations.d.ts.map +1 -0
  34. package/dist/contract-generations.js +109 -0
  35. package/dist/contract-generations.js.map +1 -0
  36. package/dist/contract-generations.json +48 -0
  37. package/dist/create2.d.ts +84 -1
  38. package/dist/create2.d.ts.map +1 -1
  39. package/dist/create2.js +158 -2
  40. package/dist/create2.js.map +1 -1
  41. package/dist/creator-token.d.ts +132 -0
  42. package/dist/creator-token.d.ts.map +1 -0
  43. package/dist/creator-token.js +183 -0
  44. package/dist/creator-token.js.map +1 -0
  45. package/dist/deploy.d.ts +234 -14
  46. package/dist/deploy.d.ts.map +1 -1
  47. package/dist/deploy.js +289 -111
  48. package/dist/deploy.js.map +1 -1
  49. package/dist/deployments.d.ts +38 -5
  50. package/dist/deployments.d.ts.map +1 -1
  51. package/dist/deployments.js +157 -30
  52. package/dist/deployments.js.map +1 -1
  53. package/dist/deps.d.ts +123 -6
  54. package/dist/deps.d.ts.map +1 -1
  55. package/dist/deps.js +249 -7
  56. package/dist/deps.js.map +1 -1
  57. package/dist/env.d.ts +5 -4
  58. package/dist/env.d.ts.map +1 -1
  59. package/dist/env.js +20 -5
  60. package/dist/env.js.map +1 -1
  61. package/dist/errors.d.ts +109 -0
  62. package/dist/errors.d.ts.map +1 -0
  63. package/dist/errors.js +148 -0
  64. package/dist/errors.js.map +1 -0
  65. package/dist/execute.d.ts +118 -0
  66. package/dist/execute.d.ts.map +1 -0
  67. package/dist/execute.js +159 -0
  68. package/dist/execute.js.map +1 -0
  69. package/dist/gateways.d.ts +79 -0
  70. package/dist/gateways.d.ts.map +1 -0
  71. package/dist/gateways.js +156 -0
  72. package/dist/gateways.js.map +1 -0
  73. package/dist/generator-document.d.ts +57 -0
  74. package/dist/generator-document.d.ts.map +1 -0
  75. package/dist/generator-document.js +123 -0
  76. package/dist/generator-document.js.map +1 -0
  77. package/dist/generator.d.ts +44 -2
  78. package/dist/generator.d.ts.map +1 -1
  79. package/dist/generator.js +29 -0
  80. package/dist/generator.js.map +1 -1
  81. package/dist/index.d.ts +19 -3
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +19 -3
  84. package/dist/index.js.map +1 -1
  85. package/dist/inspect.d.ts +48 -0
  86. package/dist/inspect.d.ts.map +1 -0
  87. package/dist/inspect.js +295 -0
  88. package/dist/inspect.js.map +1 -0
  89. package/dist/migrate.d.ts +41 -0
  90. package/dist/migrate.d.ts.map +1 -0
  91. package/dist/migrate.js +142 -0
  92. package/dist/migrate.js.map +1 -0
  93. package/dist/mime.d.ts +11 -0
  94. package/dist/mime.d.ts.map +1 -0
  95. package/dist/mime.js +40 -0
  96. package/dist/mime.js.map +1 -0
  97. package/dist/node.d.ts +69 -0
  98. package/dist/node.d.ts.map +1 -0
  99. package/dist/node.js +92 -0
  100. package/dist/node.js.map +1 -0
  101. package/dist/onchain-uri.d.ts +110 -0
  102. package/dist/onchain-uri.d.ts.map +1 -0
  103. package/dist/onchain-uri.js +276 -0
  104. package/dist/onchain-uri.js.map +1 -0
  105. package/dist/ops.d.ts +507 -14
  106. package/dist/ops.d.ts.map +1 -1
  107. package/dist/ops.js +850 -34
  108. package/dist/ops.js.map +1 -1
  109. package/dist/policy.d.ts +57 -0
  110. package/dist/policy.d.ts.map +1 -0
  111. package/dist/policy.js +35 -0
  112. package/dist/policy.js.map +1 -0
  113. package/dist/probe.d.ts +58 -0
  114. package/dist/probe.d.ts.map +1 -1
  115. package/dist/probe.js +190 -9
  116. package/dist/probe.js.map +1 -1
  117. package/dist/reconstruct.d.ts +141 -3
  118. package/dist/reconstruct.d.ts.map +1 -1
  119. package/dist/reconstruct.js +479 -100
  120. package/dist/reconstruct.js.map +1 -1
  121. package/dist/resume.d.ts +147 -0
  122. package/dist/resume.d.ts.map +1 -0
  123. package/dist/resume.js +146 -0
  124. package/dist/resume.js.map +1 -0
  125. package/dist/script-chunks.d.ts +105 -0
  126. package/dist/script-chunks.d.ts.map +1 -0
  127. package/dist/script-chunks.js +158 -0
  128. package/dist/script-chunks.js.map +1 -0
  129. package/dist/service.d.ts +155 -9
  130. package/dist/service.d.ts.map +1 -1
  131. package/dist/service.js +103 -11
  132. package/dist/service.js.map +1 -1
  133. package/dist/spine.d.ts +47 -4
  134. package/dist/spine.d.ts.map +1 -1
  135. package/dist/spine.js +0 -0
  136. package/dist/spine.js.map +1 -1
  137. package/dist/staging.d.ts +139 -0
  138. package/dist/staging.d.ts.map +1 -0
  139. package/dist/staging.js +139 -0
  140. package/dist/staging.js.map +1 -0
  141. package/dist/token.d.ts +11 -1
  142. package/dist/token.d.ts.map +1 -1
  143. package/dist/token.js +32 -1
  144. package/dist/token.js.map +1 -1
  145. package/dist/tokendata.d.ts +63 -3
  146. package/dist/tokendata.d.ts.map +1 -1
  147. package/dist/tokendata.js +81 -19
  148. package/dist/tokendata.js.map +1 -1
  149. package/dist/tokens.d.ts +119 -0
  150. package/dist/tokens.d.ts.map +1 -0
  151. package/dist/tokens.js +317 -0
  152. package/dist/tokens.js.map +1 -0
  153. package/dist/types.d.ts +107 -5
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/util.d.ts +74 -0
  156. package/dist/util.d.ts.map +1 -0
  157. package/dist/util.js +106 -0
  158. package/dist/util.js.map +1 -0
  159. package/package.json +15 -6
@@ -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