@volga-sh/evm-ghostcall 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -1,18 +1,25 @@
1
1
  # ghostcall
2
2
 
3
- `ghostcall` is a zero-deployment batching program for CREATE-style `eth_call`.
3
+ `ghostcall` batches EVM blockchain reads without deployment dependencies.
4
4
 
5
- Instead of calling a deployed Multicall contract, the client sends compiled initcode plus an
6
- appended payload. The EVM executes that initcode exactly as if it were deploying a contract, but
7
- because the transport is `eth_call`, nothing is persisted. Whatever the initcode `RETURN`s comes
8
- back as the RPC result.
5
+ ## Documentation
9
6
 
10
- The implementation lives in [`src/Ghostcall.yul`](src/Ghostcall.yul).
7
+ The docs live at [ghostcall.volga.sh](https://ghostcall.volga.sh).
11
8
 
12
- ## Quick example
9
+ Start there for installation, examples, the API reference, protocol details, and endpoint limit notes.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install @volga-sh/evm-ghostcall
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ This example uses viem for the EIP-1193-compatible client and ABI helpers. Install it with `npm install viem` if your app does not already use it.
13
20
 
14
21
  ```ts
15
- import { aggregateCalls } from "@volga-sh/evm-ghostcall";
22
+ import { aggregateDecodedCalls } from "@volga-sh/evm-ghostcall";
16
23
  import {
17
24
  createPublicClient,
18
25
  decodeFunctionResult,
@@ -22,299 +29,55 @@ import {
22
29
  } from "viem";
23
30
  import { mainnet } from "viem/chains";
24
31
 
25
- const erc20Abi = parseAbi([
26
- "function balanceOf(address account) view returns (uint256)",
27
- "function allowance(address owner, address spender) view returns (uint256)",
28
- ]);
29
-
30
- const token = "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
31
- const owner = "0x1111111111111111111111111111111111111111";
32
- const spender = "0x2222222222222222222222222222222222222222";
33
-
34
32
  const client = createPublicClient({
35
33
  chain: mainnet,
36
34
  transport: http(),
37
35
  });
38
36
 
39
- const [balance, allowance] = await aggregateCalls(
40
- client,
41
- [
42
- {
43
- to: token,
44
- data: encodeFunctionData({
45
- abi: erc20Abi,
46
- functionName: "balanceOf",
47
- args: [owner],
48
- }),
49
- decodeResult: (data) =>
50
- decodeFunctionResult({
51
- abi: erc20Abi,
52
- functionName: "balanceOf",
53
- data,
54
- }),
55
- },
56
- {
57
- to: token,
58
- data: encodeFunctionData({
37
+ const erc20Abi = parseAbi(["function totalSupply() view returns (uint256)"]);
38
+
39
+ const [totalSupply] = await aggregateDecodedCalls(client, [
40
+ {
41
+ to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
42
+ data: encodeFunctionData({
43
+ abi: erc20Abi,
44
+ functionName: "totalSupply",
45
+ }),
46
+ decodeResult: (returnData) =>
47
+ decodeFunctionResult({
59
48
  abi: erc20Abi,
60
- functionName: "allowance",
61
- args: [owner, spender],
49
+ functionName: "totalSupply",
50
+ data: returnData,
62
51
  }),
63
- decodeResult: (data) =>
64
- decodeFunctionResult({
65
- abi: erc20Abi,
66
- functionName: "allowance",
67
- data,
68
- }),
69
- },
70
- ],
71
- { results: "decoded" },
72
- );
73
-
74
- console.log({
75
- balance,
76
- allowance,
77
- });
78
- ```
79
-
80
- ## Why this works
81
-
82
- - `eth_call` without a `to` field executes the supplied `data` as CREATE initcode.
83
- - Initcode can read caller-appended bytes from its own code using `CODECOPY`.
84
- - Initcode can perform ordinary external calls, pack the returned bytes into memory, and `RETURN` them.
85
- - Returned bytes are still subject to CREATE limits because the client treats them as would-be
86
- runtime bytecode.
87
-
88
- ## Development stack
89
-
90
- The repository now uses a minimal TypeScript-based test stack:
91
-
92
- - Foundry for contract compilation and `anvil`
93
- - Node's built-in [`node:test`](https://nodejs.org/api/test.html) runner
94
- - Node's built-in TypeScript stripping for test execution
95
- - [`ox`](https://www.npmjs.com/package/ox) for JSON-RPC, ABI, hex, and byte utilities
96
- - [`@safe-global/mock-contract`](https://www.npmjs.com/package/@safe-global/mock-contract) for configurable mock-call behavior
97
-
98
- That keeps the dependency footprint small while giving us a stable place to grow ABI-heavy tests.
99
-
100
- ## TypeScript SDK
101
-
102
- Install the SDK from npm:
103
-
104
- ```bash
105
- npm install @volga-sh/evm-ghostcall
106
- ```
107
-
108
- The repository also includes a minimal internal-first TypeScript SDK in
109
- [`src/sdk/index.ts`](src/sdk/index.ts).
110
-
111
- It intentionally exposes only the small protocol surface:
112
-
113
- - `encodeCalls(calls)` bundles the canonical Ghostcall initcode and returns the full CREATE-style `eth_call` data blob.
114
- - `decodeResults(data)` parses the packed Ghostcall response format into `{ success, returnData }` entries.
115
- - `aggregateCalls(provider, calls, options?)` sends the CREATE-style `eth_call` through an EIP-1193 `request` provider, decodes the packed response, and optionally runs each call's `decodeResult` callback.
116
-
117
- `encodeCalls` fails fast if any subcall exceeds the `uint16` calldata limit or if the full
118
- encoded CREATE payload would exceed the EVM initcode size ceiling.
119
-
120
- `aggregateCalls` treats `allowFailure` as an SDK-side policy. Failed subcalls reject by default,
121
- matching Multicall3-style strict batches, while calls marked `allowFailure: true` are returned as
122
- ordinary `{ success: false, returnData }` entries.
123
-
124
- The SDK has no ABI helpers and no runtime artifact reads. To ABI-decode successful entries, pass
125
- `decodeResult` callbacks that call the ABI library already used by the application. By default,
126
- `aggregateCalls` returns result entries. Pass `{ results: "decoded" }` to return decoded values
127
- directly.
128
-
129
- ## Current scope
130
-
131
- This implementation is intentionally focused on the smallest SDK-first variant:
132
-
133
- - zero-value `CALL` for subcalls
134
- - packed binary input instead of ABI encoding
135
- - packed binary output instead of ABI encoding
136
- - always-return result entries for every subcall
137
- - SDK-enforced strict failure policy instead of engine-enforced batch reverts
138
-
139
- That keeps the initcode small, auditable, and easy to extend.
140
-
141
- ## Why not a naive Solidity constructor
142
-
143
- A straightforward deployless design is to write a Solidity constructor that:
144
-
145
- - accepts an ABI-encoded array of calls,
146
- - executes them in the constructor, and
147
- - rewrites constructor memory so the returned bytes look like a normal ABI-encoded multicall result.
148
-
149
- That approach works, but this project intentionally uses a lower-level Yul program instead.
150
-
151
- Advantages of the current design:
152
-
153
- - smaller base program, because it avoids Solidity's constructor scaffolding and generic ABI decoding,
154
- - a tighter wire format, because both requests and responses use a compact custom binary layout instead of full ABI encoding,
155
- - less compiler coupling, because the batching logic does not depend on Solidity memory-layout assumptions inside constructor-generated code.
156
-
157
- In practice, this means less initcode to ship on every request, fewer bytes on the wire, and a design that is easier to reason about at the EVM level.
158
-
159
- ## Input format
160
-
161
- The caller sends:
162
-
163
- ```text
164
- <compiled ghostcall initcode><payload>
165
- ```
166
-
167
- Payload layout:
168
-
169
- ```text
170
- N bytes repeated call entries
171
- ```
172
-
173
- Each call entry:
174
-
175
- ```text
176
- 2 bytes calldata length (big-endian uint16)
177
- 20 bytes target
178
- N bytes calldata
179
- ```
180
-
181
- Notes:
182
-
183
- - Payload bytes are not normal calldata. They are appended after the compiled initcode and read via
184
- `CODECOPY`.
185
- - The length comes first on purpose. Ghostcall copies the 22-byte fixed header into scratch memory
186
- at offset `0x0a`, so one `mload(0x00)` exposes the length in the high 2 non-zero bytes and the
187
- target address in the low 20 bytes used by `CALL`.
188
- - An empty payload is valid and returns an empty result blob.
189
- - Per-call calldata is limited to `65535` bytes because the format uses `uint16`.
190
- - The whole CREATE payload is still limited by the network/client initcode size ceiling.
191
-
192
- ## Output format
193
-
194
- The program returns:
195
-
196
- ```text
197
- N bytes repeated result entries
198
- ```
199
-
200
- Each result entry:
201
-
202
- ```text
203
- 2 bytes packed header
204
- bit 15 = success flag
205
- bits 0-14 = returndata length (big-endian uint15)
206
- N bytes returndata
207
- ```
208
-
209
- Subcall failures are returned inline as ordinary result entries with `success = 0`.
210
-
211
- The engine only reverts for malformed payloads or per-entry return-size violations, and those
212
- top-level reverts are intentionally empty. The SDK is expected to validate payloads up front and
213
- impose any higher-level "fail the whole batch" policy for callers that want it.
214
-
215
- The packed result header can represent up to `32767` bytes of returndata per entry. On
216
- Ethereum, EIP-170's returned-code limit is usually the stricter bound: CREATE-style execution
217
- limits the whole response to `24,576` bytes, including the 2-byte header on each entry.
218
-
219
- ## Limits
220
-
221
- The aggregate response is returned through CREATE-style execution, so clients still treat it as
222
- would-be runtime code. Ghostcall does not impose its own aggregate response cap; the effective
223
- ceiling comes from the chain, client, RPC provider, gas setting, and request-size policy.
224
-
225
- Common reference points:
226
-
227
- - Ethereum's EIP-170 returned-code limit is `24,576` bytes.
228
- - Ethereum's EIP-3860 initcode limit is `49,152` bytes.
229
- - Other chains may set different values. For example, Monad documents larger contract-code and
230
- initcode limits.
231
-
232
- Measure the endpoint you plan to use instead of assuming a consensus value. Provider-side request
233
- limits can be lower than the chain limit.
234
-
235
- ## Benchmark limits
236
-
237
- The repository includes a TypeScript benchmark for rough endpoint-specific measurements:
238
-
239
- ```bash
240
- npm run benchmark:limits -- --rpc-url "$RPC_URL" --mode raw
52
+ },
53
+ ]);
241
54
  ```
242
55
 
243
- `raw` mode probes accepted CREATE initcode bytes and returned runtime-code bytes. `balances` mode
244
- uses a realistic ERC-20 balance workload:
56
+ See the [Getting Started guide](https://ghostcall.volga.sh/getting-started/) for a complete viem example with ABI encoding and decoding.
245
57
 
246
- ```bash
247
- npm run benchmark:limits -- \
248
- --rpc-url "$RPC_URL" \
249
- --mode balances \
250
- --token "$TOKEN_ADDRESS" \
251
- --owner "$OWNER_ADDRESS"
252
- ```
58
+ ## API
253
59
 
254
- For balance benchmarking, pass token addresses that implement `balanceOf(address)` on the selected
255
- chain. The script repeats those token and owner inputs, builds ghostcall batches with the public
256
- SDK encoder, and searches for the largest successful call count. The balance search is capped by
257
- both `--max-calls` and `--max-initcode-bytes`.
60
+ - `aggregateDecodedCalls()` sends a strict batch and returns decoded values.
61
+ - `aggregateCalls()` sends a batch and returns raw `{ success, returnData }` entries.
62
+ - `encodeCalls()` builds the CREATE-style `eth_call` data payload.
63
+ - `decodeResults()` parses the packed ghostcall response.
258
64
 
259
- Useful options:
65
+ Full reference: [API docs](https://ghostcall.volga.sh/api/).
260
66
 
261
- - `--mode raw|balances|all`, default `all`
262
- - `--token` and `--owner`, repeatable or comma-separated
263
- - `--block`, `--from`, `--gas`, and `--timeout-ms`
264
- - `--max-calls`, `--max-initcode-bytes`, and `--max-runtime-bytes`
265
- - `--json` for machine-readable output
67
+ ## Development
266
68
 
267
- ## Install
268
-
269
- ```bash
69
+ ```sh
270
70
  npm install
71
+ npm run build:sdk
72
+ npm run test
73
+ npm run check
271
74
  ```
272
75
 
273
- ## Build contracts
274
-
275
- ```bash
276
- npm run build:contracts
277
- ```
278
-
279
- The compiled artifacts are emitted into the standard Foundry artifact tree under `out/`.
280
- That build step also refreshes the generated SDK initcode file at
281
- [`src/sdk/generated/initcode.ts`](src/sdk/generated/initcode.ts).
282
-
283
- ## Test
76
+ Docs are built with Astro Starlight:
284
77
 
285
- ```bash
286
- npm test
78
+ ```sh
79
+ npm run docs:dev
80
+ npm run docs:build
287
81
  ```
288
82
 
289
- The test suite:
290
-
291
- - compiles the contracts with Foundry,
292
- - starts an ephemeral `anvil` instance automatically,
293
- - deploys and configures `MockContract` from Foundry artifacts,
294
- - encodes function calldata with `ox`,
295
- - executes a CREATE-style `eth_call` against Ghostcall,
296
- - dogfoods the provider-facing SDK aggregation helper,
297
- - decodes both function return data and revert data with `ox`,
298
- - verifies configurable success paths, calldata-vs-method precedence, inline failure entries, the empty-batch case, the CREATE request-size boundary, the CREATE return-size boundary, and top-level malformed-payload handling.
299
-
300
- For static TypeScript checking:
301
-
302
- ```bash
303
- npm run typecheck
304
- ```
305
-
306
- ## Design notes
307
-
308
- The implementation chooses Yul over raw bytecode because it keeps the control flow legible while
309
- still mapping one-to-one onto the EVM concepts that matter here:
310
-
311
- - `dataoffset(...)` anchors the appended payload boundary
312
- - `codecopy` streams headers and calldata directly from the appended payload
313
- - the len-first header plus a `0x0a` scratch offset lets one `mload(0x00)` yield both calldata
314
- length and the `CALL` address word without extra masking
315
- - `call` executes each subcall with zero value
316
- - `returndatacopy` packs the aggregate response into a compact binary format
317
- - `return` hands the batch result back to RPC
318
-
319
- That gives you a maintainable base version first, with a straightforward path to hand-optimizing
320
- hot spots later if initcode size becomes the bottleneck.
83
+ The repository is hosted at [github.com/volga-sh/ghostcall](https://github.com/volga-sh/ghostcall).
@@ -1,2 +1,2 @@
1
- export declare const ghostcallInitcode: "0x6020606c5b38810360135750601f19016020f35b90601682600a395f51918260a01c9060168282010193388511606757825f92838093601696600289019788940184395af1913d91617fff83116067575f839182600296600f1b1760f01b84523e0101906004565b5f80fdfe";
1
+ export declare const ghostcallInitcode: "0x5f605b5b388110600d57505ff35b90601682823980515f808260f01c80938260168701918360168a01843960501c5af13d80600f1c60565760169381600293600f1b1760f01b8152815f8483013e01019201016003565b5f80fdfe";
2
2
  //# sourceMappingURL=initcode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"initcode.d.ts","sourceRoot":"","sources":["../../../src/sdk/generated/initcode.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,EAC7B,4NAAqO,CAAC"}
1
+ {"version":3,"file":"initcode.d.ts","sourceRoot":"","sources":["../../../src/sdk/generated/initcode.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,EAC7B,0LAAmM,CAAC"}
@@ -1,4 +1,4 @@
1
1
  // This file is generated by scripts/generate-sdk-initcode.mjs.
2
2
  // Run npm run build:contracts after changing src/Ghostcall.yul.
3
- export const ghostcallInitcode = "0x6020606c5b38810360135750601f19016020f35b90601682600a395f51918260a01c9060168282010193388511606757825f92838093601696600289019788940184395af1913d91617fff83116067575f839182600296600f1b1760f01b84523e0101906004565b5f80fdfe";
3
+ export const ghostcallInitcode = "0x5f605b5b388110600d57505ff35b90601682823980515f808260f01c80938260168701918360168a01843960501c5af13d80600f1c60565760169381600293600f1b1760f01b8152815f8483013e01019201016003565b5f80fdfe";
4
4
  //# sourceMappingURL=initcode.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"initcode.js","sourceRoot":"","sources":["../../../src/sdk/generated/initcode.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,gEAAgE;AAEhE,MAAM,CAAC,MAAM,iBAAiB,GAC7B,4NAAqO,CAAC"}
1
+ {"version":3,"file":"initcode.js","sourceRoot":"","sources":["../../../src/sdk/generated/initcode.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,gEAAgE;AAEhE,MAAM,CAAC,MAAM,iBAAiB,GAC7B,0LAAmM,CAAC"}
@@ -5,6 +5,14 @@
5
5
  * SDK does not accept byte arrays or ABI fragments.
6
6
  */
7
7
  type Hex = `0x${string}`;
8
+ /**
9
+ * Hex-encoded RPC quantity prefixed with `0x`.
10
+ */
11
+ type HexQuantity = `0x${string}`;
12
+ /**
13
+ * Block reference accepted by the outer `eth_call`.
14
+ */
15
+ type GhostcallBlockReference = string | number | bigint;
8
16
  /**
9
17
  * One Ghostcall subcall entry.
10
18
  */
@@ -35,39 +43,46 @@ type GhostcallAggregateCall = GhostcallCall & {
35
43
  * a call does not explicitly opt into failure.
36
44
  */
37
45
  allowFailure?: boolean;
46
+ };
47
+ /**
48
+ * One Ghostcall subcall entry for decoded aggregate results.
49
+ */
50
+ type GhostcallDecodedCall<TResult = unknown> = GhostcallCall & {
38
51
  /**
39
- * Optional decoder for this call's successful return data.
52
+ * Decodes this call's successful return data.
40
53
  *
41
54
  * This is intentionally a caller-provided function so the SDK stays independent
42
55
  * from ABI libraries while still letting callers plug in helpers such as
43
56
  * `decodeFunctionResult` from viem or ox.
44
57
  */
45
- decodeResult?: GhostcallResultDecoder<unknown>;
58
+ decodeResult: GhostcallResultDecoder<TResult>;
46
59
  };
47
60
  /**
48
- * One Ghostcall aggregate subcall entry for decoded-results mode.
61
+ * One successful Ghostcall result entry.
49
62
  */
50
- type GhostcallDecodedAggregateCall<TResult = unknown> = GhostcallCall & {
63
+ type GhostcallSuccessResult = {
51
64
  /**
52
- * Decodes this call's successful return data.
65
+ * Indicates whether the underlying EVM `CALL` returned successfully.
66
+ *
67
+ * A `true` value means the target call returned successfully.
53
68
  */
54
- decodeResult: GhostcallResultDecoder<TResult>;
69
+ success: true;
55
70
  /**
56
- * Decoded-results mode is strict and does not return failed entries.
71
+ * Raw return data produced by the target call.
57
72
  */
58
- allowFailure?: false;
73
+ returnData: Hex;
59
74
  };
60
75
  /**
61
- * One decoded Ghostcall result entry.
76
+ * One failed Ghostcall result entry.
62
77
  */
63
- type GhostcallResult = {
78
+ type GhostcallFailedResult = {
64
79
  /**
65
80
  * Indicates whether the underlying EVM `CALL` returned successfully.
66
81
  *
67
82
  * A `false` value means the target call reverted or otherwise failed, but the
68
83
  * Ghostcall batch itself still completed successfully.
69
84
  */
70
- success: boolean;
85
+ success: false;
71
86
  /**
72
87
  * Raw return data produced by the target call.
73
88
  *
@@ -76,46 +91,63 @@ type GhostcallResult = {
76
91
  */
77
92
  returnData: Hex;
78
93
  };
94
+ /**
95
+ * One decoded Ghostcall result entry.
96
+ */
97
+ type GhostcallResult = GhostcallSuccessResult | GhostcallFailedResult;
79
98
  /**
80
99
  * Function used by {@link aggregateCalls} to turn raw successful return data into
81
100
  * a caller-chosen value.
82
101
  */
83
- type GhostcallResultDecoder<TResult> = (returnData: Hex, entry: GhostcallResult, index: number) => TResult;
102
+ type GhostcallResultDecoder<TResult> = (returnData: Hex, entry: GhostcallSuccessResult, index: number) => TResult;
84
103
  /**
85
- * One decoded aggregate result entry when a call provides `decodeResult`.
104
+ * Error thrown when a strict Ghostcall batch encounters a failed subcall.
86
105
  */
87
- type GhostcallDecodedResult<TResult> = {
88
- success: true;
89
- returnData: Hex;
90
- decodedResult: TResult;
91
- };
92
- type GhostcallAggregateResult<TCall> = TCall extends {
93
- decodeResult: GhostcallResultDecoder<infer TResult>;
94
- } ? TCall extends {
95
- allowFailure: true;
96
- } ? GhostcallDecodedResult<TResult> | GhostcallResult : GhostcallDecodedResult<TResult> : GhostcallResult;
97
- type GhostcallAggregateResults<TCalls extends readonly GhostcallAggregateCall[]> = {
98
- -readonly [Index in keyof TCalls]: GhostcallAggregateResult<TCalls[Index]>;
99
- };
100
- type GhostcallDecodedResults<TCalls extends readonly GhostcallDecodedAggregateCall[]> = {
106
+ declare class GhostcallSubcallError extends Error {
107
+ readonly index: number;
108
+ readonly call: GhostcallAggregateCall;
109
+ readonly result: GhostcallFailedResult;
110
+ constructor(index: number, call: GhostcallAggregateCall, result: GhostcallFailedResult);
111
+ }
112
+ type GhostcallDecodedResults<TCalls extends readonly GhostcallDecodedCall[]> = {
101
113
  -readonly [Index in keyof TCalls]: TCalls[Index] extends {
102
114
  decodeResult: GhostcallResultDecoder<infer TResult>;
103
115
  } ? TResult : never;
104
116
  };
105
- type GhostcallAggregateOptions = {
117
+ type GhostcallEncodeOptions = {
118
+ /**
119
+ * Maximum allowed CREATE initcode size in bytes.
120
+ *
121
+ * This applies to the full request `data`, including bundled Ghostcall
122
+ * initcode and every encoded subcall entry.
123
+ *
124
+ * Defaults to Ethereum's EIP-3860 limit of `49,152` bytes.
125
+ */
126
+ maxInitcodeBytes?: number;
127
+ };
128
+ type GhostcallEthCallOptions = {
129
+ /**
130
+ * Optional `from` address for the outer `eth_call`.
131
+ */
132
+ from?: Hex;
133
+ /**
134
+ * Optional gas limit for the outer `eth_call`.
135
+ */
136
+ gas?: HexQuantity;
106
137
  /**
107
- * Result shape returned by {@link aggregateCalls}.
138
+ * Optional block tag, hex quantity, or block number for the outer `eth_call`.
108
139
  *
109
- * Defaults to `entries`, returning Ghostcall result entries. Set to `decoded`
110
- * to return each call's decoded value directly.
140
+ * Decimal strings, numbers, and bigints are normalized to hex quantities.
141
+ * Defaults to `latest`.
111
142
  */
112
- results?: "entries";
143
+ blockTag?: GhostcallBlockReference;
113
144
  };
114
- type GhostcallDecodedAggregateOptions = {
145
+ type GhostcallAggregateOptions = GhostcallEncodeOptions & {
115
146
  /**
116
- * Return each call's decoded value directly.
147
+ * Optional outer `eth_call` controls shared by {@link aggregateCalls} and
148
+ * {@link aggregateDecodedCalls}.
117
149
  */
118
- results: "decoded";
150
+ ethCall?: GhostcallEthCallOptions;
119
151
  };
120
152
  /**
121
153
  * Minimal EIP-1193 provider shape used by the SDK.
@@ -134,9 +166,12 @@ type EIP1193ProviderWithRequestFn = {
134
166
  * by the compact binary payload for each subcall, so callers can pass it directly
135
167
  * as the `data` field of an `eth_call` request without supplying a `to` address.
136
168
  * Each encoded subcall entry uses the compact layout `[len(2)][target(20)][data]`.
169
+ * The bundled initcode assumes appended bytes follow this exact shape; this
170
+ * function is the supported boundary for producing well-formed Ghostcall payloads.
137
171
  *
138
172
  * @param calls - Ordered list of subcalls to execute. Each entry becomes one
139
173
  * Ghostcall payload segment in the same order it appears here.
174
+ * @param options - Optional encoding controls.
140
175
  *
141
176
  * @returns Full CREATE payload consisting of the bundled Ghostcall initcode plus
142
177
  * the encoded call list.
@@ -144,16 +179,18 @@ type EIP1193ProviderWithRequestFn = {
144
179
  * @throws {TypeError} If any call address or calldata value is not valid hex.
145
180
  * @throws {RangeError} If any call data exceeds the protocol `uint16` length limit
146
181
  * or if the full encoded CREATE payload would exceed the
147
- * EVM initcode size limit.
182
+ * configured initcode size limit.
148
183
  *
149
184
  * @example
150
185
  * const data = encodeCalls([
151
186
  * {
152
- * to: "0x1111111111111111111111111111111111111111",
153
- * data: "0x70a08231000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
187
+ * // USDC on Ethereum mainnet: balanceOf(Binance 14)
188
+ * to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
189
+ * data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
154
190
  * },
155
191
  * {
156
- * to: "0x2222222222222222222222222222222222222222",
192
+ * // WETH9 on Ethereum mainnet: totalSupply()
193
+ * to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
157
194
  * data: "0x18160ddd",
158
195
  * },
159
196
  * ]);
@@ -161,67 +198,98 @@ type EIP1193ProviderWithRequestFn = {
161
198
  * // Later:
162
199
  * // provider.request({ method: "eth_call", params: [{ data }, "latest"] })
163
200
  */
164
- declare function encodeCalls(calls: readonly GhostcallCall[]): Hex;
201
+ declare function encodeCalls(calls: readonly GhostcallCall[], options?: GhostcallEncodeOptions): Hex;
165
202
  /**
166
203
  * Sends a Ghostcall batch with a CREATE-style `eth_call` and decodes the result.
167
204
  *
168
205
  * This is the provider-facing counterpart to {@link encodeCalls} and
169
206
  * {@link decodeResults}. It sends the bundled Ghostcall initcode as the `data`
170
- * field of `eth_call` without a `to` address, then returns decoded result entries
171
- * in the same order as the input calls.
207
+ * field of `eth_call` without a `to` address, then returns raw result entries
208
+ * in the same order as the input calls. Request bytes are built through
209
+ * {@link encodeCalls}, so SDK callers get the supported payload validation before
210
+ * the RPC request is sent.
172
211
  *
173
212
  * By default, any failed subcall makes this method reject. Set
174
213
  * `allowFailure: true` on a call to receive that failed entry in the returned
175
- * results instead. Set `decodeResult` on a call to transform successful raw
176
- * return data, for example with `decodeFunctionResult` from an ABI library.
177
- * Pass `{ results: "decoded" }` as the third argument to receive only the
178
- * decoded values.
214
+ * results instead. Use {@link aggregateDecodedCalls} when you want a strict batch
215
+ * that returns decoded values directly. Use `options.ethCall` to forward `from`,
216
+ * `gas`, or `blockTag` to the outer `eth_call`.
179
217
  *
180
218
  * @param provider - EIP-1193-compatible provider with a `request` method.
181
219
  * @param calls - Ordered list of subcalls to execute.
182
- * @param options - Optional result-shape controls.
220
+ * @param options - Optional outer call and initcode controls.
183
221
  *
184
- * @returns Ordered decoded Ghostcall result entries.
222
+ * @returns Ordered Ghostcall result entries.
185
223
  *
186
224
  * @throws {TypeError} If inputs are not valid Ghostcall call entries or if the
187
225
  * provider returns a non-hex `eth_call` result.
188
- * @throws {RangeError} If the encoded CREATE payload exceeds protocol or EVM
189
- * size limits.
190
- * @throws {Error} If a subcall fails without `allowFailure: true`, or if the
191
- * response entry count does not match the request entry count.
226
+ * @throws {RangeError} If the encoded CREATE payload exceeds protocol or the
227
+ * configured CREATE initcode ceiling.
228
+ * @throws {GhostcallSubcallError} If a subcall fails without `allowFailure: true`.
229
+ * @throws {Error} If the response entry count does not match the request entry count.
192
230
  *
193
231
  * @example
194
232
  * const results = await aggregateCalls(provider, [
195
233
  * {
196
- * to: "0x1111111111111111111111111111111111111111",
197
- * data: "0x70a08231000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
198
- * decodeResult: (returnData) => decodeFunctionResult({
199
- * abi: erc20Abi,
200
- * functionName: "balanceOf",
201
- * data: returnData,
202
- * }),
234
+ * // USDC on Ethereum mainnet: balanceOf(Binance 14)
235
+ * to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
236
+ * data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
203
237
  * },
204
238
  * {
205
- * to: "0x2222222222222222222222222222222222222222",
239
+ * // WETH9 on Ethereum mainnet: totalSupply()
240
+ * to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
206
241
  * data: "0x18160ddd",
207
- * allowFailure: true,
208
242
  * },
209
243
  * ]);
244
+ */
245
+ declare function aggregateCalls(provider: EIP1193ProviderWithRequestFn, calls: readonly GhostcallAggregateCall[], options?: GhostcallAggregateOptions): Promise<GhostcallResult[]>;
246
+ /**
247
+ * Sends a strict Ghostcall batch and decodes each successful result entry.
248
+ *
249
+ * This is the decoded counterpart to {@link aggregateCalls}. It sends the bundled
250
+ * Ghostcall initcode as the `data` field of `eth_call` without a `to` address,
251
+ * then runs each call's `decodeResult` callback over the successful return data in
252
+ * the same order as the input calls.
253
+ *
254
+ * `aggregateDecodedCalls` is always strict. Its TypeScript input shape requires a
255
+ * `decodeResult` callback on every call and does not accept `allowFailure`.
256
+ * Any failed subcall rejects with {@link GhostcallSubcallError}. Use
257
+ * {@link aggregateCalls} if you need raw failed entries. Use `options.ethCall`
258
+ * to forward `from`, `gas`, or `blockTag` to the outer `eth_call`.
259
+ *
260
+ * @param provider - EIP-1193-compatible provider with a `request` method.
261
+ * @param calls - Ordered list of strict decoded subcalls to execute.
262
+ * @param options - Optional outer call and initcode controls.
263
+ *
264
+ * @returns Ordered list of decoded values.
265
+ *
266
+ * @throws {TypeError} If inputs are not valid Ghostcall call entries or if the
267
+ * provider returns a non-hex `eth_call` result.
268
+ * @throws {RangeError} If the encoded CREATE payload exceeds protocol or the
269
+ * configured CREATE initcode ceiling.
270
+ * @throws {GhostcallSubcallError} If any subcall fails.
271
+ * @throws {Error} If the response entry count does not match the request entry count.
272
+ *
273
+ * @example
274
+ * const erc20Abi = parseAbi([
275
+ * "function balanceOf(address account) view returns (uint256)",
276
+ * ]);
277
+ * const usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
278
+ * const owner = "0x28C6c06298d514Db089934071355E5743bf21d60";
210
279
  *
211
- * const [balance] = await aggregateCalls(provider, [
280
+ * const [balance] = await aggregateDecodedCalls(provider, [
212
281
  * {
213
- * to: "0x1111111111111111111111111111111111111111",
214
- * data: "0x70a08231000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
282
+ * to: usdc,
283
+ * data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
215
284
  * decodeResult: (returnData) => decodeFunctionResult({
216
285
  * abi: erc20Abi,
217
286
  * functionName: "balanceOf",
218
287
  * data: returnData,
219
288
  * }),
220
289
  * },
221
- * ], { results: "decoded" });
290
+ * ]);
222
291
  */
223
- declare function aggregateCalls<const TCalls extends readonly GhostcallAggregateCall[]>(provider: EIP1193ProviderWithRequestFn, calls: TCalls, options?: GhostcallAggregateOptions): Promise<GhostcallAggregateResults<TCalls>>;
224
- declare function aggregateCalls<const TCalls extends readonly GhostcallDecodedAggregateCall[]>(provider: EIP1193ProviderWithRequestFn, calls: TCalls, options: GhostcallDecodedAggregateOptions): Promise<GhostcallDecodedResults<TCalls>>;
292
+ declare function aggregateDecodedCalls<const TCalls extends readonly GhostcallDecodedCall<unknown>[]>(provider: EIP1193ProviderWithRequestFn, calls: TCalls, options?: GhostcallAggregateOptions): Promise<GhostcallDecodedResults<TCalls>>;
225
293
  /**
226
294
  * Decodes the packed result blob returned by Ghostcall.
227
295
  *
@@ -249,6 +317,6 @@ declare function aggregateCalls<const TCalls extends readonly GhostcallDecodedAg
249
317
  * // ]
250
318
  */
251
319
  declare function decodeResults(data: Hex): GhostcallResult[];
252
- export type { EIP1193ProviderWithRequestFn, GhostcallAggregateCall, GhostcallAggregateOptions, GhostcallAggregateResult, GhostcallCall, GhostcallDecodedAggregateCall, GhostcallDecodedAggregateOptions, GhostcallDecodedResult, GhostcallDecodedResults, GhostcallResult, GhostcallResultDecoder, Hex, };
253
- export { aggregateCalls, decodeResults, encodeCalls };
320
+ export type { EIP1193ProviderWithRequestFn, GhostcallAggregateCall, GhostcallAggregateOptions, GhostcallBlockReference, GhostcallCall, GhostcallDecodedCall, GhostcallDecodedResults, GhostcallEncodeOptions, GhostcallEthCallOptions, GhostcallFailedResult, GhostcallResult, GhostcallResultDecoder, GhostcallSuccessResult, Hex, HexQuantity, };
321
+ export { aggregateCalls, aggregateDecodedCalls, decodeResults, encodeCalls, GhostcallSubcallError, };
254
322
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"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"}
package/dist/sdk/index.js CHANGED
@@ -1,9 +1,25 @@
1
1
  import { ghostcallInitcode } from "./generated/initcode.js";
2
+ /**
3
+ * Error thrown when a strict Ghostcall batch encounters a failed subcall.
4
+ */
5
+ class GhostcallSubcallError extends Error {
6
+ index;
7
+ call;
8
+ result;
9
+ constructor(index, call, result) {
10
+ super(`Ghostcall subcall ${index} failed`);
11
+ this.name = "GhostcallSubcallError";
12
+ this.index = index;
13
+ this.call = call;
14
+ this.result = result;
15
+ Object.setPrototypeOf(this, new.target.prototype);
16
+ }
17
+ }
2
18
  const addressHexLength = 40;
3
19
  const encodedHeaderHexLength = 4;
4
20
  const maxCalldataSize = 0xffff;
5
21
  const encodedCallHeaderSize = 0x16;
6
- const maxCreateInitcodeSize = 0xc000;
22
+ const defaultMaxCreateInitcodeSize = 0xc000;
7
23
  const successFlagMask = 0x8000;
8
24
  const returnDataLengthMask = 0x7fff;
9
25
  const bundledInitcodeSize = byteLength(ghostcallInitcode);
@@ -15,9 +31,12 @@ const bundledInitcodeSize = byteLength(ghostcallInitcode);
15
31
  * by the compact binary payload for each subcall, so callers can pass it directly
16
32
  * as the `data` field of an `eth_call` request without supplying a `to` address.
17
33
  * Each encoded subcall entry uses the compact layout `[len(2)][target(20)][data]`.
34
+ * The bundled initcode assumes appended bytes follow this exact shape; this
35
+ * function is the supported boundary for producing well-formed Ghostcall payloads.
18
36
  *
19
37
  * @param calls - Ordered list of subcalls to execute. Each entry becomes one
20
38
  * Ghostcall payload segment in the same order it appears here.
39
+ * @param options - Optional encoding controls.
21
40
  *
22
41
  * @returns Full CREATE payload consisting of the bundled Ghostcall initcode plus
23
42
  * the encoded call list.
@@ -25,16 +44,18 @@ const bundledInitcodeSize = byteLength(ghostcallInitcode);
25
44
  * @throws {TypeError} If any call address or calldata value is not valid hex.
26
45
  * @throws {RangeError} If any call data exceeds the protocol `uint16` length limit
27
46
  * or if the full encoded CREATE payload would exceed the
28
- * EVM initcode size limit.
47
+ * configured initcode size limit.
29
48
  *
30
49
  * @example
31
50
  * const data = encodeCalls([
32
51
  * {
33
- * to: "0x1111111111111111111111111111111111111111",
34
- * data: "0x70a08231000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
52
+ * // USDC on Ethereum mainnet: balanceOf(Binance 14)
53
+ * to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
54
+ * data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
35
55
  * },
36
56
  * {
37
- * to: "0x2222222222222222222222222222222222222222",
57
+ * // WETH9 on Ethereum mainnet: totalSupply()
58
+ * to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
38
59
  * data: "0x18160ddd",
39
60
  * },
40
61
  * ]);
@@ -42,9 +63,13 @@ const bundledInitcodeSize = byteLength(ghostcallInitcode);
42
63
  * // Later:
43
64
  * // provider.request({ method: "eth_call", params: [{ data }, "latest"] })
44
65
  */
45
- function encodeCalls(calls) {
66
+ function encodeCalls(calls, options = {}) {
46
67
  const encodedParts = [ghostcallInitcode.slice(2)];
68
+ const maxInitcodeBytes = resolveMaxInitcodeBytes(options.maxInitcodeBytes);
47
69
  let totalEncodedSize = bundledInitcodeSize;
70
+ if (totalEncodedSize > maxInitcodeBytes) {
71
+ throw new RangeError(`encoded Ghostcall initcode exceeds the ${maxInitcodeBytes}-byte CREATE initcode limit`);
72
+ }
48
73
  for (const [index, call] of calls.entries()) {
49
74
  assertAddress(call.to, `calls[${index}].to`);
50
75
  const calldata = assertHex(call.data, `calls[${index}].data`);
@@ -53,8 +78,8 @@ function encodeCalls(calls) {
53
78
  throw new RangeError(`calls[${index}].data exceeds the ${maxCalldataSize}-byte calldata limit`);
54
79
  }
55
80
  totalEncodedSize += encodedCallHeaderSize + calldataSize;
56
- if (totalEncodedSize > maxCreateInitcodeSize) {
57
- throw new RangeError(`encoded Ghostcall initcode exceeds the ${maxCreateInitcodeSize}-byte CREATE initcode limit`);
81
+ if (totalEncodedSize > maxInitcodeBytes) {
82
+ throw new RangeError(`encoded Ghostcall initcode exceeds the ${maxInitcodeBytes}-byte CREATE initcode limit`);
58
83
  }
59
84
  encodedParts.push(calldataSize.toString(16).padStart(4, "0"));
60
85
  encodedParts.push(call.to.slice(2));
@@ -62,54 +87,130 @@ function encodeCalls(calls) {
62
87
  }
63
88
  return `0x${encodedParts.join("")}`;
64
89
  }
65
- async function aggregateCalls(provider, calls, options = {}) {
66
- let decodedCalls;
67
- if (options.results === "decoded") {
68
- for (const [index, call] of calls.entries()) {
69
- if (call.allowFailure === true) {
70
- throw new TypeError(`calls[${index}].allowFailure cannot be true when results is decoded`);
71
- }
72
- if (call.decodeResult === undefined) {
73
- throw new TypeError(`calls[${index}].decodeResult is required when results is decoded`);
74
- }
75
- }
76
- decodedCalls = calls;
90
+ /**
91
+ * Sends a Ghostcall batch with a CREATE-style `eth_call` and decodes the result.
92
+ *
93
+ * This is the provider-facing counterpart to {@link encodeCalls} and
94
+ * {@link decodeResults}. It sends the bundled Ghostcall initcode as the `data`
95
+ * field of `eth_call` without a `to` address, then returns raw result entries
96
+ * in the same order as the input calls. Request bytes are built through
97
+ * {@link encodeCalls}, so SDK callers get the supported payload validation before
98
+ * the RPC request is sent.
99
+ *
100
+ * By default, any failed subcall makes this method reject. Set
101
+ * `allowFailure: true` on a call to receive that failed entry in the returned
102
+ * results instead. Use {@link aggregateDecodedCalls} when you want a strict batch
103
+ * that returns decoded values directly. Use `options.ethCall` to forward `from`,
104
+ * `gas`, or `blockTag` to the outer `eth_call`.
105
+ *
106
+ * @param provider - EIP-1193-compatible provider with a `request` method.
107
+ * @param calls - Ordered list of subcalls to execute.
108
+ * @param options - Optional outer call and initcode controls.
109
+ *
110
+ * @returns Ordered Ghostcall result entries.
111
+ *
112
+ * @throws {TypeError} If inputs are not valid Ghostcall call entries or if the
113
+ * provider returns a non-hex `eth_call` result.
114
+ * @throws {RangeError} If the encoded CREATE payload exceeds protocol or the
115
+ * configured CREATE initcode ceiling.
116
+ * @throws {GhostcallSubcallError} If a subcall fails without `allowFailure: true`.
117
+ * @throws {Error} If the response entry count does not match the request entry count.
118
+ *
119
+ * @example
120
+ * const results = await aggregateCalls(provider, [
121
+ * {
122
+ * // USDC on Ethereum mainnet: balanceOf(Binance 14)
123
+ * to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
124
+ * data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
125
+ * },
126
+ * {
127
+ * // WETH9 on Ethereum mainnet: totalSupply()
128
+ * to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
129
+ * data: "0x18160ddd",
130
+ * },
131
+ * ]);
132
+ */
133
+ async function aggregateCalls(provider, calls, options) {
134
+ const resolvedOptions = options ?? {};
135
+ const data = encodeCalls(calls, resolvedOptions);
136
+ const ethCall = { data };
137
+ const blockTag = normalizeBlockTag(resolvedOptions.ethCall?.blockTag ?? "latest", "options.ethCall.blockTag");
138
+ if (resolvedOptions.ethCall?.from !== undefined) {
139
+ assertAddress(resolvedOptions.ethCall.from, "options.ethCall.from");
140
+ ethCall.from = resolvedOptions.ethCall.from;
141
+ }
142
+ if (resolvedOptions.ethCall?.gas !== undefined) {
143
+ ethCall.gas = assertHexQuantity(resolvedOptions.ethCall.gas, "options.ethCall.gas");
77
144
  }
78
- const data = encodeCalls(calls);
79
145
  const result = await provider.request({
80
146
  method: "eth_call",
81
- params: [{ data }, "latest"],
147
+ params: [ethCall, blockTag],
82
148
  });
83
149
  const entries = decodeResults(assertHex(result, "eth_call result"));
84
150
  if (entries.length !== calls.length) {
85
151
  throw new Error(`Ghostcall returned ${entries.length} result entries for ${calls.length} calls`);
86
152
  }
87
153
  for (const [index, entry] of entries.entries()) {
88
- if (!entry.success && calls[index]?.allowFailure !== true) {
89
- throw new Error(`Ghostcall subcall ${index} failed`);
154
+ const call = calls[index];
155
+ if (!entry.success && call.allowFailure !== true) {
156
+ throw new GhostcallSubcallError(index, call, entry);
90
157
  }
91
158
  }
92
- if (decodedCalls !== undefined) {
93
- return entries.map((entry, index) => {
94
- const call = decodedCalls[index];
95
- if (call === undefined) {
96
- throw new Error("Ghostcall decoded call invariant failed");
97
- }
98
- return call.decodeResult(entry.returnData, entry, index);
99
- });
100
- }
101
- const decodedEntries = entries.map((entry, index) => {
102
- const decodeResult = calls[index]?.decodeResult;
103
- if (!entry.success || decodeResult === undefined) {
104
- return entry;
105
- }
106
- return {
107
- ...entry,
108
- success: true,
109
- decodedResult: decodeResult(entry.returnData, entry, index),
110
- };
159
+ return entries;
160
+ }
161
+ /**
162
+ * Sends a strict Ghostcall batch and decodes each successful result entry.
163
+ *
164
+ * This is the decoded counterpart to {@link aggregateCalls}. It sends the bundled
165
+ * Ghostcall initcode as the `data` field of `eth_call` without a `to` address,
166
+ * then runs each call's `decodeResult` callback over the successful return data in
167
+ * the same order as the input calls.
168
+ *
169
+ * `aggregateDecodedCalls` is always strict. Its TypeScript input shape requires a
170
+ * `decodeResult` callback on every call and does not accept `allowFailure`.
171
+ * Any failed subcall rejects with {@link GhostcallSubcallError}. Use
172
+ * {@link aggregateCalls} if you need raw failed entries. Use `options.ethCall`
173
+ * to forward `from`, `gas`, or `blockTag` to the outer `eth_call`.
174
+ *
175
+ * @param provider - EIP-1193-compatible provider with a `request` method.
176
+ * @param calls - Ordered list of strict decoded subcalls to execute.
177
+ * @param options - Optional outer call and initcode controls.
178
+ *
179
+ * @returns Ordered list of decoded values.
180
+ *
181
+ * @throws {TypeError} If inputs are not valid Ghostcall call entries or if the
182
+ * provider returns a non-hex `eth_call` result.
183
+ * @throws {RangeError} If the encoded CREATE payload exceeds protocol or the
184
+ * configured CREATE initcode ceiling.
185
+ * @throws {GhostcallSubcallError} If any subcall fails.
186
+ * @throws {Error} If the response entry count does not match the request entry count.
187
+ *
188
+ * @example
189
+ * const erc20Abi = parseAbi([
190
+ * "function balanceOf(address account) view returns (uint256)",
191
+ * ]);
192
+ * const usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
193
+ * const owner = "0x28C6c06298d514Db089934071355E5743bf21d60";
194
+ *
195
+ * const [balance] = await aggregateDecodedCalls(provider, [
196
+ * {
197
+ * to: usdc,
198
+ * data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
199
+ * decodeResult: (returnData) => decodeFunctionResult({
200
+ * abi: erc20Abi,
201
+ * functionName: "balanceOf",
202
+ * data: returnData,
203
+ * }),
204
+ * },
205
+ * ]);
206
+ */
207
+ async function aggregateDecodedCalls(provider, calls, options) {
208
+ const entries = await aggregateCalls(provider, calls, options);
209
+ return entries.map((entry, index) => {
210
+ const call = calls[index];
211
+ const successEntry = entry;
212
+ return call.decodeResult(successEntry.returnData, successEntry, index);
111
213
  });
112
- return decodedEntries;
113
214
  }
114
215
  /**
115
216
  * Decodes the packed result blob returned by Ghostcall.
@@ -211,6 +312,80 @@ function assertHex(value, label) {
211
312
  }
212
313
  return value;
213
314
  }
315
+ /**
316
+ * Validates that a value is an RPC hex quantity.
317
+ *
318
+ * @param value - Unknown input to validate.
319
+ * @param label - Field name used in thrown error messages.
320
+ * @returns The validated value narrowed to {@link HexQuantity}.
321
+ * @throws {TypeError} If the value is not a valid `0x`-prefixed quantity.
322
+ *
323
+ * @internal
324
+ */
325
+ function assertHexQuantity(value, label) {
326
+ if (typeof value !== "string") {
327
+ throw new TypeError(`${label} must be a hex quantity string`);
328
+ }
329
+ if (!/^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$/.test(value)) {
330
+ throw new TypeError(`${label} must be a 0x-prefixed hex quantity`);
331
+ }
332
+ return value;
333
+ }
334
+ /**
335
+ * Normalizes a block reference into the RPC shape expected by `eth_call`.
336
+ *
337
+ * @param value - Block reference to normalize.
338
+ * @param label - Field name used in thrown error messages.
339
+ * @returns Normalized block reference.
340
+ * @throws {TypeError} If the value is not a supported block reference.
341
+ *
342
+ * @internal
343
+ */
344
+ function normalizeBlockTag(value, label) {
345
+ if (typeof value === "number") {
346
+ if (!Number.isSafeInteger(value) || value < 0) {
347
+ throw new TypeError(`${label} must be a non-negative safe integer, bigint, or non-empty string`);
348
+ }
349
+ return `0x${value.toString(16)}`;
350
+ }
351
+ if (typeof value === "bigint") {
352
+ if (value < 0n) {
353
+ throw new TypeError(`${label} must be a non-negative safe integer, bigint, or non-empty string`);
354
+ }
355
+ return `0x${value.toString(16)}`;
356
+ }
357
+ if (typeof value !== "string" || value.length === 0) {
358
+ throw new TypeError(`${label} must be a non-negative safe integer, bigint, or non-empty string`);
359
+ }
360
+ if (/^-?[0-9]+$/.test(value)) {
361
+ if (value.startsWith("-")) {
362
+ throw new TypeError(`${label} must be a non-negative safe integer, bigint, or non-empty string`);
363
+ }
364
+ return `0x${BigInt(value).toString(16)}`;
365
+ }
366
+ if (value.startsWith("0x") || value.startsWith("0X")) {
367
+ return assertHexQuantity(`0x${value.slice(2)}`, label);
368
+ }
369
+ return value;
370
+ }
371
+ /**
372
+ * Resolves the active CREATE initcode ceiling.
373
+ *
374
+ * @param value - Optional caller override.
375
+ * @returns Active initcode ceiling in bytes.
376
+ * @throws {TypeError} If the override is not a non-negative safe integer.
377
+ *
378
+ * @internal
379
+ */
380
+ function resolveMaxInitcodeBytes(value) {
381
+ if (value === undefined) {
382
+ return defaultMaxCreateInitcodeSize;
383
+ }
384
+ if (!Number.isSafeInteger(value) || value < 0) {
385
+ throw new TypeError("options.maxInitcodeBytes must be a non-negative safe integer");
386
+ }
387
+ return value;
388
+ }
214
389
  /**
215
390
  * Returns the byte length of a validated hex string.
216
391
  *
@@ -222,5 +397,5 @@ function assertHex(value, label) {
222
397
  function byteLength(value) {
223
398
  return (value.length - 2) / 2;
224
399
  }
225
- export { aggregateCalls, decodeResults, encodeCalls };
400
+ export { aggregateCalls, aggregateDecodedCalls, decodeResults, encodeCalls, GhostcallSubcallError, };
226
401
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@volga-sh/evm-ghostcall",
3
- "version": "0.0.1",
4
- "description": "Zero-deployment batching program and TypeScript SDK for CREATE-style eth_call requests.",
3
+ "version": "0.0.3",
4
+ "description": "Batch EVM blockchain reads without deployment dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/volga-sh/ghostcall/issues"
13
13
  },
14
- "homepage": "https://github.com/volga-sh/ghostcall#readme",
14
+ "homepage": "https://ghostcall.volga.sh",
15
15
  "main": "./dist/sdk/index.js",
16
16
  "types": "./dist/sdk/index.d.ts",
17
17
  "exports": {
@@ -34,6 +34,9 @@
34
34
  "build:sdk": "npm run build:contracts && tsc --project tsconfig.build.json",
35
35
  "check": "biome check .",
36
36
  "check:fix": "biome check --write .",
37
+ "docs:build": "npm run build --prefix docs",
38
+ "docs:dev": "npm run dev --prefix docs",
39
+ "docs:preview": "npm run preview --prefix docs",
37
40
  "check:sdk:initcode": "git diff --exit-code -- src/sdk/generated/initcode.ts",
38
41
  "format": "biome format --write .",
39
42
  "format:check": "biome format .",
package/src/Ghostcall.yul CHANGED
@@ -42,8 +42,9 @@ object "Ghostcall" {
42
42
  // - append (success, returndata) to the response buffer
43
43
  // - continue until the payload is fully consumed
44
44
  //
45
- // The SDK is expected to validate most caller-facing invariants ahead of time. The checks
46
- // left in this file exist only to protect parser correctness and response packing.
45
+ // The SDK is expected to validate caller-facing input invariants ahead of time. The only
46
+ // top-level check left here protects response packing, because returndata size is learned
47
+ // from the EVM after each CALL.
47
48
 
48
49
  // dataoffset("user_payload_anchor") is the byte offset of the empty data section declared at
49
50
  // the bottom of this file. Because that data section is placed after the code, its offset is
@@ -52,51 +53,34 @@ object "Ghostcall" {
52
53
  let payloadCursor := dataoffset("user_payload_anchor")
53
54
 
54
55
  // Memory layout used by this program:
55
- // - 0x00..0x1f: scratch space for reading the current entry header
56
- // - 0x20..... : output buffer that will become the eth_call return value
56
+ // - 0x00..writePtr: finalized output buffer that will become the eth_call return value
57
+ // - writePtr..writePtr+0x1f: scratch space for reading the current entry header
57
58
  //
58
- // writePtr always points to "where the next result entry should be written".
59
- let writePtr := 0x20
60
-
61
- // Infinite loop with an explicit break once all payload bytes are consumed.
62
- for {} 1 {} {
63
- if eq(payloadCursor, codesize()) {
64
- break
65
- }
66
-
67
- // Read the 22-byte fixed-size entry header into scratch memory starting at 0x0a rather
68
- // than 0x00.
69
- //
70
- // Why 0x0a?
71
- // - the header layout is [len(2)][target(20)]
72
- // - placing the first header byte at memory offset 10 makes the 20-byte target end
73
- // exactly at byte 31 of the 32-byte word loaded from mload(0x00)
74
- // - that means one mload gives us:
75
- // [10 zero bytes][2-byte len][20-byte target]
76
- // - so shr(160, headerWord) yields calldata length
77
- // - and headerWord itself already has the target in the low 20 bytes for CALL
59
+ // writePtr always points to where the next result entry starts. The entry's memory is
60
+ // scratch until CALL completes, then the packed result overwrites that same region.
61
+ let writePtr := 0x00
62
+
63
+ // Process entries until the cursor reaches the end of the CREATE payload. SDK-generated
64
+ // payloads always land exactly on codesize(); raw malformed trailing bytes are outside the
65
+ // supported boundary and are not checked here.
66
+ for {} lt(payloadCursor, codesize()) {} {
67
+ // Read the 22-byte fixed-size entry header into scratch memory at writePtr.
78
68
  //
79
- // CODECOPY pads with zeros if it reads past the end of code. That is why we still need
80
- // an explicit bounds check later: without it, a truncated entry would silently decode as
81
- // zeros instead of failing.
82
- codecopy(0x0a, payloadCursor, 0x16)
69
+ // The header layout is [len(2)][target(20)]. One mload gives us:
70
+ // [2-byte len][20-byte target][10 trailing bytes]
71
+ codecopy(writePtr, payloadCursor, 0x16)
83
72
 
84
- let headerWord := mload(0x00)
73
+ let headerWord := mload(writePtr)
85
74
 
86
- // The high 2 non-zero bytes hold the big-endian uint16 calldata length.
87
- let calldataSize := shr(160, headerWord)
88
- let nextCursor := add(add(payloadCursor, 0x16), calldataSize)
75
+ // The high 2 bytes hold the big-endian uint16 calldata length. The target occupies the
76
+ // next 20 bytes, so shr(80, headerWord) yields the address for CALL.
77
+ let calldataSize := shr(240, headerWord)
89
78
 
90
- // Reject truncated entries. This single check covers both:
91
- // - not enough bytes for the 22-byte header
92
- // - not enough bytes for the calldata that the header claims exists
93
- if gt(nextCursor, codesize()) {
94
- revert(0x00, 0x00)
95
- }
96
-
97
- // The next result entry will be written at writePtr. Its first 2 bytes are the packed
98
- // header, so the calldata scratch area can safely start immediately after that header.
99
- let calldataPtr := add(writePtr, 0x02)
79
+ // Put calldata after the 22-byte input header scratch. The returned entry later uses
80
+ // only writePtr..writePtr+0x01 for its packed header and writePtr+0x02 onward for
81
+ // returndata, so this staging area can be safely overwritten after CALL.
82
+ let calldataPtr := add(writePtr, 0x16)
83
+ let returndataPtr := add(writePtr, 0x02)
100
84
 
101
85
  // Copy just this call's calldata into memory so CALL can read it.
102
86
  codecopy(calldataPtr, add(payloadCursor, 0x16), calldataSize)
@@ -107,21 +91,15 @@ object "Ghostcall" {
107
91
  // - calldata in memory at calldataPtr
108
92
  // - no output buffer yet, because we do not know returndata size in advance
109
93
  //
110
- // CALL only cares about the low 20 bytes of its address argument, so headerWord can be
111
- // passed directly: the target is already sitting there after the 0x0a codecopy trick.
112
- let success := call(gas(), headerWord, 0, calldataPtr, calldataSize, 0, 0)
94
+ let success := call(gas(), shr(80, headerWord), 0, calldataPtr, calldataSize, 0, 0)
113
95
  let returndataSize := returndatasize()
114
96
 
115
97
  // The packed result header has 15 returndata length bits; bit 15 is the success flag.
116
98
  // Revert rather than letting oversized returndata collide with the success bit.
117
- if gt(returndataSize, 0x7fff) {
99
+ if shr(15, returndataSize) {
118
100
  revert(0x00, 0x00)
119
101
  }
120
102
 
121
- // Compute where the next result entry would begin after writing:
122
- // 2-byte packed header + returndata bytes
123
- let nextWritePtr := add(add(writePtr, 0x02), returndataSize)
124
-
125
103
  // Intentionally do not enforce an aggregate response-size cap here. CREATE-style
126
104
  // execution already treats returned bytes as would-be runtime code, so the active
127
105
  // chain/client/RPC environment will reject oversized responses according to its own
@@ -135,17 +113,17 @@ object "Ghostcall" {
135
113
  mstore(writePtr, shl(240, or(shl(15, success), returndataSize)))
136
114
 
137
115
  // Append the raw returndata bytes immediately after the 2-byte header.
138
- returndatacopy(add(writePtr, 0x02), 0, returndataSize)
116
+ returndatacopy(returndataPtr, 0, returndataSize)
139
117
 
140
118
  // Advance both cursors:
141
119
  // - writePtr moves to the start of the next result entry
142
120
  // - payloadCursor moves to the next input entry
143
- writePtr := nextWritePtr
144
- payloadCursor := nextCursor
121
+ writePtr := add(returndataPtr, returndataSize)
122
+ payloadCursor := add(payloadCursor, add(0x16, calldataSize))
145
123
  }
146
124
 
147
125
  // Return exactly the bytes that were written to the response buffer.
148
- return(0x20, sub(writePtr, 0x20))
126
+ return(0x00, writePtr)
149
127
 
150
128
  }
151
129