@artblocks/abx-sdk 0.1.0-alpha.15 → 0.1.0-alpha.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abi/generated.d.ts.map +1 -1
- package/dist/abi/generated.js +1 -1
- package/dist/abi/generated.js.map +1 -1
- package/dist/anchors.d.ts +7 -1
- package/dist/anchors.d.ts.map +1 -1
- package/dist/anchors.js +8 -2
- package/dist/anchors.js.map +1 -1
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +28 -6
- package/dist/deployments.js.map +1 -1
- package/dist/gateways.d.ts +47 -0
- package/dist/gateways.d.ts.map +1 -1
- package/dist/gateways.js +99 -0
- package/dist/gateways.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +8 -3
- package/dist/inspect.js.map +1 -1
- package/dist/ops.d.ts +1 -1
- package/dist/ops.js +1 -1
- package/dist/probe.d.ts +29 -0
- package/dist/probe.d.ts.map +1 -1
- package/dist/probe.js +104 -9
- package/dist/probe.js.map +1 -1
- package/dist/reconstruct.d.ts +44 -1
- package/dist/reconstruct.d.ts.map +1 -1
- package/dist/reconstruct.js +88 -42
- package/dist/reconstruct.js.map +1 -1
- package/dist/script-chunks.d.ts +24 -0
- package/dist/script-chunks.d.ts.map +1 -0
- package/dist/script-chunks.js +63 -0
- package/dist/script-chunks.js.map +1 -0
- package/dist/spine.d.ts +27 -0
- package/dist/spine.d.ts.map +1 -1
- package/dist/spine.js +0 -0
- package/dist/spine.js.map +1 -1
- package/package.json +1 -1
package/dist/ops.d.ts
CHANGED
|
@@ -698,7 +698,7 @@ export declare function prepareLockScript(args: {
|
|
|
698
698
|
}): PreparedTx;
|
|
699
699
|
/** Set a CONTRACT-scope param to a literal `bytes32` (the raw owner setter — Params base).
|
|
700
700
|
* Only for schema-less keys (a schema'd key closes the raw path: `SchemaGoverned`). This is
|
|
701
|
-
* the write behind well-known contract params like `display.
|
|
701
|
+
* the write behind well-known contract params like `display.animation`. The key joins the
|
|
702
702
|
* contract's on-chain key list automatically (`contractParamKeys`), so it is enumerated by
|
|
703
703
|
* the generator and the renderer with no separate declaration. Signer must be the owner. */
|
|
704
704
|
export declare function prepareSetContractParam(args: {
|
package/dist/ops.js
CHANGED
|
@@ -1343,7 +1343,7 @@ export function prepareLockScript(args) {
|
|
|
1343
1343
|
}
|
|
1344
1344
|
/** Set a CONTRACT-scope param to a literal `bytes32` (the raw owner setter — Params base).
|
|
1345
1345
|
* Only for schema-less keys (a schema'd key closes the raw path: `SchemaGoverned`). This is
|
|
1346
|
-
* the write behind well-known contract params like `display.
|
|
1346
|
+
* the write behind well-known contract params like `display.animation`. The key joins the
|
|
1347
1347
|
* contract's on-chain key list automatically (`contractParamKeys`), so it is enumerated by
|
|
1348
1348
|
* the generator and the renderer with no separate declaration. Signer must be the owner. */
|
|
1349
1349
|
export function prepareSetContractParam(args) {
|
package/dist/probe.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PublicClient } from 'viem';
|
|
1
2
|
/**
|
|
2
3
|
* Capability probe for RPC endpoints. Reconstruction is `eth_getLogs`-heavy and
|
|
3
4
|
* needs *archive* depth (to read a project's deploy block) plus a usable block
|
|
@@ -27,4 +28,32 @@ export declare function probeRpcEndpoints(opts?: {
|
|
|
27
28
|
chainKey?: string;
|
|
28
29
|
urls?: string[];
|
|
29
30
|
}): Promise<RpcProbe[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Can this endpoint still serve LOG HISTORY at `blockNumber`?
|
|
33
|
+
*
|
|
34
|
+
* The check this replaced was `getLogs({address: zeroAddress, fromBlock: old, toBlock: old})` and
|
|
35
|
+
* "any non-throw counts as archive access" — which cannot fail on a pruning node. `zeroAddress`
|
|
36
|
+
* never emits logs, so an empty result is what a *perfect* archive node returns too: the assertion
|
|
37
|
+
* had no discriminating power and only caught endpoints that *error* on old blocks. A node that
|
|
38
|
+
* silently answers `[]` for pruned history was graded `best`, and `abx add` then ground through
|
|
39
|
+
* half a million blocks and reconstructed nothing, because a successful empty response is not an
|
|
40
|
+
* error and never fails over to the next endpoint.
|
|
41
|
+
*
|
|
42
|
+
* So assert on something that MUST come back. Blocks are retained by everyone; receipts and the log
|
|
43
|
+
* index are what get pruned. We read the probe block, ask for one of its transactions' receipts, and
|
|
44
|
+
* — when that receipt carries a log — require `getLogs` to return that same log. Measured on two
|
|
45
|
+
* Base Sepolia endpoints at 500k blocks back, same transaction: one returns a receipt with 1 log,
|
|
46
|
+
* the other returns null. That is the discriminator, and it needs no hardcoded address or block.
|
|
47
|
+
*
|
|
48
|
+
* Fails SAFE, in the direction of the old behaviour: when the probe block has no transactions, or
|
|
49
|
+
* the receipt has no logs, there is nothing to assert on, so we fall back to the lenient
|
|
50
|
+
* "getLogs didn't throw" signal rather than manufacturing a failure.
|
|
51
|
+
*
|
|
52
|
+
* Exported for tests — `probeOne` builds its own transport per URL, so the discriminating logic is
|
|
53
|
+
* what gets asserted directly.
|
|
54
|
+
*/
|
|
55
|
+
export declare function probeHistoryAt(client: PublicClient, blockNumber: bigint): Promise<{
|
|
56
|
+
retained: boolean;
|
|
57
|
+
reason?: string;
|
|
58
|
+
}>;
|
|
30
59
|
//# sourceMappingURL=probe.d.ts.map
|
package/dist/probe.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6E,KAAK,YAAY,EAAC,MAAM,MAAM,CAAC;AAInH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAExD,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAsB,iBAAiB,CACrC,IAAI,GAAE;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAM,GAC9C,OAAO,CAAC,QAAQ,EAAE,CAAC,CAIrB;AAyDD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,YAAY,EACpB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAC,CAAC,CAgD/C"}
|
package/dist/probe.js
CHANGED
|
@@ -2,7 +2,17 @@ import { createPublicClient, http, zeroAddress } from 'viem';
|
|
|
2
2
|
import { resolveChain, resolveRpcUrls, redactRpcUrl } from './chains.js';
|
|
3
3
|
import { isGetLogsRangeError } from './reconstruct.js';
|
|
4
4
|
const RANGE_PROBE = 2000n; // "wide" recent range to test the getLogs window
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* How far back "archive depth" means. Deliberately deep: reconstruction starts at a project's
|
|
7
|
+
* DEPLOY block, so the bar is "can this endpoint still serve a project that is not new?". At 500k
|
|
8
|
+
* blocks that is ~11 days on Base Sepolia (2s blocks) and ~10 weeks on Ethereum Sepolia (12s) —
|
|
9
|
+
* comfortably past the retention window of the pruning public endpoints, and far short of any
|
|
10
|
+
* genuinely archival node. It was 100_000, which sat *inside* the retained window of at least one
|
|
11
|
+
* default endpoint, so the probe passed on a node that could not reconstruct anything older.
|
|
12
|
+
*/
|
|
13
|
+
const ARCHIVE_DEPTH = 500000n;
|
|
14
|
+
/** How many of a probe block's transactions to try a receipt for before giving up. */
|
|
15
|
+
const RECEIPT_TRIES = 3;
|
|
6
16
|
export async function probeRpcEndpoints(opts = {}) {
|
|
7
17
|
const chain = resolveChain(opts.chainKey);
|
|
8
18
|
const urls = opts.urls ?? resolveRpcUrls(opts.chainKey);
|
|
@@ -46,14 +56,10 @@ async function probeOne(chain, url) {
|
|
|
46
56
|
}
|
|
47
57
|
// archive depth — one block, deep in the past, to isolate archive from range
|
|
48
58
|
const old = head > ARCHIVE_DEPTH ? head - ARCHIVE_DEPTH : 0n;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
catch (err) {
|
|
54
|
-
if (!probe.reason)
|
|
55
|
-
probe.reason = firstLine(err);
|
|
56
|
-
}
|
|
59
|
+
const retention = await probeHistoryAt(client, old);
|
|
60
|
+
probe.archive = retention.retained;
|
|
61
|
+
if (!probe.archive && !probe.reason)
|
|
62
|
+
probe.reason = retention.reason;
|
|
57
63
|
if (!probe.archive) {
|
|
58
64
|
probe.verdict = 'unusable';
|
|
59
65
|
probe.reason = probe.reason ?? 'no archive access — can’t read a project’s deploy block';
|
|
@@ -68,6 +74,95 @@ async function probeOne(chain, url) {
|
|
|
68
74
|
}
|
|
69
75
|
return probe;
|
|
70
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Can this endpoint still serve LOG HISTORY at `blockNumber`?
|
|
79
|
+
*
|
|
80
|
+
* The check this replaced was `getLogs({address: zeroAddress, fromBlock: old, toBlock: old})` and
|
|
81
|
+
* "any non-throw counts as archive access" — which cannot fail on a pruning node. `zeroAddress`
|
|
82
|
+
* never emits logs, so an empty result is what a *perfect* archive node returns too: the assertion
|
|
83
|
+
* had no discriminating power and only caught endpoints that *error* on old blocks. A node that
|
|
84
|
+
* silently answers `[]` for pruned history was graded `best`, and `abx add` then ground through
|
|
85
|
+
* half a million blocks and reconstructed nothing, because a successful empty response is not an
|
|
86
|
+
* error and never fails over to the next endpoint.
|
|
87
|
+
*
|
|
88
|
+
* So assert on something that MUST come back. Blocks are retained by everyone; receipts and the log
|
|
89
|
+
* index are what get pruned. We read the probe block, ask for one of its transactions' receipts, and
|
|
90
|
+
* — when that receipt carries a log — require `getLogs` to return that same log. Measured on two
|
|
91
|
+
* Base Sepolia endpoints at 500k blocks back, same transaction: one returns a receipt with 1 log,
|
|
92
|
+
* the other returns null. That is the discriminator, and it needs no hardcoded address or block.
|
|
93
|
+
*
|
|
94
|
+
* Fails SAFE, in the direction of the old behaviour: when the probe block has no transactions, or
|
|
95
|
+
* the receipt has no logs, there is nothing to assert on, so we fall back to the lenient
|
|
96
|
+
* "getLogs didn't throw" signal rather than manufacturing a failure.
|
|
97
|
+
*
|
|
98
|
+
* Exported for tests — `probeOne` builds its own transport per URL, so the discriminating logic is
|
|
99
|
+
* what gets asserted directly.
|
|
100
|
+
*/
|
|
101
|
+
export async function probeHistoryAt(client, blockNumber) {
|
|
102
|
+
const depth = `~${ARCHIVE_DEPTH} blocks back`; // the FAULT only — each caller words its own remedy
|
|
103
|
+
let txHashes = [];
|
|
104
|
+
try {
|
|
105
|
+
const block = await client.getBlock({ blockNumber, includeTransactions: false });
|
|
106
|
+
txHashes = block.transactions.slice(0, RECEIPT_TRIES);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
return { retained: false, reason: `can't read a block ${depth} — ${firstLine(err)}` };
|
|
110
|
+
}
|
|
111
|
+
for (const hash of txHashes) {
|
|
112
|
+
let logAddress;
|
|
113
|
+
try {
|
|
114
|
+
const receipt = await client.getTransactionReceipt({ hash });
|
|
115
|
+
logAddress = receipt.logs[0]?.address;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
// Only a genuine "no such receipt" proves pruning. A rate-limit or a transport blip must NOT
|
|
119
|
+
// become a "history pruned" claim about an endpoint that is actually fine — so anything else
|
|
120
|
+
// falls through to the lenient check below, which is what this function did before.
|
|
121
|
+
if (!looksLikeReceiptMissing(err))
|
|
122
|
+
break;
|
|
123
|
+
// The node served the block but not the receipt of a transaction inside it: its history is
|
|
124
|
+
// pruned at this depth. This is exactly what a "recent logs only" endpoint looks like.
|
|
125
|
+
return {
|
|
126
|
+
retained: false,
|
|
127
|
+
reason: `history pruned ${depth} — it serves the block but not the receipts of transactions inside it`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (!logAddress)
|
|
131
|
+
continue; // nothing to assert on in this tx — try the next
|
|
132
|
+
try {
|
|
133
|
+
const logs = await client.getLogs({ address: logAddress, fromBlock: blockNumber, toBlock: blockNumber });
|
|
134
|
+
if (logs.length > 0)
|
|
135
|
+
return { retained: true };
|
|
136
|
+
return {
|
|
137
|
+
retained: false,
|
|
138
|
+
reason: `log index pruned ${depth} — eth_getLogs returns EMPTY for a block whose receipts still hold logs`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
return { retained: false, reason: `eth_getLogs failed ${depth} — ${firstLine(err)}` };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Inconclusive (no transactions at the probe block, or none of them logged anything): keep the
|
|
146
|
+
// old lenient signal rather than inventing a verdict.
|
|
147
|
+
try {
|
|
148
|
+
await client.getLogs({ address: zeroAddress, fromBlock: blockNumber, toBlock: blockNumber });
|
|
149
|
+
return { retained: true };
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
return { retained: false, reason: firstLine(err) };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Does this error mean "that receipt does not exist here" (viem's `TransactionReceiptNotFoundError`,
|
|
157
|
+
* i.e. the node answered `null`) as opposed to "I could not ask" (rate limit, timeout, 5xx)? Only the
|
|
158
|
+
* first is evidence about retention; treating the second as evidence would libel a healthy endpoint.
|
|
159
|
+
*/
|
|
160
|
+
function looksLikeReceiptMissing(err) {
|
|
161
|
+
const e = err;
|
|
162
|
+
if (e?.name === 'TransactionReceiptNotFoundError')
|
|
163
|
+
return true;
|
|
164
|
+
return /receipt.*(not be found|not found)|could not be found/i.test(e?.message ?? '');
|
|
165
|
+
}
|
|
71
166
|
function firstLine(err) {
|
|
72
167
|
return (err?.message ?? String(err)).split('\n')[0].slice(0, 100);
|
|
73
168
|
}
|
package/dist/probe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,kBAAkB,EAAE,IAAI,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"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"}
|
package/dist/reconstruct.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Address, type Hex, type PublicClient } from 'viem';
|
|
1
|
+
import { type Address, type Hex, type PublicClient, type Log } from 'viem';
|
|
2
2
|
import { AbxSdkError } from './errors.js';
|
|
3
3
|
import type { DependencyInfo, ExtensionInfo, MetadataField, ParamHooks, ParamSchema, ParamValue, ProjectState, SpineEvent, TokenState } from './types.js';
|
|
4
4
|
export interface ReconstructOptions {
|
|
@@ -46,6 +46,21 @@ export declare function reconstructIncremental(client: PublicClient, prior: Proj
|
|
|
46
46
|
getLogsRange?: bigint;
|
|
47
47
|
readUriDocuments?: boolean;
|
|
48
48
|
}): Promise<ProjectState>;
|
|
49
|
+
/**
|
|
50
|
+
* The fold / head-read half of {@link reconstructIncremental}, given logs the caller already
|
|
51
|
+
* fetched. A multi-address watch loop (`getLogs({address: Address[]})`) holds each touched
|
|
52
|
+
* project's delta; this consumes it so that loop is the only `eth_getLogs`. Folding stays here.
|
|
53
|
+
*
|
|
54
|
+
* `toBlock` is the inclusive scan head those logs cover — required even when `logs` is empty,
|
|
55
|
+
* so a quiet window still advances the checkpoint to the range the caller scanned, and so a
|
|
56
|
+
* caller that scanned a capped window (not chain head) cannot stamp `toBlock` past the last
|
|
57
|
+
* log they actually held.
|
|
58
|
+
*/
|
|
59
|
+
export declare function reconstructFromLogs(client: PublicClient, prior: ProjectState, logs: readonly Log[], opts: {
|
|
60
|
+
factory?: Address;
|
|
61
|
+
readUriDocuments?: boolean;
|
|
62
|
+
toBlock: bigint;
|
|
63
|
+
}): Promise<ProjectState>;
|
|
49
64
|
/** The structural projection folded from the spine (everything that isn't a head read).
|
|
50
65
|
* Exported for tests and advanced consumers; the shape may evolve with the spine. */
|
|
51
66
|
export interface Fold {
|
|
@@ -113,6 +128,34 @@ export declare class GetLogsScanTooLargeError extends AbxSdkError {
|
|
|
113
128
|
* contract has no code at block 0 (true for every ABX clone — deployed long after genesis).
|
|
114
129
|
*/
|
|
115
130
|
export declare function discoverDeployBlock(client: PublicClient, address: Address, toBlock?: bigint): Promise<bigint | null>;
|
|
131
|
+
/**
|
|
132
|
+
* Ordinary RPC tiers cap how many addresses one `eth_getLogs` filter may carry.
|
|
133
|
+
* This is an RPC-tier opinion, not a protocol constant — callers with a dedicated
|
|
134
|
+
* node can raise it; `0` disables splitting (one filter for the whole list).
|
|
135
|
+
*/
|
|
136
|
+
export declare const DEFAULT_GETLOGS_ADDRESS_BATCH = 1000;
|
|
137
|
+
export interface GetLogsAdaptiveOptions {
|
|
138
|
+
maxChunks?: number;
|
|
139
|
+
range?: bigint;
|
|
140
|
+
/** Split `address[]` filters into this many addresses per request. Default
|
|
141
|
+
* {@link DEFAULT_GETLOGS_ADDRESS_BATCH}. `0` = no split. */
|
|
142
|
+
addressBatch?: number;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Pull logs over a block range, adapting to RPC range caps — and, when `address` is
|
|
146
|
+
* a list, splitting that list so a registered-set watch scan stays one code path for
|
|
147
|
+
* self-host and hosted. Providers cap `eth_getLogs` very differently and the numbers
|
|
148
|
+
* change, so we don't hard-code a window: try the whole range, and when the RPC
|
|
149
|
+
* rejects it, halve the window and sweep. `ABX_GETLOGS_RANGE` overrides the starting
|
|
150
|
+
* window. Address-list chunking is a separate cap (`addressBatch`); 5000-block
|
|
151
|
+
* per-tick windows belong to the caller (the watcher), not here.
|
|
152
|
+
*
|
|
153
|
+
* Once the working window is known we estimate the whole job *before* grinding it: if
|
|
154
|
+
* it would exceed the cap, we stop early (only the probe calls spent) and throw
|
|
155
|
+
* {@link GetLogsScanTooLargeError} so the caller can offer the real fix (a higher-range
|
|
156
|
+
* RPC) or an explicit opt-in to chunk through it.
|
|
157
|
+
*/
|
|
158
|
+
export declare function getLogsAdaptive(client: PublicClient, address: Address | readonly Address[], fromBlock: bigint, toBlock: bigint, opts?: GetLogsAdaptiveOptions): Promise<Log[]>;
|
|
116
159
|
/**
|
|
117
160
|
* Multicall in chunks, tolerating per-leg failure — and, when a whole chunk comes back failed,
|
|
118
161
|
* retrying its legs individually before believing it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconstruct.d.ts","sourceRoot":"","sources":["../src/reconstruct.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,GAAG,EACR,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"reconstruct.d.ts","sourceRoot":"","sources":["../src/reconstruct.ts"],"names":[],"mappings":"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"}
|
package/dist/reconstruct.js
CHANGED
|
@@ -38,14 +38,29 @@ export async function reconstructIncremental(client, prior, opts = {}) {
|
|
|
38
38
|
const priorTo = BigInt(prior.toBlock);
|
|
39
39
|
const origin = BigInt(prior.deployBlock ?? prior.fromBlock);
|
|
40
40
|
const resumeFrom = priorTo + 1n > origin ? priorTo + 1n : origin;
|
|
41
|
-
let
|
|
41
|
+
let rawLogs = [];
|
|
42
42
|
if (head >= resumeFrom) {
|
|
43
|
-
|
|
43
|
+
rawLogs = await getLogsAdaptive(client, address, resumeFrom, head, {
|
|
44
44
|
maxChunks: opts.maxChunks,
|
|
45
45
|
range: opts.getLogsRange,
|
|
46
46
|
});
|
|
47
|
-
fresh = decodedToSpineEvents(parseEventLogs({ abi: spineEventAbi, logs: rawLogs }));
|
|
48
47
|
}
|
|
48
|
+
const toBlock = head >= priorTo ? head : priorTo;
|
|
49
|
+
return reconstructFromLogs(client, prior, rawLogs, { factory, readUriDocuments: opts.readUriDocuments, toBlock });
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The fold / head-read half of {@link reconstructIncremental}, given logs the caller already
|
|
53
|
+
* fetched. A multi-address watch loop (`getLogs({address: Address[]})`) holds each touched
|
|
54
|
+
* project's delta; this consumes it so that loop is the only `eth_getLogs`. Folding stays here.
|
|
55
|
+
*
|
|
56
|
+
* `toBlock` is the inclusive scan head those logs cover — required even when `logs` is empty,
|
|
57
|
+
* so a quiet window still advances the checkpoint to the range the caller scanned, and so a
|
|
58
|
+
* caller that scanned a capped window (not chain head) cannot stamp `toBlock` past the last
|
|
59
|
+
* log they actually held.
|
|
60
|
+
*/
|
|
61
|
+
export async function reconstructFromLogs(client, prior, logs, opts) {
|
|
62
|
+
const factory = opts.factory ?? prior.factory ?? undefined;
|
|
63
|
+
const fresh = decodedToSpineEvents(parseEventLogs({ abi: spineEventAbi, logs: logs }));
|
|
49
64
|
// Merge the prior log with the freshly-fetched tail, de-duping by position so an
|
|
50
65
|
// overlapping/duplicate re-run can't double-count, then fold the complete spine.
|
|
51
66
|
const seen = new Set();
|
|
@@ -58,8 +73,13 @@ export async function reconstructIncremental(client, prior, opts = {}) {
|
|
|
58
73
|
all.push(ev);
|
|
59
74
|
}
|
|
60
75
|
all.sort(byOrder);
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
// Chunk content has no log (too large); `ScriptUpdated` is the ping. A Transfer currently
|
|
77
|
+
// used to re-download every chunk. Keep the prior digest unless that ping is in `fresh`.
|
|
78
|
+
const scriptTouched = fresh.some((e) => e.name === 'ScriptUpdated');
|
|
79
|
+
const reuseScript = !scriptTouched && prior.script?.digest != null ? prior.script : undefined;
|
|
80
|
+
const priorTo = BigInt(prior.toBlock);
|
|
81
|
+
const toBlock = opts.toBlock >= priorTo ? opts.toBlock : priorTo;
|
|
82
|
+
return assembleState(client, prior.address, all, foldSpine(all), BigInt(prior.fromBlock), toBlock, factory, !!opts.readUriDocuments, reuseScript);
|
|
63
83
|
}
|
|
64
84
|
/** Turn decoded viem logs into the canonical, serializable {@link SpineEvent} list. */
|
|
65
85
|
function decodedToSpineEvents(decoded) {
|
|
@@ -365,7 +385,7 @@ export function foldSpine(events) {
|
|
|
365
385
|
return fold;
|
|
366
386
|
}
|
|
367
387
|
/** Build the full {@link ProjectState} from a fold + provenance, then overlay head reads. */
|
|
368
|
-
async function assembleState(client, address, events, fold, fromBlock, toBlock, factory, readUriDocuments = false) {
|
|
388
|
+
async function assembleState(client, address, events, fold, fromBlock, toBlock, factory, readUriDocuments = false, reuseScript) {
|
|
369
389
|
// ensure a TokenState exists for every token referenced by on-chain fields/locks/params
|
|
370
390
|
// (a deployed-but-unminted token can carry fields before any Transfer)
|
|
371
391
|
for (const id of fold.tokenFields.keys())
|
|
@@ -477,7 +497,7 @@ async function assembleState(client, address, events, fold, fromBlock, toBlock,
|
|
|
477
497
|
reconstructedAt: new Date().toISOString(),
|
|
478
498
|
rpcUrl: client.transport?.url,
|
|
479
499
|
};
|
|
480
|
-
await applyHeadReads(client, state, factory, readUriDocuments);
|
|
500
|
+
await applyHeadReads(client, state, factory, readUriDocuments, reuseScript);
|
|
481
501
|
return state;
|
|
482
502
|
}
|
|
483
503
|
/** Default safety cap on chunk count; raise via the `maxChunks` opt (or ABX_GETLOGS_MAX_CHUNKS —
|
|
@@ -550,49 +570,68 @@ export async function discoverDeployBlock(client, address, toBlock) {
|
|
|
550
570
|
}
|
|
551
571
|
}
|
|
552
572
|
/**
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
*
|
|
573
|
+
* Ordinary RPC tiers cap how many addresses one `eth_getLogs` filter may carry.
|
|
574
|
+
* This is an RPC-tier opinion, not a protocol constant — callers with a dedicated
|
|
575
|
+
* node can raise it; `0` disables splitting (one filter for the whole list).
|
|
576
|
+
*/
|
|
577
|
+
export const DEFAULT_GETLOGS_ADDRESS_BATCH = 1000;
|
|
578
|
+
/**
|
|
579
|
+
* Pull logs over a block range, adapting to RPC range caps — and, when `address` is
|
|
580
|
+
* a list, splitting that list so a registered-set watch scan stays one code path for
|
|
581
|
+
* self-host and hosted. Providers cap `eth_getLogs` very differently and the numbers
|
|
582
|
+
* change, so we don't hard-code a window: try the whole range, and when the RPC
|
|
583
|
+
* rejects it, halve the window and sweep. `ABX_GETLOGS_RANGE` overrides the starting
|
|
584
|
+
* window. Address-list chunking is a separate cap (`addressBatch`); 5000-block
|
|
585
|
+
* per-tick windows belong to the caller (the watcher), not here.
|
|
560
586
|
*
|
|
561
587
|
* Once the working window is known we estimate the whole job *before* grinding it: if
|
|
562
588
|
* it would exceed the cap, we stop early (only the probe calls spent) and throw
|
|
563
589
|
* {@link GetLogsScanTooLargeError} so the caller can offer the real fix (a higher-range
|
|
564
590
|
* RPC) or an explicit opt-in to chunk through it.
|
|
565
591
|
*/
|
|
566
|
-
async function getLogsAdaptive(client, address, fromBlock, toBlock, opts = {}) {
|
|
592
|
+
export async function getLogsAdaptive(client, address, fromBlock, toBlock, opts = {}) {
|
|
593
|
+
const addrs = (Array.isArray(address) ? address : [address]);
|
|
594
|
+
if (addrs.length === 0)
|
|
595
|
+
return [];
|
|
596
|
+
const batchSize = opts.addressBatch === 0 ? addrs.length : Math.max(1, opts.addressBatch ?? DEFAULT_GETLOGS_ADDRESS_BATCH);
|
|
597
|
+
const batches = [];
|
|
598
|
+
for (let i = 0; i < addrs.length; i += batchSize)
|
|
599
|
+
batches.push(addrs.slice(i, i + batchSize));
|
|
567
600
|
const span = toBlock - fromBlock + 1n;
|
|
568
601
|
const envRange = readEnv('ABX_GETLOGS_RANGE');
|
|
569
|
-
|
|
602
|
+
const startWindow = opts.range && opts.range > 0n ? opts.range : envRange && BigInt(envRange) > 0n ? BigInt(envRange) : span;
|
|
570
603
|
const out = [];
|
|
571
|
-
let start = fromBlock;
|
|
572
604
|
let estimated = false;
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
let
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
605
|
+
for (const batch of batches) {
|
|
606
|
+
const filter = batch.length === 1 ? batch[0] : batch;
|
|
607
|
+
let window = startWindow;
|
|
608
|
+
let start = fromBlock;
|
|
609
|
+
while (start <= toBlock) {
|
|
610
|
+
const end = start + window - 1n < toBlock ? start + window - 1n : toBlock;
|
|
611
|
+
let logs;
|
|
612
|
+
try {
|
|
613
|
+
logs = (await client.getLogs({ address: filter, fromBlock: start, toBlock: end }));
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
if (isGetLogsRangeError(err) && window > 1n) {
|
|
617
|
+
window = window > 2n ? window / 2n : 1n; // RPC rejected this window — shrink and retry
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
throw err; // genuine failure, or already at one block per call
|
|
621
|
+
}
|
|
622
|
+
out.push(...logs);
|
|
623
|
+
start = end + 1n;
|
|
624
|
+
// Window now proven. Estimate the whole job once (range chunks × address batches)
|
|
625
|
+
// and bail *before* grinding if absurd.
|
|
626
|
+
if (!estimated) {
|
|
627
|
+
estimated = true;
|
|
628
|
+
const remaining = toBlock - start + 1n;
|
|
629
|
+
const perBatch = (remaining > 0n ? Number(remaining / window) + 1 : 0) + 1; // +1 for the chunk just done
|
|
630
|
+
const need = perBatch * batches.length;
|
|
631
|
+
const cap = resolveMaxChunks(opts.maxChunks);
|
|
632
|
+
if (need > cap)
|
|
633
|
+
throw new GetLogsScanTooLargeError(span, window, need, cap);
|
|
583
634
|
}
|
|
584
|
-
throw err; // genuine failure, or already at one block per call
|
|
585
|
-
}
|
|
586
|
-
out.push(...logs);
|
|
587
|
-
start = end + 1n;
|
|
588
|
-
// Window now proven. Estimate the whole job once and bail *before* grinding if absurd.
|
|
589
|
-
if (!estimated) {
|
|
590
|
-
estimated = true;
|
|
591
|
-
const remaining = toBlock - start + 1n;
|
|
592
|
-
const need = (remaining > 0n ? Number(remaining / window) + 1 : 0) + 1; // +1 for the chunk just done
|
|
593
|
-
const cap = resolveMaxChunks(opts.maxChunks);
|
|
594
|
-
if (need > cap)
|
|
595
|
-
throw new GetLogsScanTooLargeError(span, window, need, cap);
|
|
596
635
|
}
|
|
597
636
|
}
|
|
598
637
|
return out;
|
|
@@ -702,7 +741,7 @@ export async function multicallChunked(client, contracts, chunkSize) {
|
|
|
702
741
|
* actually wants to display it (`nothing served depends on it`, the one exception being a fresh
|
|
703
742
|
* `abx demo` read-back, which asks for it explicitly).
|
|
704
743
|
*/
|
|
705
|
-
async function applyHeadReads(client, state, factory, readUriDocuments = false) {
|
|
744
|
+
async function applyHeadReads(client, state, factory, readUriDocuments = false, reuseScript) {
|
|
706
745
|
const token = { address: state.address, abi: oneOfOneImageAbi };
|
|
707
746
|
const tokenIds = state.tokens.map((t) => t.tokenId);
|
|
708
747
|
// The edition family (ERC-1155) shares every cheap fixed-return name with the 721 superset ABI
|
|
@@ -730,8 +769,11 @@ async function applyHeadReads(client, state, factory, readUriDocuments = false)
|
|
|
730
769
|
cheap.push({ address: factory, abi: oneOfOneImageFactoryAbi, functionName: 'isAbxClone', args: [state.address] }, { address: factory, abi: oneOfOneImageFactoryAbi, functionName: 'implementation' });
|
|
731
770
|
}
|
|
732
771
|
// On-chain script: chunk content is too large to log, so the count is a head read.
|
|
772
|
+
// Skip when the caller already holds a digest and `fresh` had no `ScriptUpdated` —
|
|
773
|
+
// a Transfer must not re-download every chunk.
|
|
774
|
+
const keepPriorScript = !!(state.script && reuseScript?.digest != null);
|
|
733
775
|
const scriptIdx = cheap.length;
|
|
734
|
-
if (state.script) {
|
|
776
|
+
if (state.script && !keepPriorScript) {
|
|
735
777
|
cheap.push({ address: state.address, abi: seriesCodeAbi, functionName: 'scriptChunkCount' });
|
|
736
778
|
}
|
|
737
779
|
// ── unbounded returns: each one may assemble a whole document on-chain ──
|
|
@@ -774,7 +816,11 @@ async function applyHeadReads(client, state, factory, readUriDocuments = false)
|
|
|
774
816
|
tok.tokenURI = heavyVals[1 + k] ?? null;
|
|
775
817
|
});
|
|
776
818
|
}
|
|
777
|
-
if (state.script) {
|
|
819
|
+
if (state.script && keepPriorScript) {
|
|
820
|
+
state.script.chunkCount = reuseScript.chunkCount;
|
|
821
|
+
state.script.digest = reuseScript.digest;
|
|
822
|
+
}
|
|
823
|
+
else if (state.script) {
|
|
778
824
|
const count = cheapVals[scriptIdx] ?? null;
|
|
779
825
|
state.script.chunkCount = count === null ? null : Number(count);
|
|
780
826
|
// digest = keccak over the concatenated chunks — the content half of effect inputsHash
|