@crewhaus/tool-chaincall 0.7.0 → 0.8.0

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.
@@ -1,6 +1,10 @@
1
1
  /** Encode a call from a signature and its arguments. Throws on a bad type. */
2
2
  export declare function encodeCallData(signature: string, args: ReadonlyArray<unknown>, what: string): Promise<string>;
3
- /** Decode return data against a list of ABI types. */
3
+ /**
4
+ * Decode return data against a list of ABI types. An empty list decodes to
5
+ * no values, as it always has: EvmMulticall accepts `outputs: []`, and
6
+ * AbiDecode's own schema (which wants at least one type) is not asked.
7
+ */
4
8
  export declare function decodeValues(types: ReadonlyArray<string>, hex: string, what: string): Promise<unknown[]>;
5
9
  export type CheckedAddress = {
6
10
  /** EIP-55 mixed case, which is what goes into any output. */
package/dist/lib/codec.js CHANGED
@@ -20,9 +20,23 @@
20
20
  */
21
21
  import { abiDecode, abiEncodeCall, addressCheck } from "@crewhaus/tool-onchain";
22
22
  import { ChainCallError } from "./rpc";
23
- /** Read a tool result that is known to be a JSON string. */
23
+ /**
24
+ * Run one of `tool-onchain`'s tools and read its JSON result.
25
+ *
26
+ * The input goes through the tool's own schema first, as it would from a
27
+ * model. Calling `execute` directly skips the executor that normally parses
28
+ * it, and with it the limits the schema carries — `AbiDecode`'s cap on the
29
+ * size of the data it decodes among them — while the data here comes from
30
+ * a node answering for a contract somebody else deployed.
31
+ */
24
32
  async function toolJson(tool, input, what) {
25
- const result = await tool.execute(input);
33
+ const parsed = tool.inputSchema.safeParse(input);
34
+ if (!parsed.success) {
35
+ const issue = parsed.error.issues[0];
36
+ const path = issue === undefined || issue.path.length === 0 ? "" : ` (${issue.path.join(".")})`;
37
+ throw new ChainCallError(`${what}: ${tool.name} refused the input${path}: ${issue?.message ?? "invalid"}`);
38
+ }
39
+ const result = await tool.execute(parsed.data);
26
40
  if (typeof result !== "string") {
27
41
  throw new ChainCallError(`${what}: ${tool.name} returned non-text content`);
28
42
  }
@@ -36,8 +50,14 @@ export async function encodeCallData(signature, args, what) {
36
50
  throw new ChainCallError(`${what}: could not encode the call`);
37
51
  return encoded;
38
52
  }
39
- /** Decode return data against a list of ABI types. */
53
+ /**
54
+ * Decode return data against a list of ABI types. An empty list decodes to
55
+ * no values, as it always has: EvmMulticall accepts `outputs: []`, and
56
+ * AbiDecode's own schema (which wants at least one type) is not asked.
57
+ */
40
58
  export async function decodeValues(types, hex, what) {
59
+ if (types.length === 0)
60
+ return [];
41
61
  const out = await toolJson(abiDecode, { data: hex, types }, what);
42
62
  const values = out["values"];
43
63
  if (!Array.isArray(values))
package/dist/lib/rpc.d.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  * does — this package holds no endpoint list of its own and there is no
17
17
  * caller-supplied URL anywhere in it.
18
18
  */
19
- import type { ChainAdapter } from "@crewhaus/chain-adapter-base";
19
+ import type { ChainAdapter, ChainAdapterConfig } from "@crewhaus/chain-adapter-base";
20
20
  import { CrewhausError } from "@crewhaus/errors";
21
21
  /** A refusal raised by this package: a bad input, a broken promise, a limit. */
22
22
  export declare class ChainCallError extends CrewhausError {
@@ -30,6 +30,12 @@ export declare class ChainCallError extends CrewhausError {
30
30
  */
31
31
  export type ChainRpc = (method: string, params: ReadonlyArray<unknown>, opts?: {
32
32
  readonly signal?: AbortSignal;
33
+ /**
34
+ * What is left of the tool's own deadline, in ms. A transport with a
35
+ * deadline of its own (the chain adapter's defaults to 30 s) uses this
36
+ * instead, so a `timeoutMs` above that default is not silently cut to it.
37
+ */
38
+ readonly timeoutMs?: number;
33
39
  }) => Promise<unknown>;
34
40
  /** Boot-time binding: chain id in, transport out. */
35
41
  export type ChainRpcResolver = (chainId: string) => ChainRpc | undefined;
@@ -41,9 +47,19 @@ export declare function setChainRpcResolver(fn: ChainRpcResolver | undefined): v
41
47
  /**
42
48
  * Adapt a `ChainAdapter` into the seam, so the runtime can hand these tools
43
49
  * the same adapters `tool-evm` already gets. `rpcRead` keeps its own
44
- * allowlist and boundary classification; this does not replace either.
50
+ * allowlist and boundary classification; this does not replace either. The
51
+ * call's signal goes through, so a cancelled read closes its request instead
52
+ * of leaving it in flight.
45
53
  */
46
54
  export declare function chainRpcFromAdapter(adapter: ChainAdapter): ChainRpc;
55
+ /**
56
+ * Bind the transports from the spec's `chains` block — what every generated
57
+ * bundle, `crewhaus run` and `crewhaus eval` call at boot when a spec lists
58
+ * one of these tools. Each chain gets the adapter `tool-evm` gets.
59
+ */
60
+ export declare function bindChainCallChains(config: {
61
+ readonly chains: ReadonlyArray<ChainAdapterConfig>;
62
+ }): void;
47
63
  /**
48
64
  * Test-only injection point, the convention every networked package here
49
65
  * follows: one transport for every chain id. `undefined` unbinds, and a
@@ -62,12 +78,12 @@ export declare function resolveRpc(chainId: string, toolName: string): ChainRpc;
62
78
  * write-class method reaching this line is a defect in this package and
63
79
  * should arrive at the log looking like one.
64
80
  *
65
- * The signal is both passed down AND raced against. `ChainAdapter.rpcRead`
66
- * takes no signal, so a transport built on one cannot be cancelled — and a
67
- * `timeoutMs` that bounds nothing is a promise this package should not be
68
- * making. Racing means the TOOL returns on time; the request underneath it
69
- * may still be in flight, which is the honest limit of a seam whose other
70
- * side does not accept a signal.
81
+ * The signal is both passed down AND raced against. The chain adapter
82
+ * honours it (see `chainRpcFromAdapter`), but a transport bound some other
83
+ * way may not — and a `timeoutMs` that bounds nothing is a promise this
84
+ * package should not be making. Racing means the TOOL returns on time even
85
+ * then; the request underneath may still be in flight, which is the honest
86
+ * limit of a seam whose other side does not accept a signal.
71
87
  */
72
88
  export declare function rpcRead(rpc: ChainRpc, chainId: string, method: string, params: ReadonlyArray<unknown>, signal?: AbortSignal): Promise<unknown>;
73
89
  /** The JSON-RPC error object, as much of it as a thrown value tends to carry. */
@@ -82,19 +98,50 @@ export type RpcErrorShape = {
82
98
  * `.data` attached, some an `Error` wrapping one under `.cause` or `.error`.
83
99
  */
84
100
  export declare function rpcError(err: unknown): RpcErrorShape;
85
- /** True when the failure is the caller's or the runtime's cancellation. */
101
+ /**
102
+ * True when the failure is a cancellation or a deadline — the caller's, the
103
+ * runtime's, or the transport's own (a chain adapter's timeout carries
104
+ * `timedOut`) — rather than anything the node said.
105
+ */
86
106
  export declare function isAbort(err: unknown, signal?: AbortSignal): boolean;
87
107
  /**
88
- * -32601 and the strings providers use instead of it.
108
+ * What each node the read was sent to said, one entry per node.
109
+ *
110
+ * The chain adapter reports a read that failed on every node as one error
111
+ * whose words end with the last node's reason; which node failed last is a
112
+ * matter of timing, so nothing here decides on those words. It also carries
113
+ * `attempts`, each node's own failure, and each one that the node answered
114
+ * with a JSON-RPC error carries that error as `rpc`. `answer` is that error,
115
+ * or undefined for a node that answered nothing a JSON-RPC client can read:
116
+ * it could not be reached, answered an HTTP error or a redirect, or the
117
+ * adapter refused what it sent. An adapter error without `attempts` (a quorum
118
+ * lost before every voter was heard, or no node at all) is one entry with no
119
+ * answer. Anything else, a transport bound some other way, is read as the
120
+ * node's own answer, as it always was.
121
+ */
122
+ export declare function nodeAnswers(err: unknown): ReadonlyArray<{
123
+ readonly answer: RpcErrorShape | undefined;
124
+ readonly text: string;
125
+ }>;
126
+ /**
127
+ * -32601 and the strings providers use instead of it, from EVERY node.
89
128
  *
90
129
  * This is the one predicate `EvmSimulateBundle` degrades on, so it has to be
91
130
  * narrow in both directions. A node that has never heard of `eth_simulateV1`
92
131
  * is a reason to answer a lesser question; a node that timed out, or that
93
132
  * rejected the parameters, or whose simulation reverted, is NOT — degrading
94
133
  * on those would turn a transient fault into a quietly weaker answer that
95
- * still looks like an answer.
134
+ * still looks like an answer. So with several nodes every one must have said
135
+ * so: one that rejected the parameters, could not be reached, or was not
136
+ * heard may implement the method.
96
137
  */
97
138
  export declare function isMethodUnsupported(err: unknown, signal?: AbortSignal): boolean;
139
+ /**
140
+ * The first node, in the order the chain names them, that answered with an
141
+ * error other than "no such method": a node that implements the method and
142
+ * refused this request. Undefined when no node did.
143
+ */
144
+ export declare function firstRefusal(err: unknown): string | undefined;
98
145
  /** A one-line description of a node failure, for a refusal message. */
99
146
  export declare function rpcErrorText(err: unknown): string;
100
147
  export type Deadline = {
@@ -102,9 +149,4 @@ export type Deadline = {
102
149
  /** Clear the timer. Always call it, or the process keeps a handle alive. */
103
150
  cancel(): void;
104
151
  };
105
- /**
106
- * A deadline that also honours the runtime's own cancellation. Every tool
107
- * here opens one before its first byte and cancels it in a `finally`: a tool
108
- * that fans out over several round trips can hang in several places.
109
- */
110
152
  export declare function startDeadline(ms: number, outer?: AbortSignal): Deadline;
package/dist/lib/rpc.js CHANGED
@@ -1,4 +1,5 @@
1
- import { assertReadOnlyMethod } from "@crewhaus/chain-adapter-base";
1
+ import { CHAINS_BLOCK_EXAMPLE, assertReadOnlyMethod } from "@crewhaus/chain-adapter-base";
2
+ import { createEvmAdapters } from "@crewhaus/chain-adapter-evm";
2
3
  import { CrewhausError } from "@crewhaus/errors";
3
4
  /** A refusal raised by this package: a bad input, a broken promise, a limit. */
4
5
  export class ChainCallError extends CrewhausError {
@@ -18,10 +19,27 @@ export function setChainRpcResolver(fn) {
18
19
  /**
19
20
  * Adapt a `ChainAdapter` into the seam, so the runtime can hand these tools
20
21
  * the same adapters `tool-evm` already gets. `rpcRead` keeps its own
21
- * allowlist and boundary classification; this does not replace either.
22
+ * allowlist and boundary classification; this does not replace either. The
23
+ * call's signal goes through, so a cancelled read closes its request instead
24
+ * of leaving it in flight.
22
25
  */
23
26
  export function chainRpcFromAdapter(adapter) {
24
- return (method, params) => adapter.rpcRead(method, params);
27
+ return (method, params, opts) => adapter.rpcRead(method, params, {
28
+ ...(opts?.signal === undefined ? {} : { signal: opts.signal }),
29
+ ...(opts?.timeoutMs === undefined ? {} : { timeoutMs: opts.timeoutMs }),
30
+ });
31
+ }
32
+ /**
33
+ * Bind the transports from the spec's `chains` block — what every generated
34
+ * bundle, `crewhaus run` and `crewhaus eval` call at boot when a spec lists
35
+ * one of these tools. Each chain gets the adapter `tool-evm` gets.
36
+ */
37
+ export function bindChainCallChains(config) {
38
+ const adapters = createEvmAdapters(config.chains);
39
+ setChainRpcResolver((chainId) => {
40
+ const adapter = adapters.get(chainId);
41
+ return adapter === undefined ? undefined : chainRpcFromAdapter(adapter);
42
+ });
25
43
  }
26
44
  /**
27
45
  * Test-only injection point, the convention every networked package here
@@ -35,7 +53,7 @@ export function _setRpc(fn) {
35
53
  /** Find the transport for a chain, or say what the operator has to wire. */
36
54
  export function resolveRpc(chainId, toolName) {
37
55
  if (resolver === undefined) {
38
- throw new ChainCallError(`${toolName}: no chain transport is bound — the runtime must call setChainRpcResolver() at boot with the chains from spec.chains[]`);
56
+ throw new ChainCallError(`${toolName}: no chain is configured. Declare one in the spec — ${CHAINS_BLOCK_EXAMPLE}.`);
39
57
  }
40
58
  const rpc = resolver(chainId);
41
59
  if (rpc === undefined) {
@@ -58,12 +76,12 @@ function abortError(signal) {
58
76
  * write-class method reaching this line is a defect in this package and
59
77
  * should arrive at the log looking like one.
60
78
  *
61
- * The signal is both passed down AND raced against. `ChainAdapter.rpcRead`
62
- * takes no signal, so a transport built on one cannot be cancelled — and a
63
- * `timeoutMs` that bounds nothing is a promise this package should not be
64
- * making. Racing means the TOOL returns on time; the request underneath it
65
- * may still be in flight, which is the honest limit of a seam whose other
66
- * side does not accept a signal.
79
+ * The signal is both passed down AND raced against. The chain adapter
80
+ * honours it (see `chainRpcFromAdapter`), but a transport bound some other
81
+ * way may not — and a `timeoutMs` that bounds nothing is a promise this
82
+ * package should not be making. Racing means the TOOL returns on time even
83
+ * then; the request underneath may still be in flight, which is the honest
84
+ * limit of a seam whose other side does not accept a signal.
67
85
  */
68
86
  export async function rpcRead(rpc, chainId, method, params, signal) {
69
87
  assertReadOnlyMethod(chainId, method);
@@ -71,7 +89,13 @@ export async function rpcRead(rpc, chainId, method, params, signal) {
71
89
  // reason a node sees one more request.
72
90
  if (signal?.aborted === true)
73
91
  throw abortError(signal);
74
- const call = rpc(method, params, signal === undefined ? {} : { signal });
92
+ const deadlineAt = signal === undefined ? undefined : DEADLINES.get(signal);
93
+ const call = rpc(method, params, {
94
+ ...(signal === undefined ? {} : { signal }),
95
+ // The rest of the tool's budget, so the transport's own deadline is not
96
+ // shorter than the one the caller asked for.
97
+ ...(deadlineAt === undefined ? {} : { timeoutMs: Math.max(1, deadlineAt - Date.now()) }),
98
+ });
75
99
  if (signal === undefined)
76
100
  return call;
77
101
  let onAbort;
@@ -124,27 +148,55 @@ export function rpcError(err) {
124
148
  const message = record?.["message"];
125
149
  return typeof message === "string" ? { message } : { message: String(err) };
126
150
  }
127
- /** True when the failure is the caller's or the runtime's cancellation. */
151
+ /**
152
+ * True when the failure is a cancellation or a deadline — the caller's, the
153
+ * runtime's, or the transport's own (a chain adapter's timeout carries
154
+ * `timedOut`) — rather than anything the node said.
155
+ */
128
156
  export function isAbort(err, signal) {
129
157
  if (signal?.aborted === true)
130
158
  return true;
131
159
  const record = asRecord(err);
132
- return record?.["name"] === "AbortError" || record?.["name"] === "TimeoutError";
160
+ return (record?.["name"] === "AbortError" ||
161
+ record?.["name"] === "TimeoutError" ||
162
+ record?.["timedOut"] === true);
133
163
  }
134
164
  /**
135
- * -32601 and the strings providers use instead of it.
165
+ * What each node the read was sent to said, one entry per node.
136
166
  *
137
- * This is the one predicate `EvmSimulateBundle` degrades on, so it has to be
138
- * narrow in both directions. A node that has never heard of `eth_simulateV1`
139
- * is a reason to answer a lesser question; a node that timed out, or that
140
- * rejected the parameters, or whose simulation reverted, is NOT — degrading
141
- * on those would turn a transient fault into a quietly weaker answer that
142
- * still looks like an answer.
167
+ * The chain adapter reports a read that failed on every node as one error
168
+ * whose words end with the last node's reason; which node failed last is a
169
+ * matter of timing, so nothing here decides on those words. It also carries
170
+ * `attempts`, each node's own failure, and each one that the node answered
171
+ * with a JSON-RPC error carries that error as `rpc`. `answer` is that error,
172
+ * or undefined for a node that answered nothing a JSON-RPC client can read:
173
+ * it could not be reached, answered an HTTP error or a redirect, or the
174
+ * adapter refused what it sent. An adapter error without `attempts` (a quorum
175
+ * lost before every voter was heard, or no node at all) is one entry with no
176
+ * answer. Anything else, a transport bound some other way, is read as the
177
+ * node's own answer, as it always was.
143
178
  */
144
- export function isMethodUnsupported(err, signal) {
145
- if (isAbort(err, signal))
146
- return false;
147
- const { code, message } = rpcError(err);
179
+ export function nodeAnswers(err) {
180
+ const record = asRecord(err);
181
+ if (record?.["name"] !== "ChainAdapterError") {
182
+ return [{ answer: rpcError(err), text: rpcErrorText(err) }];
183
+ }
184
+ const attempts = record["attempts"];
185
+ const nodes = Array.isArray(attempts) && attempts.length > 0 ? attempts : [err];
186
+ return nodes.map((node) => {
187
+ const rpc = asRecord(asRecord(node)?.["rpc"]);
188
+ const code = rpc?.["code"];
189
+ const message = rpc?.["message"];
190
+ if (typeof code !== "number") {
191
+ const said = asRecord(node)?.["message"];
192
+ return { answer: undefined, text: typeof said === "string" ? said : String(node) };
193
+ }
194
+ const words = typeof message === "string" ? message : "";
195
+ return { answer: { code, message: words }, text: `${words} (code ${code})` };
196
+ });
197
+ }
198
+ /** -32601 and the strings providers use instead of it, in one node's answer. */
199
+ function saysUnsupported({ code, message }) {
148
200
  // -32601 is "method not found". -32004 is the "method not supported" code
149
201
  // several providers return instead; Geth and Erigon both use -32601.
150
202
  if (code === -32601 || code === -32004)
@@ -159,6 +211,32 @@ export function isMethodUnsupported(err, signal) {
159
211
  /\bunknown method\b/.test(text) ||
160
212
  /\bdoes not exist\/is not available\b/.test(text));
161
213
  }
214
+ /**
215
+ * -32601 and the strings providers use instead of it, from EVERY node.
216
+ *
217
+ * This is the one predicate `EvmSimulateBundle` degrades on, so it has to be
218
+ * narrow in both directions. A node that has never heard of `eth_simulateV1`
219
+ * is a reason to answer a lesser question; a node that timed out, or that
220
+ * rejected the parameters, or whose simulation reverted, is NOT — degrading
221
+ * on those would turn a transient fault into a quietly weaker answer that
222
+ * still looks like an answer. So with several nodes every one must have said
223
+ * so: one that rejected the parameters, could not be reached, or was not
224
+ * heard may implement the method.
225
+ */
226
+ export function isMethodUnsupported(err, signal) {
227
+ if (isAbort(err, signal))
228
+ return false;
229
+ return nodeAnswers(err).every(({ answer }) => answer !== undefined && saysUnsupported(answer));
230
+ }
231
+ /**
232
+ * The first node, in the order the chain names them, that answered with an
233
+ * error other than "no such method": a node that implements the method and
234
+ * refused this request. Undefined when no node did.
235
+ */
236
+ export function firstRefusal(err) {
237
+ return nodeAnswers(err).find(({ answer }) => answer !== undefined && !saysUnsupported(answer))
238
+ ?.text;
239
+ }
162
240
  /** A one-line description of a node failure, for a refusal message. */
163
241
  export function rpcErrorText(err) {
164
242
  const { code, message } = rpcError(err);
@@ -170,8 +248,11 @@ export function rpcErrorText(err) {
170
248
  * here opens one before its first byte and cancels it in a `finally`: a tool
171
249
  * that fans out over several round trips can hang in several places.
172
250
  */
251
+ /** When each deadline's signal runs out (epoch ms), for `rpcRead` to pass down. */
252
+ const DEADLINES = new WeakMap();
173
253
  export function startDeadline(ms, outer) {
174
254
  const ctrl = new AbortController();
255
+ DEADLINES.set(ctrl.signal, Date.now() + ms);
175
256
  const timer = setTimeout(() => ctrl.abort(new Error(`deadline of ${ms}ms elapsed`)), ms);
176
257
  const onOuter = () => ctrl.abort(outer?.reason);
177
258
  if (outer !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/tool-chaincall",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Deterministic EVM contract reads: batched calls, contract introspection, gas market and simulation",
6
6
  "main": "dist/index.js",
@@ -8,18 +8,20 @@
8
8
  "exports": {
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
12
13
  }
13
14
  },
14
15
  "scripts": {
15
16
  "test": "bun test src"
16
17
  },
17
18
  "dependencies": {
18
- "@crewhaus/chain-adapter-base": "0.7.0",
19
- "@crewhaus/errors": "0.7.0",
20
- "@crewhaus/tool-builder": "0.7.0",
21
- "@crewhaus/tool-catalog": "0.7.0",
22
- "@crewhaus/tool-onchain": "0.7.0",
19
+ "@crewhaus/chain-adapter-base": "0.8.0",
20
+ "@crewhaus/chain-adapter-evm": "0.8.0",
21
+ "@crewhaus/errors": "0.8.0",
22
+ "@crewhaus/tool-builder": "0.8.0",
23
+ "@crewhaus/tool-catalog": "0.8.0",
24
+ "@crewhaus/tool-onchain": "0.8.0",
23
25
  "zod": "^3.23.8"
24
26
  },
25
27
  "devDependencies": {
@@ -43,5 +45,8 @@
43
45
  "publishConfig": {
44
46
  "access": "public"
45
47
  },
46
- "files": ["dist", "README.md", "LICENSE", "NOTICE"]
48
+ "files": ["dist", "README.md", "LICENSE", "NOTICE"],
49
+ "engines": {
50
+ "bun": ">=1.3.14"
51
+ }
47
52
  }
@@ -1,120 +0,0 @@
1
- import type { ChainRpc } from "./lib/rpc";
2
- /** A 32-byte word holding an unsigned integer. */
3
- export declare function word(value: bigint | number): string;
4
- /** A 32-byte word holding an address in its low 20 bytes. */
5
- export declare function addressWord(address: string): string;
6
- /** A 32-byte word that is not an address: the top bytes are dirty. */
7
- export declare const NON_ADDRESS_WORD = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bec8";
8
- export declare const ZERO_WORD: string;
9
- /**
10
- * ABI-encode what `aggregate3` returns, through the real encoder.
11
- *
12
- * The trick is to encode a call taking exactly that argument and drop the
13
- * four-byte selector: the remaining bytes are the argument's encoding, which
14
- * is what a node hands back.
15
- */
16
- export declare function aggregate3Return(rows: ReadonlyArray<readonly [boolean, string]>): Promise<string>;
17
- /** `Error(string)` revert bytes for a given message, through the real encoder. */
18
- export declare function errorStringRevert(message: string): Promise<string>;
19
- /** The canonical EIP-1167 runtime bytecode delegating to `target`. */
20
- export declare function minimalProxyCode(target: string): string;
21
- /** A JSON-RPC error as a provider throws it: an Error carrying `code`. */
22
- export declare function jsonRpcError(code: number, message: string, data?: unknown): Error;
23
- /** What a node says about a method it has never heard of. */
24
- export declare function methodNotFound(method: string): Error;
25
- export type RpcHandler = (params: ReadonlyArray<unknown>, call: number) => unknown | Promise<unknown>;
26
- export type RpcStub = {
27
- readonly rpc: ChainRpc;
28
- /** Every dispatch, in order. The "nothing sends" test reads this. */
29
- readonly calls: Array<{
30
- method: string;
31
- params: ReadonlyArray<unknown>;
32
- }>;
33
- /** How many times one method was dispatched. */
34
- count(method: string): number;
35
- };
36
- /**
37
- * A transport built from a table of handlers.
38
- *
39
- * An unhandled method answers -32601, which is what a real node does and is
40
- * also the one failure this package degrades on — so a test that forgets a
41
- * handler exercises the degradation path loudly rather than hanging.
42
- */
43
- export declare function rpcStub(handlers: Readonly<Record<string, RpcHandler>>): RpcStub;
44
- /** Serve a different blob to each successive `eth_call`, in order. */
45
- export declare function sequentialCalls(blobs: ReadonlyArray<string>): RpcHandler;
46
- export type InspectViews = {
47
- /** What supportsInterface(0xffffffff) answers. A compliant contract: false. */
48
- readonly sentinel?: boolean | "revert";
49
- /** Interface ids the contract claims. */
50
- readonly supports?: ReadonlyArray<string>;
51
- /** What implementation() answers, when it answers. */
52
- readonly implementationView?: string;
53
- /** Whether proxiableUUID() answers at all. */
54
- readonly proxiable?: boolean;
55
- /** Present only when the beacon slot is set: the beacon's implementation(). */
56
- readonly beaconImplementation?: string | "revert";
57
- };
58
- /**
59
- * The view rows `ContractInspect` expects, in the order it builds them:
60
- * the 0xffffffff sentinel, one row per probed interface id, implementation(),
61
- * proxiableUUID(), and the beacon's implementation() when there is a beacon.
62
- */
63
- export declare function inspectViewRows(spec: InspectViews): Array<readonly [boolean, string]>;
64
- export type SimulatedCall = {
65
- readonly returnData?: string;
66
- readonly gasUsed?: number;
67
- readonly status?: 0 | 1;
68
- readonly logs?: ReadonlyArray<{
69
- address: string;
70
- topics: string[];
71
- data: string;
72
- }>;
73
- readonly error?: {
74
- code: number;
75
- message: string;
76
- };
77
- };
78
- /** One block of `eth_simulateV1` output. */
79
- export declare function simulateV1Result(blockNumber: number, calls: ReadonlyArray<SimulatedCall>): unknown;
80
- /**
81
- * One block of `eth_simulateV1` output with the call objects passed through
82
- * VERBATIM.
83
- *
84
- * `simulateV1Result` fills in `status`, `gasUsed` and `logs` for every call,
85
- * which makes the case worth testing — a node that leaves one of them out —
86
- * inexpressible. A fixture that cannot express a shape hides every bug that
87
- * shape causes.
88
- */
89
- export declare function simulateV1Raw(blockNumber: number, calls: ReadonlyArray<Record<string, unknown>>): unknown;
90
- /** An ERC-20 Transfer log, the shape a simulation returns them in. */
91
- export declare const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
92
- export declare function transferLog(token: string, from: string, to: string, amount: bigint): {
93
- address: string;
94
- topics: string[];
95
- data: string;
96
- };
97
- export type BlockFixture = {
98
- readonly number: number;
99
- readonly gasUsed: bigint;
100
- readonly gasLimit: bigint;
101
- readonly baseFeePerGas?: bigint;
102
- };
103
- export declare function blockResult(block: BlockFixture): unknown;
104
- export type FeeHistoryFixture = {
105
- readonly oldestBlock: number;
106
- /** blockCount + 1 entries: the last is the NEXT block's base fee. */
107
- readonly baseFeePerGas: ReadonlyArray<bigint>;
108
- readonly reward?: ReadonlyArray<ReadonlyArray<bigint>>;
109
- readonly baseFeePerBlobGas?: ReadonlyArray<bigint>;
110
- };
111
- export declare function feeHistoryResult(history: FeeHistoryFixture): unknown;
112
- export declare const ADDR: Readonly<{
113
- proxy: "0x1111111111111111111111111111111111111111";
114
- implementation: "0x2222222222222222222222222222222222222222";
115
- admin: "0x3333333333333333333333333333333333333333";
116
- beacon: "0x4444444444444444444444444444444444444444";
117
- other: "0x5555555555555555555555555555555555555555";
118
- token: "0x6666666666666666666666666666666666666666";
119
- wallet: "0x7777777777777777777777777777777777777777";
120
- }>;