@volga-sh/evm-ghostcall 0.0.4 → 0.0.6

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,11 +1,10 @@
1
1
  # ghostcall
2
2
 
3
- `ghostcall` batches EVM contract reads without deploying a Multicall contract.
3
+ `ghostcall` batches EVM contract reads into one `eth_call` without deploying a
4
+ Multicall contract.
4
5
 
5
- ## Documentation
6
-
7
- Start at [ghostcall.volga.sh](https://ghostcall.volga.sh) for the setup guide,
8
- recipes, API reference, protocol, and size limits.
6
+ **Documentation: [ghostcall.volga.sh](https://ghostcall.volga.sh)**: getting
7
+ started, API reference, protocol, and size limits.
9
8
 
10
9
  ## Install
11
10
 
@@ -13,94 +12,24 @@ recipes, API reference, protocol, and size limits.
13
12
  npm install @volga-sh/evm-ghostcall
14
13
  ```
15
14
 
16
- ## Quick start
17
-
18
- This example uses viem for its client and ABI definition. ghostcall uses ox
19
- internally to encode arguments and decode results:
20
-
21
- ```sh
22
- npm install viem
23
- ```
15
+ ## Example
24
16
 
25
17
  ```ts
26
18
  import { aggregateDecodedCalls } from "@volga-sh/evm-ghostcall";
27
- import {
28
- createPublicClient,
29
- http,
30
- parseAbi,
31
- } from "viem";
19
+ import { createPublicClient, http, parseAbi } from "viem";
32
20
  import { mainnet } from "viem/chains";
33
21
 
34
- const client = createPublicClient({
35
- chain: mainnet,
36
- transport: http(),
37
- });
38
-
22
+ const client = createPublicClient({ chain: mainnet, transport: http() });
39
23
  const abi = parseAbi(["function totalSupply() view returns (uint256)"]);
40
- const token = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
24
+ const weth = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
41
25
 
42
26
  const [totalSupply] = await aggregateDecodedCalls(client, [
43
- {
44
- to: token,
45
- abi,
46
- functionName: "totalSupply",
47
- },
27
+ { to: weth, abi, functionName: "totalSupply" },
48
28
  ]);
49
29
  // totalSupply is inferred as bigint.
50
30
  ```
51
31
 
52
- Function names, arguments, and results are inferred from the ABI. Functions
53
- with inputs require `args`, for example `args: [owner]` for `balanceOf`.
54
- Keep ABIs literal with `as const`, viem's `parseAbi`, or ox's `Abi.from`.
55
-
56
- For already-encoded calldata, use the raw API:
57
-
58
- ```ts
59
- import { aggregateCalls } from "@volga-sh/evm-ghostcall";
60
-
61
- const results = await aggregateCalls(client, [
62
- { to: token, data: "0x18160ddd" },
63
- ]);
64
- // [{ success: true, returnData: "0x..." }]
65
- ```
66
-
67
- `aggregateDecodedCalls()` also accepts raw `data` with a custom `decodeResult`
68
- callback, including in the same batch as ABI calls. Each entry uses either ABI
69
- fields or raw calldata; TypeScript rejects entries that mix the two forms.
70
-
71
- See [Getting Started](https://ghostcall.volga.sh/getting-started/) for a complete
72
- two-call walkthrough.
73
-
74
- ## API
75
-
76
- - `aggregateDecodedCalls()` accepts ABI calls or custom decoders and returns a typed result tuple.
77
- - `aggregateCalls()` sends calls and returns raw success or failure results.
78
- - `encodeCalls()` builds request data for an `eth_call` without `to`.
79
- - `decodeResults()` parses a raw ghostcall response.
80
-
81
- Read the [API reference](https://ghostcall.volga.sh/api/) for signatures,
82
- options, return types, and errors.
83
-
84
- The public type surface contains seven types, including ghostcall's own `Hex`.
85
- `GhostcallAggregateCall` is merged into `GhostcallCall`. See
86
- [type import migration](https://ghostcall.volga.sh/api/types/#migrating-type-imports)
87
- for the removed helper aliases. Runtime exports are unchanged.
88
-
89
32
  ## Development
90
33
 
91
- ```sh
92
- npm install
93
- npm run build:sdk
94
- npm run test
95
- npm run check
96
- ```
97
-
98
- To work on the documentation:
99
-
100
- ```sh
101
- npm run docs:dev
102
- npm run docs:build
103
- ```
104
-
105
- The source is hosted at
106
- [github.com/volga-sh/ghostcall](https://github.com/volga-sh/ghostcall).
34
+ See [Development](https://ghostcall.volga.sh/development/) for setup, the Yul
35
+ workflow, tests, and benchmarks.
package/dist/sdk/abi.d.ts CHANGED
@@ -32,8 +32,11 @@ type CallArguments<TCall extends GhostcallAbiCall> = TCall extends {
32
32
  type ResolvedFunction<TCall extends GhostcallAbiCall> = AbiItem.fromAbi.Options<TCall["abi"], CallFunctionName<TCall>> extends AbiItem.fromAbi.Options<TCall["abi"], CallFunctionName<TCall>, infer TArgs> ? AbiItem.fromAbi.ReturnType<TCall["abi"], CallFunctionName<TCall>, Extract<CallArguments<TCall>, TArgs>, AbiFunction.AbiFunction> : never;
33
33
  /** The decoded result of an ABI call, including argument-selected overloads. */
34
34
  type GhostcallAbiResult<TCall extends GhostcallAbiCall> = AbiFunction.decodeResult.ReturnType<Extract<ResolvedFunction<TCall>, AbiFunction.AbiFunction>>;
35
- /** Bind encoding and decoding to the same resolved ABI function. */
36
- declare function prepareAbiCall(call: GhostcallAbiCall): GhostcallDecodedCall;
35
+ /**
36
+ * Turn ABI calls into raw decoded calls that encode and decode with the same
37
+ * resolved function. Raw decoded calls pass through unchanged.
38
+ */
39
+ declare function prepareDecodedCalls(calls: readonly (GhostcallAbiCall | GhostcallDecodedCall)[]): GhostcallDecodedCall[];
37
40
  export type { GhostcallAbiCall, GhostcallAbiResult };
38
- export { prepareAbiCall };
41
+ export { prepareDecodedCalls };
39
42
  //# sourceMappingURL=abi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"abi.d.ts","sourceRoot":"","sources":["../../src/sdk/abi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,WAAW,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,KAAK,OAAO,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAG5D,KAAK,YAAY,CAAC,SAAS,SAAS,WAAW,CAAC,WAAW,IAC1D,SAAS,SAAS,WAAW,CAAC,WAAW,GACtC;IACA,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;CAChC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,SAAS,EAAE,GACzC;IAAE,IAAI,CAAC,EAAE,SAAS,EAAE,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC,GACnE,KAAK,CAAC;AAEV;;;GAGG;AACH,KAAK,gBAAgB,CAAC,IAAI,SAAS,GAAG,GAAG,GAAG,IAAI;IAC/C,EAAE,EAAE,GAAG,CAAC;IACR,GAAG,EAAE,IAAI,CAAC;IACV,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,YAAY,CAAC,EAAE,KAAK,CAAC;CACrB,GAAG,CAAC,GAAG,SAAS,IAAI,GAClB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CAAE,GACnD,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC,CAAC,CAAC;AAE9D,KAAK,gBAAgB,CAAC,KAAK,SAAS,gBAAgB,IAAI,KAAK,CAAC,cAAc,CAAC,GAC5E,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAEhC,KAAK,aAAa,CAAC,KAAK,SAAS,gBAAgB,IAAI,KAAK,SAAS;IAClE,IAAI,EAAE,MAAM,KAAK,SAAS,SAAS,OAAO,EAAE,CAAC;CAC7C,GACE,KAAK,GACL,SAAS,EAAE,CAAC;AAIf,KAAK,gBAAgB,CAAC,KAAK,SAAS,gBAAgB,IACnD,OAAO,CAAC,OAAO,CAAC,OAAO,CACtB,KAAK,CAAC,KAAK,CAAC,EACZ,gBAAgB,CAAC,KAAK,CAAC,CACvB,SAAS,OAAO,CAAC,OAAO,CAAC,OAAO,CAChC,KAAK,CAAC,KAAK,CAAC,EACZ,gBAAgB,CAAC,KAAK,CAAC,EACvB,MAAM,KAAK,CACX,GACE,OAAO,CAAC,OAAO,CAAC,UAAU,CAC1B,KAAK,CAAC,KAAK,CAAC,EACZ,gBAAgB,CAAC,KAAK,CAAC,EACvB,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,EACpC,WAAW,CAAC,WAAW,CACvB,GACA,KAAK,CAAC;AAEV,gFAAgF;AAChF,KAAK,kBAAkB,CAAC,KAAK,SAAS,gBAAgB,IACrD,WAAW,CAAC,YAAY,CAAC,UAAU,CAClC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,CACzD,CAAC;AAEH,oEAAoE;AACpE,iBAAS,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,oBAAoB,CAoBpE;AAED,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"abi.d.ts","sourceRoot":"","sources":["../../src/sdk/abi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,WAAW,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,KAAK,OAAO,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAG5D,KAAK,YAAY,CAAC,SAAS,SAAS,WAAW,CAAC,WAAW,IAC1D,SAAS,SAAS,WAAW,CAAC,WAAW,GACtC;IACA,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;CAChC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,SAAS,EAAE,GACzC;IAAE,IAAI,CAAC,EAAE,SAAS,EAAE,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC,GACnE,KAAK,CAAC;AAEV;;;GAGG;AACH,KAAK,gBAAgB,CAAC,IAAI,SAAS,GAAG,GAAG,GAAG,IAAI;IAC/C,EAAE,EAAE,GAAG,CAAC;IACR,GAAG,EAAE,IAAI,CAAC;IACV,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,YAAY,CAAC,EAAE,KAAK,CAAC;CACrB,GAAG,CAAC,GAAG,SAAS,IAAI,GAClB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CAAE,GACnD,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC,CAAC,CAAC;AAE9D,KAAK,gBAAgB,CAAC,KAAK,SAAS,gBAAgB,IAAI,KAAK,CAAC,cAAc,CAAC,GAC5E,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAEhC,KAAK,aAAa,CAAC,KAAK,SAAS,gBAAgB,IAAI,KAAK,SAAS;IAClE,IAAI,EAAE,MAAM,KAAK,SAAS,SAAS,OAAO,EAAE,CAAC;CAC7C,GACE,KAAK,GACL,SAAS,EAAE,CAAC;AAIf,KAAK,gBAAgB,CAAC,KAAK,SAAS,gBAAgB,IACnD,OAAO,CAAC,OAAO,CAAC,OAAO,CACtB,KAAK,CAAC,KAAK,CAAC,EACZ,gBAAgB,CAAC,KAAK,CAAC,CACvB,SAAS,OAAO,CAAC,OAAO,CAAC,OAAO,CAChC,KAAK,CAAC,KAAK,CAAC,EACZ,gBAAgB,CAAC,KAAK,CAAC,EACvB,MAAM,KAAK,CACX,GACE,OAAO,CAAC,OAAO,CAAC,UAAU,CAC1B,KAAK,CAAC,KAAK,CAAC,EACZ,gBAAgB,CAAC,KAAK,CAAC,EACvB,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,EACpC,WAAW,CAAC,WAAW,CACvB,GACA,KAAK,CAAC;AAEV,gFAAgF;AAChF,KAAK,kBAAkB,CAAC,KAAK,SAAS,gBAAgB,IACrD,WAAW,CAAC,YAAY,CAAC,UAAU,CAClC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,CACzD,CAAC;AAOH;;;GAGG;AACH,iBAAS,mBAAmB,CAC3B,KAAK,EAAE,SAAS,CAAC,gBAAgB,GAAG,oBAAoB,CAAC,EAAE,GACzD,oBAAoB,EAAE,CAoBxB;AA0BD,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
package/dist/sdk/abi.js CHANGED
@@ -1,20 +1,43 @@
1
1
  import * as AbiFunction from "ox/AbiFunction";
2
- /** Bind encoding and decoding to the same resolved ABI function. */
3
- function prepareAbiCall(call) {
4
- const args = call.args ?? [];
5
- const abiFunction = AbiFunction.fromAbi(call.abi, call.functionName, {
6
- args,
2
+ /**
3
+ * Turn ABI calls into raw decoded calls that encode and decode with the same
4
+ * resolved function. Raw decoded calls pass through unchanged.
5
+ */
6
+ function prepareDecodedCalls(calls) {
7
+ const sharedFunctions = new Map();
8
+ return calls.map((call) => {
9
+ if (call.abi === undefined)
10
+ return call;
11
+ const args = call.args ?? [];
12
+ const abiFunction = resolveAbiFunction(call, args, sharedFunctions);
13
+ // ox encodes only the selector when args are missing, so reject that
14
+ // before RPC; literal ABIs catch it in types, runtime-loaded ABIs cannot.
15
+ if (args.length !== abiFunction.inputs.length) {
16
+ throw new TypeError(`${call.functionName} expects ${abiFunction.inputs.length} arguments, received ${args.length}`);
17
+ }
18
+ return {
19
+ to: call.to,
20
+ data: AbiFunction.encodeData(abiFunction, args),
21
+ decodeResult: (returnData) => AbiFunction.decodeResult(abiFunction, returnData),
22
+ };
7
23
  });
8
- // ox permits selector-only encoding with no args. At this wire boundary,
9
- // missing arguments must fail before RPC, including for dynamically loaded ABIs.
10
- if (args.length !== abiFunction.inputs.length) {
11
- throw new TypeError(`${call.functionName} expects ${abiFunction.inputs.length} arguments, received ${args.length}`);
24
+ }
25
+ // Each ox lookup hashes the function signature, so a batch resolves each name
26
+ // once when it can. ox ignores args when exactly one ABI item has the name, of
27
+ // any item type. Other names resolve per call, by argument types.
28
+ function resolveAbiFunction(call, args, sharedFunctions) {
29
+ const functions = sharedFunctions.get(call.abi) ?? new Map();
30
+ sharedFunctions.set(call.abi, functions);
31
+ let shared = functions.get(call.functionName);
32
+ if (shared === undefined) {
33
+ const matches = call.abi.filter((item) => "name" in item && item.name === call.functionName);
34
+ shared =
35
+ matches.length === 1
36
+ ? AbiFunction.fromAbi(call.abi, call.functionName)
37
+ : null;
38
+ functions.set(call.functionName, shared);
12
39
  }
13
- return {
14
- to: call.to,
15
- data: AbiFunction.encodeData(abiFunction, args),
16
- decodeResult: (returnData) => AbiFunction.decodeResult(abiFunction, returnData),
17
- };
40
+ return shared ?? AbiFunction.fromAbi(call.abi, call.functionName, { args });
18
41
  }
19
- export { prepareAbiCall };
42
+ export { prepareDecodedCalls };
20
43
  //# sourceMappingURL=abi.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"abi.js","sourceRoot":"","sources":["../../src/sdk/abi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,WAAW,MAAM,gBAAgB,CAAC;AA+D9C,oEAAoE;AACpE,SAAS,cAAc,CAAC,IAAsB;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE;QACpE,IAAI;KACJ,CAAC,CAAC;IAEH,yEAAyE;IACzE,iFAAiF;IACjF,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAC/C,MAAM,IAAI,SAAS,CAClB,GAAG,IAAI,CAAC,YAAY,YAAY,WAAW,CAAC,MAAM,CAAC,MAAM,wBAAwB,IAAI,CAAC,MAAM,EAAE,CAC9F,CAAC;IACH,CAAC;IAED,OAAO;QACN,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,WAAW,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC;QAC/C,YAAY,EAAE,CAAC,UAAU,EAAE,EAAE,CAC5B,WAAW,CAAC,YAAY,CAAC,WAAW,EAAE,UAAU,CAAC;KAClD,CAAC;AACH,CAAC;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"abi.js","sourceRoot":"","sources":["../../src/sdk/abi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,WAAW,MAAM,gBAAgB,CAAC;AAoE9C;;;GAGG;AACH,SAAS,mBAAmB,CAC3B,KAA2D;IAE3D,MAAM,eAAe,GAAoB,IAAI,GAAG,EAAE,CAAC;IACnD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAwB,EAAE;QAC/C,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;QACpE,qEAAqE;QACrE,0EAA0E;QAC1E,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC/C,MAAM,IAAI,SAAS,CAClB,GAAG,IAAI,CAAC,YAAY,YAAY,WAAW,CAAC,MAAM,CAAC,MAAM,wBAAwB,IAAI,CAAC,MAAM,EAAE,CAC9F,CAAC;QACH,CAAC;QACD,OAAO;YACN,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,IAAI,EAAE,WAAW,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC;YAC/C,YAAY,EAAE,CAAC,UAAU,EAAE,EAAE,CAC5B,WAAW,CAAC,YAAY,CAAC,WAAW,EAAE,UAAU,CAAC;SAClD,CAAC;IACH,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,+EAA+E;AAC/E,kEAAkE;AAClE,SAAS,kBAAkB,CAC1B,IAAsB,EACtB,IAAwB,EACxB,eAAgC;IAEhC,MAAM,SAAS,GAAoB,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;IAC9E,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACzC,IAAI,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAC9B,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,YAAY,CAC3D,CAAC;QACF,MAAM;YACL,OAAO,CAAC,MAAM,KAAK,CAAC;gBACnB,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC;gBAClD,CAAC,CAAC,IAAI,CAAC;QACT,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,MAAM,IAAI,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7E,CAAC;AAGD,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
@@ -1,4 +1,4 @@
1
- // This file is generated by scripts/generate-sdk-initcode.mjs.
1
+ // This file is generated by scripts/generate-sdk-initcode.ts.
2
2
  // Run npm run build:contracts after changing src/Ghostcall.yul.
3
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,0LAAmM,CAAC"}
1
+ {"version":3,"file":"initcode.js","sourceRoot":"","sources":["../../../src/sdk/generated/initcode.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,gEAAgE;AAEhE,MAAM,CAAC,MAAM,iBAAiB,GAC7B,0LAAmM,CAAC"}
@@ -9,27 +9,22 @@ type GhostcallCall = {
9
9
  /** Return failed entries from aggregateCalls instead of throwing. Default: false. */
10
10
  allowFailure?: boolean;
11
11
  };
12
- /** Raw calldata with a decoder that only receives successful results. */
13
- type GhostcallDecodedCall<TResult = unknown> = GhostcallCall & {
12
+ /** Raw calldata with a decoder that only receives successful return data. */
13
+ type GhostcallDecodedCall<TResult = unknown> = {
14
+ to: Hex;
15
+ /** At most 65,535 bytes (the wire format stores a uint16 length). */
16
+ data: Hex;
17
+ decodeResult: (returnData: Hex, index: number) => TResult;
14
18
  abi?: never;
15
19
  functionName?: never;
16
20
  args?: never;
17
21
  allowFailure?: never;
18
- decodeResult: (returnData: Hex, entry: Extract<GhostcallResult, {
19
- success: true;
20
- }>, index: number) => TResult;
21
22
  };
22
- /** Raw results retain input order and include revert data for failed calls. */
23
+ /** One subcall result. Failed calls carry their revert data in returnData. */
23
24
  type GhostcallResult = {
24
- success: true;
25
- returnData: Hex;
26
- } | {
27
- success: false;
25
+ success: boolean;
28
26
  returnData: Hex;
29
27
  };
30
- type GhostcallFailedResult = Extract<GhostcallResult, {
31
- success: false;
32
- }>;
33
28
  type GhostcallDecodedInput = GhostcallAbiCall | GhostcallDecodedCall;
34
29
  type ValidatedDecodedCall<TCall extends GhostcallDecodedInput> = TCall extends GhostcallAbiCall ? GhostcallAbiCall<TCall["abi"]> : TCall;
35
30
  type ValidatedDecodedCalls<TCalls extends readonly GhostcallDecodedInput[]> = {
@@ -47,13 +42,13 @@ type GhostcallAggregateOptions = GhostcallEncodeOptions & {
47
42
  /** Controls the outer eth_call, shared by the entire batch. */
48
43
  ethCall?: {
49
44
  from?: Hex;
50
- /** Canonical RPC hex quantity. */
51
- gas?: Hex;
52
- /** Decimal block numbers are normalized to hex. Default: "latest". */
53
- blockTag?: string | number | bigint;
45
+ gas?: bigint;
46
+ /** A block number or named tag. Default: "latest". */
47
+ blockTag?: bigint | "latest" | "earliest" | "pending" | "safe" | "finalized";
54
48
  };
55
49
  };
56
- type Provider = {
50
+ /** A provider with an EIP-1193-style request method, such as a viem client or ox transport. */
51
+ type GhostcallProvider = {
57
52
  request(args: {
58
53
  method: string;
59
54
  params?: unknown;
@@ -63,26 +58,27 @@ type Provider = {
63
58
  declare class GhostcallSubcallError extends Error {
64
59
  readonly index: number;
65
60
  readonly call: GhostcallCall;
66
- readonly result: GhostcallFailedResult;
67
- constructor(index: number, call: GhostcallCall, result: GhostcallFailedResult);
61
+ readonly returnData: Hex;
62
+ constructor(index: number, call: GhostcallCall, returnData: Hex);
68
63
  }
69
64
  /**
70
65
  * Build CREATE-style eth_call data: initcode followed by [length (2)][target (20)][data].
71
66
  * Send without a `to` address. Invalid inputs throw TypeError; size limits throw RangeError.
72
67
  */
73
- declare function encodeCalls(calls: readonly GhostcallCall[], { maxInitcodeBytes, }?: GhostcallEncodeOptions): Hex;
68
+ declare function encodeCalls(calls: readonly GhostcallCall[], { maxInitcodeBytes }?: GhostcallEncodeOptions): Hex;
74
69
  /**
75
70
  * Execute a raw batch in order. Failed calls throw GhostcallSubcallError unless
76
71
  * their entry sets allowFailure. Provider errors pass through unchanged.
77
72
  */
78
- declare function aggregateCalls(provider: Provider, calls: readonly GhostcallCall[], options?: GhostcallAggregateOptions): Promise<GhostcallResult[]>;
73
+ declare function aggregateCalls(provider: GhostcallProvider, calls: readonly GhostcallCall[], options?: GhostcallAggregateOptions): Promise<GhostcallResult[]>;
79
74
  /**
80
75
  * Execute ABI-described calls or raw calls with custom decoders. Each tuple position
81
76
  * retains its result type. Any failed call throws; encoding/decoding errors pass through.
77
+ * ABI-decoded addresses are checksummed, including addresses nested in tuples or arrays.
82
78
  */
83
- declare function aggregateDecodedCalls<const TCalls extends readonly GhostcallDecodedInput[]>(provider: Provider, calls: TCalls & NoInfer<ValidatedDecodedCalls<TCalls>>, options?: GhostcallAggregateOptions): Promise<GhostcallDecodedResults<TCalls>>;
79
+ declare function aggregateDecodedCalls<const TCalls extends readonly GhostcallDecodedInput[]>(provider: GhostcallProvider, calls: TCalls & NoInfer<ValidatedDecodedCalls<TCalls>>, options?: GhostcallAggregateOptions): Promise<GhostcallDecodedResults<TCalls>>;
84
80
  /** Decode ordered [success bit | uint15 length][returndata] entries. Reject malformed data. */
85
81
  declare function decodeResults(data: Hex): GhostcallResult[];
86
- export type { GhostcallAbiCall, GhostcallAggregateOptions, GhostcallCall, GhostcallDecodedCall, GhostcallEncodeOptions, GhostcallResult, Hex, };
82
+ export type { GhostcallAbiCall, GhostcallAggregateOptions, GhostcallCall, GhostcallDecodedCall, GhostcallEncodeOptions, GhostcallProvider, GhostcallResult, Hex, };
87
83
  export { aggregateCalls, aggregateDecodedCalls, decodeResults, encodeCalls, GhostcallSubcallError, };
88
84
  //# 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,128 +1,142 @@
1
1
  import { validate as isAddress } from "ox/Address";
2
2
  import { size as hexSize, validate as isHex } from "ox/Hex";
3
- import { prepareAbiCall, } from "./abi.js";
3
+ import { prepareDecodedCalls, } from "./abi.js";
4
4
  import { ghostcallInitcode } from "./generated/initcode.js";
5
5
  /** A disallowed failure, with its zero-based index, executed call, and revert data. */
6
6
  class GhostcallSubcallError extends Error {
7
7
  index;
8
8
  call;
9
- result;
10
- constructor(index, call, result) {
11
- super(`Ghostcall subcall ${index} failed`);
9
+ returnData;
10
+ constructor(index, call, returnData) {
11
+ super(`ghostcall subcall ${index} failed`);
12
12
  this.name = "GhostcallSubcallError";
13
13
  this.index = index;
14
14
  this.call = call;
15
- this.result = result;
15
+ this.returnData = returnData;
16
16
  }
17
17
  }
18
- const encodedCallHeaderSize = 22;
19
- const encodedHeaderHexLength = 4;
18
+ // Request entries: [uint16 calldata length][20-byte target][calldata].
19
+ const callHeaderSize = 22;
20
+ const calldataLengthHexChars = 4;
20
21
  const maxCalldataSize = 0xffff;
21
- const defaultMaxCreateInitcodeSize = 0xc000;
22
- const successFlagMask = 0x8000;
22
+ // Result entries: [success bit | uint15 returndata length][returndata].
23
+ const resultHeaderHexChars = 4;
24
+ const successFlag = 0x8000;
23
25
  const returnDataLengthMask = 0x7fff;
26
+ // EIP-3860 initcode limit.
27
+ const defaultMaxInitcodeBytes = 0xc000;
24
28
  const bundledInitcodeSize = hexSize(ghostcallInitcode);
25
29
  /**
26
30
  * Build CREATE-style eth_call data: initcode followed by [length (2)][target (20)][data].
27
31
  * Send without a `to` address. Invalid inputs throw TypeError; size limits throw RangeError.
28
32
  */
29
- function encodeCalls(calls, { maxInitcodeBytes = defaultMaxCreateInitcodeSize, } = {}) {
30
- if (!Number.isSafeInteger(maxInitcodeBytes) || maxInitcodeBytes < 0) {
31
- throw new TypeError("options.maxInitcodeBytes must be a non-negative safe integer");
32
- }
33
- const encodedParts = [ghostcallInitcode.slice(2)];
33
+ function encodeCalls(calls, { maxInitcodeBytes = defaultMaxInitcodeBytes } = {}) {
34
+ let encodedData = ghostcallInitcode;
34
35
  let totalEncodedSize = bundledInitcodeSize;
35
- const sizeError = `encoded Ghostcall initcode exceeds the ${maxInitcodeBytes}-byte CREATE initcode limit`;
36
+ const sizeError = `encoded ghostcall initcode exceeds the ${maxInitcodeBytes}-byte CREATE initcode limit`;
36
37
  if (totalEncodedSize > maxInitcodeBytes)
37
38
  throw new RangeError(sizeError);
38
- for (const [index, call] of calls.entries()) {
39
- assertAddress(call.to, `calls[${index}].to`);
39
+ // A plain loop keeps the string concatenation fast; see benchmark:sdk.
40
+ let index = 0;
41
+ for (const call of calls) {
42
+ const to = assertAddress(call.to, `calls[${index}].to`);
40
43
  const calldata = assertHex(call.data, `calls[${index}].data`);
41
44
  const calldataSize = hexSize(calldata);
42
45
  if (calldataSize > maxCalldataSize) {
43
46
  throw new RangeError(`calls[${index}].data exceeds the ${maxCalldataSize}-byte calldata limit`);
44
47
  }
45
- totalEncodedSize += encodedCallHeaderSize + calldataSize;
48
+ totalEncodedSize += callHeaderSize + calldataSize;
46
49
  if (totalEncodedSize > maxInitcodeBytes)
47
50
  throw new RangeError(sizeError);
48
- encodedParts.push(calldataSize.toString(16).padStart(4, "0"), call.to.slice(2), calldata.slice(2));
51
+ const header = calldataSize
52
+ .toString(16)
53
+ .padStart(calldataLengthHexChars, "0");
54
+ encodedData = `${encodedData}${header}${to.slice(2)}${calldata.slice(2)}`;
55
+ index += 1;
49
56
  }
50
- return `0x${encodedParts.join("")}`;
57
+ return encodedData;
51
58
  }
52
59
  /**
53
60
  * Execute a raw batch in order. Failed calls throw GhostcallSubcallError unless
54
61
  * their entry sets allowFailure. Provider errors pass through unchanged.
55
62
  */
56
63
  async function aggregateCalls(provider, calls, options) {
57
- const { from, gas, blockTag } = options?.ethCall ?? {};
58
- const ethCall = {
59
- data: encodeCalls(calls, options ?? {}),
60
- };
61
- if (from !== undefined) {
62
- assertAddress(from, "options.ethCall.from");
63
- ethCall.from = from;
64
- }
65
- if (gas !== undefined)
66
- ethCall.gas = assertHexQuantity(gas, "options.ethCall.gas");
67
- const result = await provider.request({
68
- method: "eth_call",
69
- params: [ethCall, normalizeBlockTag(blockTag ?? "latest")],
70
- });
71
- const entries = decodeResults(assertHex(result, "eth_call result"));
72
- if (entries.length !== calls.length) {
73
- throw new Error(`Ghostcall returned ${entries.length} result entries for ${calls.length} calls`);
74
- }
75
- for (const [index, entry] of entries.entries()) {
64
+ const results = await executeCalls(provider, calls, options);
65
+ let index = 0;
66
+ for (const { success, returnData } of results) {
76
67
  const call = calls[index];
77
- if (!entry.success && call.allowFailure !== true) {
78
- throw new GhostcallSubcallError(index, call, entry);
68
+ if (!success && !call.allowFailure) {
69
+ throw new GhostcallSubcallError(index, call, returnData);
79
70
  }
71
+ index += 1;
80
72
  }
81
- return entries;
73
+ return results;
82
74
  }
83
75
  /**
84
76
  * Execute ABI-described calls or raw calls with custom decoders. Each tuple position
85
77
  * retains its result type. Any failed call throws; encoding/decoding errors pass through.
78
+ * ABI-decoded addresses are checksummed, including addresses nested in tuples or arrays.
86
79
  */
87
80
  async function aggregateDecodedCalls(provider, calls, options) {
88
- const preparedCalls = calls.map((call) => call.abi === undefined ? call : prepareAbiCall(call));
89
- const entries = await aggregateCalls(provider, preparedCalls, options);
90
- return entries.map((entry, index) => {
91
- // aggregateCalls has checked the count. Hidden allowFailure fields still cannot
92
- // bypass this guard and send failed returndata to a success-only decoder.
81
+ const preparedCalls = prepareDecodedCalls(calls);
82
+ const results = await executeCalls(provider, preparedCalls, options);
83
+ return results.map(({ success, returnData }, index) => {
93
84
  const call = preparedCalls[index];
94
- if (!entry.success)
95
- throw new GhostcallSubcallError(index, call, entry);
96
- return call.decodeResult(entry.returnData, entry, index);
85
+ if (!success)
86
+ throw new GhostcallSubcallError(index, call, returnData);
87
+ return call.decodeResult(returnData, index);
97
88
  });
98
89
  }
99
90
  /** Decode ordered [success bit | uint15 length][returndata] entries. Reject malformed data. */
100
91
  function decodeResults(data) {
101
- const encodedData = assertHex(data, "data").slice(2);
92
+ return decodeValidatedResults(assertHex(data, "data"));
93
+ }
94
+ /** Send one eth_call and return one result per call, without applying a failure policy. */
95
+ async function executeCalls(provider, calls, options = {}) {
96
+ const { from, gas, blockTag = "latest" } = options.ethCall ?? {};
97
+ const ethCall = {
98
+ data: encodeCalls(calls, options),
99
+ ...(from !== undefined && {
100
+ from: assertAddress(from, "options.ethCall.from"),
101
+ }),
102
+ ...(gas !== undefined && { gas: toQuantity(gas) }),
103
+ };
104
+ const response = await provider.request({
105
+ method: "eth_call",
106
+ params: [
107
+ ethCall,
108
+ typeof blockTag === "bigint" ? toQuantity(blockTag) : blockTag,
109
+ ],
110
+ });
111
+ const results = decodeValidatedResults(assertHex(response, "eth_call result"));
112
+ if (results.length !== calls.length) {
113
+ throw new Error(`ghostcall returned ${results.length} result entries for ${calls.length} calls`);
114
+ }
115
+ return results;
116
+ }
117
+ /** Parse only after the public API or RPC boundary has validated the entire hex string. */
118
+ function decodeValidatedResults(data) {
102
119
  const results = [];
103
- let cursor = 0;
104
- while (cursor < encodedData.length) {
105
- if (cursor + encodedHeaderHexLength > encodedData.length) {
106
- throw new TypeError("Truncated Ghostcall response header");
107
- }
108
- const header = Number.parseInt(encodedData.slice(cursor, cursor + encodedHeaderHexLength), 16);
109
- cursor += encodedHeaderHexLength;
110
- const returnDataEnd = cursor + (header & returnDataLengthMask) * 2;
111
- if (returnDataEnd > encodedData.length) {
112
- throw new TypeError("Truncated Ghostcall response body");
120
+ let cursor = 2;
121
+ while (cursor < data.length) {
122
+ const start = cursor + resultHeaderHexChars;
123
+ const header = Number.parseInt(data.slice(cursor, start), 16);
124
+ cursor = start + (header & returnDataLengthMask) * 2;
125
+ // A partial header also ends past the data, so one check covers both cases.
126
+ if (cursor > data.length) {
127
+ throw new TypeError("Truncated ghostcall response");
113
128
  }
114
- results.push({
115
- success: (header & successFlagMask) !== 0,
116
- returnData: `0x${encodedData.slice(cursor, returnDataEnd)}`,
117
- });
118
- cursor = returnDataEnd;
129
+ const success = (header & successFlag) !== 0;
130
+ results.push({ success, returnData: `0x${data.slice(start, cursor)}` });
119
131
  }
120
132
  return results;
121
133
  }
134
+ // Types cannot express a 20-byte length; call targets reach the Yul program unchecked.
122
135
  function assertAddress(value, label) {
123
- if (typeof value !== "string" || !isAddress(value, { strict: false })) {
136
+ if (!isAddress(value, { strict: false })) {
124
137
  throw new TypeError(`${label} must be a 20-byte hex string`);
125
138
  }
139
+ return value;
126
140
  }
127
141
  function assertHex(value, label) {
128
142
  // ox checks prefix/digits; the wire format additionally requires whole bytes.
@@ -131,29 +145,8 @@ function assertHex(value, label) {
131
145
  }
132
146
  return value;
133
147
  }
134
- function assertHexQuantity(value, label) {
135
- if (typeof value !== "string" ||
136
- !/^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$/.test(value)) {
137
- throw new TypeError(`${label} must be a 0x-prefixed hex quantity`);
138
- }
139
- return value;
140
- }
141
- function normalizeBlockTag(value) {
142
- if (typeof value === "bigint" ||
143
- (typeof value === "number" && Number.isSafeInteger(value))) {
144
- if (value >= 0)
145
- return `0x${value.toString(16)}`;
146
- }
147
- else if (typeof value === "string" &&
148
- value.length > 0 &&
149
- !/^-\d+$/.test(value)) {
150
- if (/^\d+$/.test(value))
151
- return `0x${BigInt(value).toString(16)}`;
152
- return /^0x/i.test(value)
153
- ? assertHexQuantity(`0x${value.slice(2)}`, "options.ethCall.blockTag")
154
- : value;
155
- }
156
- throw new TypeError("options.ethCall.blockTag must be a non-negative safe integer, bigint, or non-empty string");
148
+ function toQuantity(value) {
149
+ return `0x${value.toString(16)}`;
157
150
  }
158
151
  export { aggregateCalls, aggregateDecodedCalls, decodeResults, encodeCalls, GhostcallSubcallError, };
159
152
  //# 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,9 +1,10 @@
1
1
  {
2
2
  "name": "@volga-sh/evm-ghostcall",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Batch EVM contract reads without deploying a Multicall contract.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
+ "packageManager": "npm@12.1.0",
7
8
  "repository": {
8
9
  "type": "git",
9
10
  "url": "git+https://github.com/volga-sh/ghostcall.git"
@@ -22,39 +23,38 @@
22
23
  },
23
24
  "files": [
24
25
  "dist/",
25
- "src/Ghostcall.yul"
26
+ "src/Ghostcall.yul",
27
+ "src/sdk/"
26
28
  ],
27
29
  "sideEffects": false,
28
30
  "publishConfig": {
29
31
  "access": "public"
30
32
  },
31
33
  "scripts": {
32
- "benchmark:limits": "node --disable-warning=ExperimentalWarning --experimental-strip-types scripts/benchmark-limits.ts",
34
+ "benchmark:limits": "node scripts/benchmark-limits.ts",
35
+ "benchmark:sdk": "node --expose-gc scripts/benchmark-sdk.ts",
33
36
  "build:contracts": "forge build && npm run generate:sdk:initcode",
34
37
  "build:sdk": "npm run build:contracts && tsc --project tsconfig.build.json",
35
38
  "check": "biome check .",
36
39
  "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",
40
+ "docs:build": "npm run build --prefix docs --",
41
+ "docs:dev": "npm run dev --prefix docs --",
42
+ "docs:preview": "npm run preview --prefix docs --",
40
43
  "check:sdk:initcode": "git diff --exit-code -- src/sdk/generated/initcode.ts",
41
44
  "format": "biome format --write .",
42
- "format:check": "biome format .",
43
- "generate:sdk:initcode": "node scripts/generate-sdk-initcode.mjs",
44
- "lint": "biome lint .",
45
- "lint:fix": "npm run check:fix",
45
+ "generate:sdk:initcode": "node scripts/generate-sdk-initcode.ts",
46
46
  "prepack": "npm run build:sdk",
47
- "test": "npm run build:contracts && node --disable-warning=ExperimentalWarning --experimental-strip-types --test test/*.test.ts",
48
- "test:watch": "node --disable-warning=ExperimentalWarning --experimental-strip-types --test --watch test/*.test.ts",
47
+ "test": "npm run build:contracts && node --test test/*.test.ts",
48
+ "test:watch": "node --test --watch test/*.test.ts",
49
49
  "typecheck": "tsc --project tsconfig.json"
50
50
  },
51
51
  "dependencies": {
52
- "ox": "0.9.6"
52
+ "ox": "^1.8.3"
53
53
  },
54
54
  "devDependencies": {
55
- "@biomejs/biome": "2.4.12",
55
+ "@biomejs/biome": "2.5.14",
56
56
  "@safe-global/mock-contract": "4.1.0",
57
- "@types/node": "24.3.1",
58
- "typescript": "5.9.2"
57
+ "@types/node": "24.19.1",
58
+ "typescript": "7.0.2"
59
59
  }
60
60
  }
package/src/sdk/abi.ts ADDED
@@ -0,0 +1,123 @@
1
+ import type { Abi } from "ox/Abi";
2
+ import * as AbiFunction from "ox/AbiFunction";
3
+ import type * as AbiItem from "ox/AbiItem";
4
+
5
+ import type { GhostcallDecodedCall, Hex } from "./index.ts";
6
+
7
+ // Distribute over functions so each name stays paired with its own arguments.
8
+ type FunctionCall<TFunction extends AbiFunction.AbiFunction> =
9
+ TFunction extends AbiFunction.AbiFunction
10
+ ? {
11
+ functionName: TFunction["name"];
12
+ } & (TFunction["inputs"] extends readonly []
13
+ ? { args?: readonly [] }
14
+ : { args: NonNullable<AbiFunction.encodeData.Args<TFunction>[0]> })
15
+ : never;
16
+
17
+ /**
18
+ * An ABI-described call. A literal ABI determines valid function names and args.
19
+ * Use `as const` or an ABI parser to retain those literal types.
20
+ */
21
+ type GhostcallAbiCall<TAbi extends Abi = Abi> = {
22
+ to: Hex;
23
+ abi: TAbi;
24
+ data?: never;
25
+ decodeResult?: never;
26
+ allowFailure?: never;
27
+ } & (Abi extends TAbi
28
+ ? { functionName: string; args?: readonly unknown[] }
29
+ : FunctionCall<Extract<TAbi[number], { type: "function" }>>);
30
+
31
+ type CallFunctionName<TCall extends GhostcallAbiCall> = TCall["functionName"] &
32
+ AbiFunction.Name<TCall["abi"]>;
33
+
34
+ type CallArguments<TCall extends GhostcallAbiCall> = TCall extends {
35
+ args: infer TArgs extends readonly unknown[];
36
+ }
37
+ ? TArgs
38
+ : readonly [];
39
+
40
+ // Infer ox's argument constraint through its public options, then let ox choose
41
+ // the overload. This keeps our result types aligned with runtime resolution.
42
+ type ResolvedFunction<TCall extends GhostcallAbiCall> =
43
+ AbiItem.fromAbi.Options<
44
+ TCall["abi"],
45
+ CallFunctionName<TCall>
46
+ > extends AbiItem.fromAbi.Options<
47
+ TCall["abi"],
48
+ CallFunctionName<TCall>,
49
+ infer TArgs
50
+ >
51
+ ? AbiItem.fromAbi.ReturnType<
52
+ TCall["abi"],
53
+ CallFunctionName<TCall>,
54
+ Extract<CallArguments<TCall>, TArgs>,
55
+ AbiFunction.AbiFunction
56
+ >
57
+ : never;
58
+
59
+ /** The decoded result of an ABI call, including argument-selected overloads. */
60
+ type GhostcallAbiResult<TCall extends GhostcallAbiCall> =
61
+ AbiFunction.decodeResult.ReturnType<
62
+ Extract<ResolvedFunction<TCall>, AbiFunction.AbiFunction>
63
+ >;
64
+
65
+ // Functions resolved within one batch, keyed by ABI object and function name.
66
+ // `null` marks a name that must be resolved per call from its arguments.
67
+ type FunctionsByName = Map<string, AbiFunction.AbiFunction | null>;
68
+ type SharedFunctions = Map<Abi, FunctionsByName>;
69
+
70
+ /**
71
+ * Turn ABI calls into raw decoded calls that encode and decode with the same
72
+ * resolved function. Raw decoded calls pass through unchanged.
73
+ */
74
+ function prepareDecodedCalls(
75
+ calls: readonly (GhostcallAbiCall | GhostcallDecodedCall)[],
76
+ ): GhostcallDecodedCall[] {
77
+ const sharedFunctions: SharedFunctions = new Map();
78
+ return calls.map((call): GhostcallDecodedCall => {
79
+ if (call.abi === undefined) return call;
80
+ const args = call.args ?? [];
81
+ const abiFunction = resolveAbiFunction(call, args, sharedFunctions);
82
+ // ox encodes only the selector when args are missing, so reject that
83
+ // before RPC; literal ABIs catch it in types, runtime-loaded ABIs cannot.
84
+ if (args.length !== abiFunction.inputs.length) {
85
+ throw new TypeError(
86
+ `${call.functionName} expects ${abiFunction.inputs.length} arguments, received ${args.length}`,
87
+ );
88
+ }
89
+ return {
90
+ to: call.to,
91
+ data: AbiFunction.encodeData(abiFunction, args),
92
+ decodeResult: (returnData) =>
93
+ AbiFunction.decodeResult(abiFunction, returnData),
94
+ };
95
+ });
96
+ }
97
+
98
+ // Each ox lookup hashes the function signature, so a batch resolves each name
99
+ // once when it can. ox ignores args when exactly one ABI item has the name, of
100
+ // any item type. Other names resolve per call, by argument types.
101
+ function resolveAbiFunction(
102
+ call: GhostcallAbiCall,
103
+ args: readonly unknown[],
104
+ sharedFunctions: SharedFunctions,
105
+ ): AbiFunction.AbiFunction {
106
+ const functions: FunctionsByName = sharedFunctions.get(call.abi) ?? new Map();
107
+ sharedFunctions.set(call.abi, functions);
108
+ let shared = functions.get(call.functionName);
109
+ if (shared === undefined) {
110
+ const matches = call.abi.filter(
111
+ (item) => "name" in item && item.name === call.functionName,
112
+ );
113
+ shared =
114
+ matches.length === 1
115
+ ? AbiFunction.fromAbi(call.abi, call.functionName)
116
+ : null;
117
+ functions.set(call.functionName, shared);
118
+ }
119
+ return shared ?? AbiFunction.fromAbi(call.abi, call.functionName, { args });
120
+ }
121
+
122
+ export type { GhostcallAbiCall, GhostcallAbiResult };
123
+ export { prepareDecodedCalls };
@@ -0,0 +1,5 @@
1
+ // This file is generated by scripts/generate-sdk-initcode.ts.
2
+ // Run npm run build:contracts after changing src/Ghostcall.yul.
3
+
4
+ export const ghostcallInitcode =
5
+ "0x5f605b5b388110600d57505ff35b90601682823980515f808260f01c80938260168701918360168a01843960501c5af13d80600f1c60565760169381600293600f1b1760f01b8152815f8483013e01019201016003565b5f80fdfe" as const;
@@ -0,0 +1,283 @@
1
+ import { validate as isAddress } from "ox/Address";
2
+ import { size as hexSize, validate as isHex } from "ox/Hex";
3
+
4
+ import {
5
+ type GhostcallAbiCall,
6
+ type GhostcallAbiResult,
7
+ prepareDecodedCalls,
8
+ } from "./abi.ts";
9
+ import { ghostcallInitcode } from "./generated/initcode.ts";
10
+
11
+ /** A 0x-prefixed string, validated at SDK wire boundaries. */
12
+ type Hex = `0x${string}`;
13
+
14
+ /** Raw calldata for one target. Encoding ignores the SDK-only failure policy. */
15
+ type GhostcallCall = {
16
+ to: Hex;
17
+ /** At most 65,535 bytes (the wire format stores a uint16 length). */
18
+ data: Hex;
19
+ /** Return failed entries from aggregateCalls instead of throwing. Default: false. */
20
+ allowFailure?: boolean;
21
+ };
22
+
23
+ /** Raw calldata with a decoder that only receives successful return data. */
24
+ type GhostcallDecodedCall<TResult = unknown> = {
25
+ to: Hex;
26
+ /** At most 65,535 bytes (the wire format stores a uint16 length). */
27
+ data: Hex;
28
+ decodeResult: (returnData: Hex, index: number) => TResult;
29
+ abi?: never;
30
+ functionName?: never;
31
+ args?: never;
32
+ allowFailure?: never;
33
+ };
34
+
35
+ /** One subcall result. Failed calls carry their revert data in returnData. */
36
+ type GhostcallResult = { success: boolean; returnData: Hex };
37
+
38
+ type GhostcallDecodedInput = GhostcallAbiCall | GhostcallDecodedCall;
39
+
40
+ // Distribute over mixed call unions while retaining each tuple position.
41
+ type ValidatedDecodedCall<TCall extends GhostcallDecodedInput> =
42
+ TCall extends GhostcallAbiCall ? GhostcallAbiCall<TCall["abi"]> : TCall;
43
+
44
+ type ValidatedDecodedCalls<TCalls extends readonly GhostcallDecodedInput[]> = {
45
+ [Index in keyof TCalls]: ValidatedDecodedCall<TCalls[Index]>;
46
+ };
47
+
48
+ type DecodedResult<TCall extends GhostcallDecodedInput> =
49
+ TCall extends GhostcallAbiCall
50
+ ? GhostcallAbiResult<TCall>
51
+ : TCall extends GhostcallDecodedCall
52
+ ? ReturnType<TCall["decodeResult"]>
53
+ : never;
54
+
55
+ type GhostcallDecodedResults<TCalls extends readonly GhostcallDecodedInput[]> =
56
+ {
57
+ -readonly [Index in keyof TCalls]: DecodedResult<TCalls[Index]>;
58
+ };
59
+
60
+ type GhostcallEncodeOptions = {
61
+ /** Full CREATE request ceiling, including bundled initcode. Default: 49,152 bytes. */
62
+ maxInitcodeBytes?: number;
63
+ };
64
+
65
+ type GhostcallAggregateOptions = GhostcallEncodeOptions & {
66
+ /** Controls the outer eth_call, shared by the entire batch. */
67
+ ethCall?: {
68
+ from?: Hex;
69
+ gas?: bigint;
70
+ /** A block number or named tag. Default: "latest". */
71
+ blockTag?:
72
+ | bigint
73
+ | "latest"
74
+ | "earliest"
75
+ | "pending"
76
+ | "safe"
77
+ | "finalized";
78
+ };
79
+ };
80
+
81
+ /** A provider with an EIP-1193-style request method, such as a viem client or ox transport. */
82
+ type GhostcallProvider = {
83
+ request(args: { method: string; params?: unknown }): Promise<unknown>;
84
+ };
85
+
86
+ /** A disallowed failure, with its zero-based index, executed call, and revert data. */
87
+ class GhostcallSubcallError extends Error {
88
+ readonly index: number;
89
+ readonly call: GhostcallCall;
90
+ readonly returnData: Hex;
91
+
92
+ constructor(index: number, call: GhostcallCall, returnData: Hex) {
93
+ super(`ghostcall subcall ${index} failed`);
94
+ this.name = "GhostcallSubcallError";
95
+ this.index = index;
96
+ this.call = call;
97
+ this.returnData = returnData;
98
+ }
99
+ }
100
+
101
+ // Request entries: [uint16 calldata length][20-byte target][calldata].
102
+ const callHeaderSize = 22;
103
+ const calldataLengthHexChars = 4;
104
+ const maxCalldataSize = 0xffff;
105
+ // Result entries: [success bit | uint15 returndata length][returndata].
106
+ const resultHeaderHexChars = 4;
107
+ const successFlag = 0x8000;
108
+ const returnDataLengthMask = 0x7fff;
109
+ // EIP-3860 initcode limit.
110
+ const defaultMaxInitcodeBytes = 0xc000;
111
+ const bundledInitcodeSize = hexSize(ghostcallInitcode);
112
+
113
+ /**
114
+ * Build CREATE-style eth_call data: initcode followed by [length (2)][target (20)][data].
115
+ * Send without a `to` address. Invalid inputs throw TypeError; size limits throw RangeError.
116
+ */
117
+ function encodeCalls(
118
+ calls: readonly GhostcallCall[],
119
+ { maxInitcodeBytes = defaultMaxInitcodeBytes }: GhostcallEncodeOptions = {},
120
+ ): Hex {
121
+ let encodedData: Hex = ghostcallInitcode;
122
+ let totalEncodedSize = bundledInitcodeSize;
123
+ const sizeError = `encoded ghostcall initcode exceeds the ${maxInitcodeBytes}-byte CREATE initcode limit`;
124
+ if (totalEncodedSize > maxInitcodeBytes) throw new RangeError(sizeError);
125
+
126
+ // A plain loop keeps the string concatenation fast; see benchmark:sdk.
127
+ let index = 0;
128
+ for (const call of calls) {
129
+ const to = assertAddress(call.to, `calls[${index}].to`);
130
+ const calldata = assertHex(call.data, `calls[${index}].data`);
131
+ const calldataSize = hexSize(calldata);
132
+ if (calldataSize > maxCalldataSize) {
133
+ throw new RangeError(
134
+ `calls[${index}].data exceeds the ${maxCalldataSize}-byte calldata limit`,
135
+ );
136
+ }
137
+ totalEncodedSize += callHeaderSize + calldataSize;
138
+ if (totalEncodedSize > maxInitcodeBytes) throw new RangeError(sizeError);
139
+ const header = calldataSize
140
+ .toString(16)
141
+ .padStart(calldataLengthHexChars, "0");
142
+ encodedData = `${encodedData}${header}${to.slice(2)}${calldata.slice(2)}`;
143
+ index += 1;
144
+ }
145
+ return encodedData;
146
+ }
147
+
148
+ /**
149
+ * Execute a raw batch in order. Failed calls throw GhostcallSubcallError unless
150
+ * their entry sets allowFailure. Provider errors pass through unchanged.
151
+ */
152
+ async function aggregateCalls(
153
+ provider: GhostcallProvider,
154
+ calls: readonly GhostcallCall[],
155
+ options?: GhostcallAggregateOptions,
156
+ ): Promise<GhostcallResult[]> {
157
+ const results = await executeCalls(provider, calls, options);
158
+ let index = 0;
159
+ for (const { success, returnData } of results) {
160
+ const call = calls[index] as GhostcallCall;
161
+ if (!success && !call.allowFailure) {
162
+ throw new GhostcallSubcallError(index, call, returnData);
163
+ }
164
+ index += 1;
165
+ }
166
+ return results;
167
+ }
168
+
169
+ /**
170
+ * Execute ABI-described calls or raw calls with custom decoders. Each tuple position
171
+ * retains its result type. Any failed call throws; encoding/decoding errors pass through.
172
+ * ABI-decoded addresses are checksummed, including addresses nested in tuples or arrays.
173
+ */
174
+ async function aggregateDecodedCalls<
175
+ const TCalls extends readonly GhostcallDecodedInput[],
176
+ >(
177
+ provider: GhostcallProvider,
178
+ calls: TCalls & NoInfer<ValidatedDecodedCalls<TCalls>>,
179
+ options?: GhostcallAggregateOptions,
180
+ ): Promise<GhostcallDecodedResults<TCalls>> {
181
+ const preparedCalls = prepareDecodedCalls(calls);
182
+ const results = await executeCalls(provider, preparedCalls, options);
183
+ return results.map(({ success, returnData }, index) => {
184
+ const call = preparedCalls[index] as GhostcallDecodedCall;
185
+ if (!success) throw new GhostcallSubcallError(index, call, returnData);
186
+ return call.decodeResult(returnData, index);
187
+ }) as GhostcallDecodedResults<TCalls>;
188
+ }
189
+
190
+ /** Decode ordered [success bit | uint15 length][returndata] entries. Reject malformed data. */
191
+ function decodeResults(data: Hex): GhostcallResult[] {
192
+ return decodeValidatedResults(assertHex(data, "data"));
193
+ }
194
+
195
+ /** Send one eth_call and return one result per call, without applying a failure policy. */
196
+ async function executeCalls(
197
+ provider: GhostcallProvider,
198
+ calls: readonly GhostcallCall[],
199
+ options: GhostcallAggregateOptions = {},
200
+ ): Promise<GhostcallResult[]> {
201
+ const { from, gas, blockTag = "latest" } = options.ethCall ?? {};
202
+ const ethCall = {
203
+ data: encodeCalls(calls, options),
204
+ ...(from !== undefined && {
205
+ from: assertAddress(from, "options.ethCall.from"),
206
+ }),
207
+ ...(gas !== undefined && { gas: toQuantity(gas) }),
208
+ };
209
+ const response = await provider.request({
210
+ method: "eth_call",
211
+ params: [
212
+ ethCall,
213
+ typeof blockTag === "bigint" ? toQuantity(blockTag) : blockTag,
214
+ ],
215
+ });
216
+ const results = decodeValidatedResults(
217
+ assertHex(response, "eth_call result"),
218
+ );
219
+ if (results.length !== calls.length) {
220
+ throw new Error(
221
+ `ghostcall returned ${results.length} result entries for ${calls.length} calls`,
222
+ );
223
+ }
224
+ return results;
225
+ }
226
+
227
+ /** Parse only after the public API or RPC boundary has validated the entire hex string. */
228
+ function decodeValidatedResults(data: Hex): GhostcallResult[] {
229
+ const results: GhostcallResult[] = [];
230
+ let cursor = 2;
231
+ while (cursor < data.length) {
232
+ const start = cursor + resultHeaderHexChars;
233
+ const header = Number.parseInt(data.slice(cursor, start), 16);
234
+ cursor = start + (header & returnDataLengthMask) * 2;
235
+ // A partial header also ends past the data, so one check covers both cases.
236
+ if (cursor > data.length) {
237
+ throw new TypeError("Truncated ghostcall response");
238
+ }
239
+ const success = (header & successFlag) !== 0;
240
+ results.push({ success, returnData: `0x${data.slice(start, cursor)}` });
241
+ }
242
+ return results;
243
+ }
244
+
245
+ // Types cannot express a 20-byte length; call targets reach the Yul program unchecked.
246
+ function assertAddress(value: string, label: string): Hex {
247
+ if (!isAddress(value, { strict: false })) {
248
+ throw new TypeError(`${label} must be a 20-byte hex string`);
249
+ }
250
+ return value;
251
+ }
252
+
253
+ function assertHex(value: unknown, label: string): Hex {
254
+ // ox checks prefix/digits; the wire format additionally requires whole bytes.
255
+ if (!isHex(value, { strict: true }) || value.length % 2 !== 0) {
256
+ throw new TypeError(
257
+ `${label} must be an even-length 0x-prefixed hex string`,
258
+ );
259
+ }
260
+ return value;
261
+ }
262
+
263
+ function toQuantity(value: bigint): Hex {
264
+ return `0x${value.toString(16)}`;
265
+ }
266
+
267
+ export type {
268
+ GhostcallAbiCall,
269
+ GhostcallAggregateOptions,
270
+ GhostcallCall,
271
+ GhostcallDecodedCall,
272
+ GhostcallEncodeOptions,
273
+ GhostcallProvider,
274
+ GhostcallResult,
275
+ Hex,
276
+ };
277
+ export {
278
+ aggregateCalls,
279
+ aggregateDecodedCalls,
280
+ decodeResults,
281
+ encodeCalls,
282
+ GhostcallSubcallError,
283
+ };