@onrail-xyz/evm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # @onrail-xyz/evm
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@onrail-xyz/evm.svg)](https://www.npmjs.com/package/@onrail-xyz/evm)
4
+
5
+ Ethereum/EVM utilities built on [viem](https://www.npmjs.com/package/viem). Provides type-safe ERC20, EIP-2612 permit and EIP-3009 authorization interactions, batched on-chain queries via Multicall3, and low-level EVM binary layout primitives.
6
+
7
+ - [Contract Specs](#contract-specs) – declarative contract interface definitions
8
+ - [ERC20](#erc20) – read/write ERC20 methods via spec
9
+ - [EIP-2612 Permit](#eip-2612-permit) – on-chain permit calls and off-chain message composition
10
+ - [EIP-3009 Authorizations](#eip-3009-authorizations) – transfers by signed authorization, on-chain calls and message composition
11
+ - [Batched Queries](#batched-queries) – Multicall3-based read batching with block-consistent results
12
+ - [EVM Layout Primitives](#evm-layout-primitives) – word-aligned binary layouts, function selectors, storage slot computation
13
+ - [Hashing](#hashing) – keccak256, sha3_256
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @onrail-xyz/evm \
19
+ @onrail-xyz/utils @onrail-xyz/binary-layout @onrail-xyz/common @onrail-xyz/amount \
20
+ @noble/hashes viem
21
+ ```
22
+
23
+ The `@onrail-xyz/*` packages, `viem`, and `@noble/hashes`, which backs the hash re-exports, are peer dependencies — install them alongside `evm` (recent pnpm/npm auto-install peers; Yarn does not). Peering keeps a single shared `utils` (which anchors the SDK's branded types) and a single `viem` across the consuming app. `@noble/hashes` is accepted at `^1.8 || ^2`: the functions used are the same in both majors, so the copy viem pins serves. Node and TypeScript floors are [SDK-wide](https://github.com/Onrail/ts-sdk#requirements).
24
+
25
+ ## Contract Specs
26
+
27
+ `contractFromSpec` generates a typed contract interface from a declarative spec: a tuple of `abiFunction(name, params, composer, outputLayout?)` rows.
28
+
29
+ - **Params** are `abiParam(name, solidityType, item)` triples: the type is the string the ABI uses, the item says how its slot reads. A parameter spanning several words that is not a tuple — a signature's `(v, r, s)` — spells its types comma-joined, `"uint8,bytes32,bytes32"`, so the signature string reads as if they were separate parameters. The surfaced value type is the item's; where `DeriveType` cannot reduce it — an amount at an open kind — `abiParam<V>()(name, type, item)` states it, the zero-argument overload taking `V` alone so the rest stays inferred; `paramOf<V>()(type, item)` is the same door for a factory. Two more exist beside it: `amountParam(name, type, kind?, size?)`, which states `AmountOr<K, NumSizeToPrimitive<S>>` — an `Amount<K>` given a kind, otherwise the primitive the item's width derives — and `trailingBytesParam(name, layout?)`, which carries the dynamic marker `abiParam` cannot express. Note the ABI type and the item's width are independent: the type is what the slot is spelled as, the width is the domain's range, so a `uint64` amount the contract holds is `amountParam(name, "uint256", kind, 8)` — an 8-byte item on a `uint256` word, which rejects a value the contract could not hold where a word-wide item would pass it. What recurs across a domain's tables is the type-and-item pair, not the name, so `paramOf(type, item)` fixes the pair and returns a factory taking the name — `addressParam` is `paramOf("address", addressItem)`, and a domain spells its own the same way (`const usdcParam = paramOf("uint256", usdcItem)`). Beyond that there is no shared parameter vocabulary — names are cheap to spell and each table defines the handful it reuses.
30
+ - **Read calls** (output layout given) return a layout triple `[layout, params, outputLayout]` for use with the query function from `createQuery`.
31
+ - **Write calls** (no output layout) return pre-serialized call data; the sender is the transaction's business, so none is taken. `toViemTx` bridges the result to viem's transaction parameters.
32
+ - The **composer** maps positional arguments to the params object, giving every method both a positional and an object overload. It is typed from the params, so it is written with names only. The two forms are told apart by arity, except for a single object argument to a single-parameter method, which is read as the object form iff its one key is the parameter's name — so a positional record whose sole key happens to be the parameter's name, or an object-form value carrying extra keys, is read the other way. Name such a parameter after something other than its record's only key, or pass the object form with exactly the one key.
33
+ - **Solidity overloads** share an ABI name, so a row whose method key must differ names both, key first: `abiFunction(["safeTransferFromWithData", "safeTransferFrom"], [from, to, tokenId, data], ...)`. The key names the method on the generated interface; the ABI name goes into the selector.
34
+ - **Events and errors** are `[signature, struct]` pairs, listed as the variants of `buildParseEvent` / `buildParseError` (`erc20Events`, say); `sigVariant(name, ...params)` spells one from the same params. The struct is in *wire* order. For an error that is the parameter order. For an event it is the indexed parameters first, then the rest, each group in declaration order — which is `sigVariant`'s param order exactly when the indexed parameters lead the declaration, as they conventionally do. An event whose declaration interleaves them is spelled as a pair directly: the signature in declaration order, the struct in wire order. An indexed `bytes`, `string`, array or struct parameter is its keccak on the wire, so its item is `hashItem`. Since topics and data are read as one sequence, events that share a signature but not its indexing decode alike (ERC-721's `Transfer` indexes the token id that ERC-20's carries as data): parse the logs of known contracts, filtered by address.
35
+
36
+ The pieces are typed too. A row is a `FuncSpec`, named by a `FuncName` — a string, or a `[key, abiName]` pair whose `FuncKey` names the method. Params are `AbiParam`s (`DynamicAbiParam` for the trailing one), each surfacing its `AbiParamValue`; for a param list, `ParamsStruct` is its items, `ParamsLayout` the call layout they form, and `ParamsRecord` the record the composer builds. `ContractMethods<S>` is the interface `contractFromSpec` generates. A variant is a `SigVariant` (a non-empty tuple of them, `SigVariants`), and `SigVariantOf<N, P>` is the one `sigVariant` spells; `signatureOf(name, params)` is the signature string a spec derives, `sigNameOf`/`SigNameOf` read the name back. `buildParseError` reads revert data and `buildParseEvent` a `ViemLog`, yielding `ParsedError<V>`/`ParsedEvent<V>`; both run on `sigSwitchItem(idSize, tag)`, a *switch* keyed by a signature hash's leading bytes (4 for errors, the whole 32-byte topic for events), whose returned factory is a `SigSwitcher`.
37
+
38
+ ### What specs cover, and what they do not
39
+
40
+ **This is not a full ABI codec.** A spec lays each parameter out on its ABI slots with `binary-layout`, which buys the typed, branded values this SDK is built on — an `Amount`, a `Deadline`, a domain type — at a fraction of an encoder's cost. The price is that the argument shapes it can express are a subset of the ABI's, and the boundary is *static versus dynamic*, not "primitives versus structs".
41
+
42
+ Covered:
43
+
44
+ - Any statically sized parameter — `address`, `bool`, `uintN`, `intN`, `bytesN`. A signed parameter narrower than a word takes the word's own width on the wire, since the ABI sign-extends it, and a value its declared width cannot hold is rejected. A `bytesN` is `bytesNItem(N)`: the one static type the ABI pads on the *right*, so it is spelled at word width, whereas a bare `{ binary: "bytes", size }` on a slot is left-padded like every other value — which is how `addressItem`, a bare 20-byte item, lands on its `uint160` slot.
45
+ - **Static tuples**, which the ABI encodes inline, as one multi-word parameter whose type is paren-spelled: `abiParam("order", "(uint256,address)", { binary: "bytes", layout: { amount: uint256Item, taker: abiAddressItem } })`. A tuple's members each occupy a whole slot, so each is padded individually (`paddedFields` does that for a struct of sub-word items). Nesting is fine as long as every member is static.
46
+ - **Fixed-size arrays of static types** (`uint256[2]`), likewise inline.
47
+ - **One trailing dynamic argument**, via `trailingBytesParam(name, layout?)` — the common `f(..., bytes data)` shape (CCTP's `messageBody` and `hookData`, Uniswap v4's `hookData`). Given a `layout` it carries that structure's serialization rather than raw bytes.
48
+ - **A single dynamic return value.** An output layout is handed straight to `deserialize` rather than laid out on slots, so `abiEncodedBytesItem` spells one — which is what `erc20`'s `name` and `symbol` do. It is an `AbiEncodedBytesItem<L>`, deriving `AbiEncoded<L>`: the content layout's type, or raw bytes without one. Several of them hit the same offset problem as several dynamic arguments.
49
+
50
+ Not covered:
51
+
52
+ - **Dynamic arrays** — `address[]`, `uint256[]`, `bytes[]`.
53
+ - **More than one dynamic parameter**, or one that is not last. With two, each offset depends on the lengths of the tails ahead of it; nothing constant remains to bake into a layout, which is where clever layouts end and an encoder begins. A spec that asks for this through `trailingBytesParam` anywhere but last throws when it is built. A plain `abiParam` is not parsed for its type string: the item is the author's statement of how the slots read, and the type string is only what goes into the selector, so a dynamic type over a static item is the author's error and encodes inline, wrongly.
54
+ - **Dynamic tuples** — a tuple with any dynamic member is itself dynamic, offset pointer and all.
55
+
56
+ These exclusions are somewhat common: ERC-1155's `safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)` and any router taking a multi-hop path or a batch of calls fall within that limitation and require a complete encoder like viem's `encodeFunctionData`.
57
+
58
+
59
+ ```ts
60
+ import { contractFromSpec, abiFunction, addressParam, amountParam, evmAmountItem } from "@onrail-xyz/evm";
61
+
62
+ //a spec's parameter vocabulary is its own
63
+ const owner = addressParam("owner");
64
+ const to = addressParam("to");
65
+ const value = amountParam("value", "uint256");
66
+
67
+ const spec = [
68
+ abiFunction("balanceOf", [owner], owner => ({ owner }), evmAmountItem()),
69
+ abiFunction("transfer", [to, value], (to, value) => ({ to, value })),
70
+ ] as const;
71
+
72
+ const contract = contractFromSpec("0xA0b8...eB48", spec);
73
+
74
+ // Read call — returns { to, data: [layout, params, outputLayout] }
75
+ const call = contract.balanceOf({ owner: "0xd8dA...6045" });
76
+
77
+ // Write call — returns { to, data: Uint8Array }
78
+ const tx = contract.transfer({ to: "0xd8dA...6045", value: 500n });
79
+ ```
80
+
81
+ `toViemTx` converts a `ContractTx` — a write call's result, optionally carrying a `from`, a `value` and an access list — into viem's transaction parameters (a `ViemTx`): call data as hex, the value as an atomic `bigint`, and the sender under viem's name for it, `account`.
82
+
83
+ ```ts
84
+ import { toViemTx } from "@onrail-xyz/evm";
85
+
86
+ await walletClient.sendTransaction(toViemTx(tx));
87
+ ```
88
+
89
+ `addressItem` surfaces the EIP-55 checksummed spelling, matching what viem's decoders return — a lowercase one would make the same word decode to two strings that compare unequal. Either spelling is accepted on the way in.
90
+
91
+ ## ERC20
92
+
93
+ Built on `contractFromSpec`. Provides `name`, `symbol`, `decimals`, `totalSupply`, `balanceOf`, `allowance`, `approve`, `transfer`, and `transferFrom`. The optional `kind` parameter enables typed `Amount` values via `@onrail-xyz/amount`.
94
+
95
+ `erc20Events` lists `Transfer` and `Approval` as variants for `buildParseEvent`. `allowanceAdjusters` provides `increaseAllowance` and `decreaseAllowance` the same way as `erc20`. They are OpenZeppelin's extension rather than ERC-20, dropped from OpenZeppelin 5.0 but implemented by USDC and most tokens built on earlier releases, hence a spec of their own.
96
+
97
+ ```ts
98
+ import { createPublicClient, http } from "viem";
99
+ import { mainnet } from "viem/chains";
100
+ import { erc20, createQuery } from "@onrail-xyz/evm";
101
+
102
+ const client = createPublicClient({ chain: mainnet, transport: http() });
103
+ const query = createQuery(client);
104
+ const usdc = erc20("0xA0b8...eB48");
105
+
106
+ // Read calls — use with the query function from createQuery
107
+ const [[name, decimals, balance]] = await query([
108
+ usdc.name(),
109
+ usdc.decimals(),
110
+ usdc.balanceOf({ owner: "0xd8dA...6045" }),
111
+ ]);
112
+
113
+ // Write calls
114
+ const approveTx = usdc.approve({ spender: spenderAddr, value: 1000n });
115
+ const transferTx = usdc.transfer({ to: toAddr, value: 500n });
116
+ ```
117
+
118
+ ## EIP-2612 Permit
119
+
120
+ ### On-chain calls
121
+
122
+ The `permit` function provides `DOMAIN_SEPARATOR`, `nonces`, and `permit` methods via `contractFromSpec`:
123
+
124
+ ```ts
125
+ import { permit } from "@onrail-xyz/evm";
126
+
127
+ const p = permit("0xA0b8...eB48");
128
+
129
+ // Read the domain separator and nonce
130
+ const [[domainSep, nonce]] = await query([
131
+ p.DOMAIN_SEPARATOR(),
132
+ p.nonces({ owner: ownerAddr }),
133
+ ]);
134
+
135
+ // Submit a permit
136
+ const tx = p.permit({
137
+ owner: ownerAddr, spender: spenderAddr, value: 1000n,
138
+ deadline: new Date("2030-01-01"), signature,
139
+ });
140
+ ```
141
+
142
+ `deadline` is a `Date` or `"infinity"`, the latter encoding the maxed out uint256 that means "never expires". `signature` is the 65 packed bytes a signer returns (`hex.decode(await walletClient.signTypedData(...))`); the spec spreads them over the `v`, `r`, `s` words the Solidity signature takes (`abiSignatureItem`), so nothing is split by hand. `signatureItem` carries the packed bytes as is and `compactSignatureItem` EIP-2098's 64-byte form; all three type the signature as the packed bytes. `signatureSize` is those 65 bytes, and `signatureLayout`/`abiSignatureLayout` are the structs behind the items — `r`, `s`, `v` packed, and `v`, `r`, `s` on words. The deadline is `deadlineItem`, a word-wide timestamp saturating at `"infinity"` (a `Deadline`); `evmTimestampItem` is the same word without the sentinel.
143
+
144
+ ### Off-chain message composition
145
+
146
+ `guessEip712Domain` reconstructs the EIP-712 domain from on-chain data by brute-forcing the version field against the domain separator hash. It covers the `{ name, chainId, verifyingContract }` domain with an optional version, i.e. tokens following EIP-2612's reference implementation. `composePermitMsg` builds an EIP-2612 permit, and `toViemTypedData` hands it to viem: a composed message keeps its `bytes32` values (the domain's `salt`, an EIP-3009 `nonce`) as byte arrays and an all-optional domain, whereas viem wants hex and derives the domain's required fields from `types.EIP712Domain`. A message is an `Eip712Message<F>`, the object its `Eip712Field` list describes, and a composed one an `Eip712Data` (`Eip2612Message`/`Eip2612Data` for a permit) with an `Eip712Domain`; `toViemTypedData` returns a `ViemTypedData<T>`, and `eip712DomainType(domain)` is the domain's field list as viem derives it.
147
+
148
+ Only EIP-2612 is covered — DAI-style permits (`nonce`/`expiry`/`allowed` instead of `value`/`deadline`, and hence a different struct and selector) need their own spec.
149
+
150
+ ```ts
151
+ import { composePermitMsg, guessEip712Domain, toViemTypedData } from "@onrail-xyz/evm";
152
+
153
+ const domain = guessEip712Domain(
154
+ tokenName, contractAddress, chainId, domainSeparator,
155
+ );
156
+
157
+ const permitData = composePermitMsg(
158
+ owner, spender, amount, domain, nonce,
159
+ deadline, // optional — defaults to max uint256
160
+ );
161
+
162
+ const signature = await walletClient.signTypedData(toViemTypedData(permitData));
163
+ ```
164
+
165
+ `eip712EncodeDataLayout(fields)` spells a struct's `encodeData` as a layout, an `Eip712EncodeDataLayout<F>`: each atomic field (`bool`, `address`, `uintN`, `intN`, `bytesN`) on a word, in field order. Serializing a message through it yields the bytes `hashStruct` hashes after the type hash, and deserializing derives a message from any data that already has a word-per-field layout — a command a contract also accepts through its ABI, say — so the message need not be composed by hand. It is atomic-only: a dynamic, array or struct-typed field takes a word holding a hash rather than the value, which the layout does not compute, so a field list with one is rejected.
166
+
167
+ ## EIP-3009 Authorizations
168
+
169
+ [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) moves tokens on the holder's signature alone: whoever submits the authorization pays the gas, and the receiving variant additionally requires the submitter to be the recipient. `erc3009` provides `authorizationState`, `transferWithAuthorization`, `receiveWithAuthorization` and `cancelAuthorization` via `contractFromSpec`; USDC implements the standard.
170
+
171
+ ```ts
172
+ import { erc3009, composeTransferWithAuthorizationMsg, randomAuthorizationNonce, toViemTypedData } from "@onrail-xyz/evm";
173
+
174
+ const nonce = randomAuthorizationNonce();
175
+ const authorization = composeTransferWithAuthorizationMsg(
176
+ holder, recipient, amount, domain, nonce,
177
+ validBefore, // optional — defaults to max uint256
178
+ validAfter, // optional — defaults to the epoch, i.e. valid immediately
179
+ );
180
+ const signature = await walletClient.signTypedData(toViemTypedData(authorization));
181
+
182
+ const a = erc3009("0xA0b8...eB48");
183
+ const tx = a.transferWithAuthorization({
184
+ from: holder, to: recipient, value: amount, validAfter, validBefore, nonce,
185
+ signature: hex.decode(signature),
186
+ });
187
+
188
+ // A pending authorization is revoked by its holder through a second signature
189
+ const cancellation = composeCancelAuthorizationMsg(holder, domain, nonce);
190
+ ```
191
+
192
+ `composeReceiveWithAuthorizationMsg` is the receiving variant's twin, taking the same arguments; the messages are `Eip3009AuthorizationMessage` and `Eip3009CancelMessage`.
193
+
194
+ Nonces are 32 random bytes rather than a counter, so several authorizations can stand at once and composing one needs no chain state; `authorizationState(authorizer, nonce)` reads whether a nonce has been used or canceled, and `erc3009Events` lists the standard's `AuthorizationUsed` / `AuthorizationCanceled` for `buildParseEvent`. The standard leaves revert reasons to implementations, so none are listed.
195
+
196
+ ## Batched Queries
197
+
198
+ `createQuery(client)` returns a `query` function that batches arbitrary read calls into a single [Multicall3](https://www.multicall3.com/) request and returns the results together with the block they were read from — so all reads come from the same point in time.
199
+
200
+ Consistency is not merely a matter of batching: the block tag is first resolved to a block hash, and the batch is then executed against that hash with `requireCanonical`, so a reorg fails the query (and is retried) instead of silently mixing states. This requires an RPC that supports [EIP-1898](https://eips.ethereum.org/EIPS/eip-1898) block hash parameters, plus archive state for blocks beyond the pruning window.
201
+
202
+ ```ts
203
+ import { createPublicClient, http } from "viem";
204
+ import { mainnet } from "viem/chains";
205
+ import { createQuery, erc20 } from "@onrail-xyz/evm";
206
+
207
+ const client = createPublicClient({ chain: mainnet, transport: http() });
208
+ const query = createQuery(client);
209
+
210
+ const token = erc20("0xA0b8...eB48");
211
+
212
+ const [[balance, allowance], blockNumber, blockHash, blockTime] = await query(
213
+ [
214
+ token.balanceOf({ owner: "0xd8dA...6045" }),
215
+ token.allowance({ owner: "0xd8dA...6045", spender: "0xBEEF...0000" }),
216
+ ],
217
+ "latest",
218
+ );
219
+ ```
220
+
221
+ The block can also be given as a number, as `"finalized"`, or as a block hash — the latter pins the query directly and hence yields the results only, without the block meta tuple. The types follow: the block is a `BlockSpec`, the results a `QueryResult` or `QueryResultWithMeta`, and `Query` is the function `createQuery` returns.
222
+
223
+ A call is a `QueryCall`, and its data a `QueryCallData` in one of three formats — raw bytes, a `QueryLayoutTriple`, or a `QueryAbiPair` whose result is its `QueryAbiReturn` — which mix freely, even within the same batch:
224
+
225
+ ```ts
226
+ const [[rawBytes, amount, viemDecoded]] = await query(
227
+ [
228
+ // 1. Raw bytes — pre-serialized in, raw bytes out
229
+ { to: tokenAddr, data: serialize(balanceOfLayout, { owner }) },
230
+ // 2. Layout triple — deserialized via the output layout
231
+ { to: tokenAddr, data: [balanceOfLayout, { owner }, evmAmountItem(ethKind)] },
232
+ // 3. Function signature — viem ABI-encodes the call and decodes the return
233
+ { to: tokenAddr, data: ["balanceOf(address) view returns (uint256)", [owner]] },
234
+ ],
235
+ "latest",
236
+ );
237
+ ```
238
+
239
+ A reverting call fails the whole query unless it opts into `allowFailure`, which turns its result into a `{ success, data }` pair — with the raw revert data on failure — while the rest of the batch is unaffected:
240
+
241
+ ```ts
242
+ const [[maybeAllowance]] = await query([
243
+ { to: tokenAddr, data: [allowanceLayout, { owner, spender }, uint256Item], allowFailure: true },
244
+ ]);
245
+ if (maybeAllowance.success)
246
+ console.log(maybeAllowance.data);
247
+ ```
248
+
249
+ ## EVM Layout Primitives
250
+
251
+ The plumbing that the rest of the package is built on. Layout items for EVM's 32-byte word-aligned world, function selector helpers, and storage slot computation — all plugging into `@onrail-xyz/binary-layout`.
252
+
253
+ ```ts
254
+ import {
255
+ uint256Item,
256
+ addressItem,
257
+ bytesNItem,
258
+ signatureItem,
259
+ selectorOf,
260
+ selectorLayout,
261
+ evmAmountItem,
262
+ mappingSlot,
263
+ paddedSlotLayout,
264
+ paddedFields,
265
+ } from "@onrail-xyz/evm";
266
+
267
+ // Compute a 4-byte function selector — the signature must be canonical, i.e. no parameter
268
+ // names, no whitespace, and no type aliases (`uint256`, not `uint`)
269
+ const sel = selectorOf("transfer(address,uint256)");
270
+
271
+ // Wrap a struct with a function selector prefix
272
+ const transferLayout = selectorLayout("transfer(address,uint256)")({
273
+ to: paddedSlotLayout(addressItem),
274
+ value: uint256Item,
275
+ });
276
+
277
+ // The ABI's bytes4 on its word: right-padded, unlike everything else on a slot
278
+ const tag = bytesNItem(4);
279
+
280
+ // Compute a Solidity mapping storage slot
281
+ const slot = mappingSlot(key, declarationSlot);
282
+
283
+ // Put every field of a struct on a slot of its own - the ABI's static parameter list
284
+ const paramsLayout = paddedFields({ to: addressItem, value: uint256Item, flag: boolItem() });
285
+ ```
286
+
287
+ **Storage slots:** `mappingSlot(key, slot)` hashes `key . slot` for a value-type key, padded to the word the way Solidity pads its type, which the key's JS type names: a `bigint`/`number` is an integer (a negative one sign-extended), a `boolean` a bool, an `Address` an address — all right-aligned — and a `Uint8Array` a `bytesN`, left-aligned, so a full word is itself (chain the calls for nested mappings; `string`/`bytes` keys are hashed unpadded and are not covered). `keccakSlot(slot)` yields the first element's slot of a dynamic array.
288
+
289
+ **More items:** `abiBoolItem` is a bool on its word, `selectorItem(sig)` a selector as a fixed constant, and `addressConversion` the EIP-55 conversion `addressItem` (an `AddressItem`) carries. `paddedSlotLayout` makes a `PaddedSlotLayout<T>` of an item — for a signed one a `SignedSlotItem`, sign-extended onto the word — and `paddedFields` a `PaddedFields<S>`; `bytesNItem(N)` is a `BytesNItem<N>`, and a mapping key a `MappingKey`.
290
+
291
+ **Constants:** `wordSize` (32), `addressSize` (20), `selectorLength` (4).
292
+
293
+ ## Hashing
294
+
295
+ Re-exports of the hashes the EVM runs on, from [@noble/hashes](https://github.com/paulmillr/noble-hashes):
296
+
297
+ ```typescript
298
+ import { keccak256, sha3_256 } from "@onrail-xyz/evm";
299
+ ```
300
+
301
+ `keccak256` is the pre-standardization padding Ethereum uses, not `sha3_256` — the two disagree on every input.
@@ -0,0 +1,56 @@
1
+ import type { Address, AccessList, Hex } from "viem";
2
+ import type { RoUint8Array, RoTuple, Function, OptionalArg } from "@onrail-xyz/utils";
3
+ import type { Layout } from "@onrail-xyz/binary-layout";
4
+ import type { KindWithAtomic } from "@onrail-xyz/amount";
5
+ import { type AmountOrAtomic } from "@onrail-xyz/common";
6
+ import type { AbiParam, AbiParamValue, PaddedSlotLayout } from "./layouting.js";
7
+ import type { QueryLayoutTriple } from "./query.js";
8
+ export type ContractTx<K extends KindWithAtomic | undefined = KindWithAtomic | undefined> = {
9
+ to: Address;
10
+ from?: Address;
11
+ value?: AmountOrAtomic<K>;
12
+ data: RoUint8Array;
13
+ accessList?: AccessList;
14
+ };
15
+ export type ViemTx = {
16
+ readonly to: Address;
17
+ readonly data: Hex;
18
+ readonly account?: Address;
19
+ readonly value?: bigint;
20
+ readonly accessList?: AccessList;
21
+ };
22
+ export declare const toViemTx: (tx: ContractTx) => ViemTx;
23
+ export type ParamsStruct<P extends RoTuple<AbiParam>> = {
24
+ readonly [E in P[number] as E["name"]]: E["item"];
25
+ };
26
+ export type ParamsLayout<P extends RoTuple<AbiParam>> = {
27
+ readonly [E in P[number] as E["name"]]: E extends {
28
+ readonly dynamic: object;
29
+ } ? E["item"] : PaddedSlotLayout<E["item"]>;
30
+ };
31
+ type ParamValues<P extends RoTuple<AbiParam>> = {
32
+ [K in keyof P]: P[K] extends AbiParam ? AbiParamValue<P[K]> : never;
33
+ };
34
+ export type ParamsRecord<P extends RoTuple<AbiParam>> = {
35
+ readonly [E in P[number] as E["name"]]: AbiParamValue<E>;
36
+ };
37
+ export type FuncName = string | readonly [key: string, abiName: string];
38
+ export type FuncKey<N extends FuncName> = N extends readonly [infer K extends string, string] ? K : N;
39
+ export type FuncSpec<N extends FuncName = FuncName, P extends RoTuple<AbiParam> = RoTuple<AbiParam>, C extends Function<any> = Function<any>, R extends Layout | undefined = Layout | undefined> = readonly [N, P, C, R];
40
+ type ContractSpec = RoTuple<FuncSpec>;
41
+ export declare const abiFunction: <const N extends FuncName, const P extends RoTuple<AbiParam>, C extends (...args: ParamValues<P>) => ParamsRecord<P>, const R extends Layout | undefined = undefined>(name: N, params: P, composer: C, ...output: OptionalArg<R>) => FuncSpec<N, P, C, R>;
42
+ type ReadResult<P extends RoTuple<AbiParam>, OL extends Layout> = {
43
+ readonly to: Address;
44
+ readonly data: QueryLayoutTriple<ParamsLayout<P>, OL>;
45
+ };
46
+ type WriteResult = Readonly<{
47
+ to: Address;
48
+ data: Uint8Array;
49
+ }>;
50
+ type ContractMethodOf<P extends RoTuple<AbiParam>, OL, C extends Function<any>> = OL extends Layout ? ((params: ParamsRecord<P>) => ReadResult<P, OL>) & ((...args: Parameters<C>) => ReadResult<P, OL>) : ((params: ParamsRecord<P>) => WriteResult) & ((...args: Parameters<C>) => WriteResult);
51
+ export type ContractMethods<S extends RoTuple<any>> = {
52
+ readonly [E in S[number] as E extends FuncSpec<infer N> ? FuncKey<N> : never]: E extends FuncSpec<FuncName, infer P, infer C, infer OL> ? ContractMethodOf<P, OL, C> : never;
53
+ };
54
+ export declare const contractFromSpec: <const S extends ContractSpec>(contract: Address, spec: S) => ContractMethods<S>;
55
+ export {};
56
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}