@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 +201 -0
- package/README.md +301 -0
- package/dist/client.d.ts +56 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +79 -0
- package/dist/eip712.d.ts +72 -0
- package/dist/eip712.d.ts.map +1 -0
- package/dist/eip712.js +96 -0
- package/dist/erc20.d.ts +113 -0
- package/dist/erc20.d.ts.map +1 -0
- package/dist/erc20.js +44 -0
- package/dist/erc3009.d.ts +416 -0
- package/dist/erc3009.d.ts.map +1 -0
- package/dist/erc3009.js +72 -0
- package/dist/hashing.d.ts +2 -0
- package/dist/hashing.d.ts.map +1 -0
- package/dist/hashing.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/layouting.d.ts +264 -0
- package/dist/layouting.d.ts.map +1 -0
- package/dist/layouting.js +238 -0
- package/dist/parsing.d.ts +16 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +26 -0
- package/dist/permit.d.ts +139 -0
- package/dist/permit.d.ts.map +1 -0
- package/dist/permit.js +38 -0
- package/dist/query.d.ts +54 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +105 -0
- package/package.json +70 -0
- package/src/client.ts +196 -0
- package/src/eip712.ts +215 -0
- package/src/erc20.ts +76 -0
- package/src/erc3009.ts +122 -0
- package/src/hashing.ts +1 -0
- package/src/index.ts +9 -0
- package/src/layouting.ts +491 -0
- package/src/parsing.ts +43 -0
- package/src/permit.ts +68 -0
- package/src/query.ts +257 -0
package/src/query.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import type { Abi, AbiStateMutability, Address, Hex, PublicClient, ParseAbi,
|
|
2
|
+
ContractFunctionName, ContractFunctionArgs, ContractFunctionReturnType } from "viem";
|
|
3
|
+
import { multicall3Abi, encodeFunctionData, decodeFunctionResult, parseAbi } from "viem";
|
|
4
|
+
import type { RoUint8Array, RoArray, RoPair } from "@onrail-xyz/utils";
|
|
5
|
+
import { type MaybeArray, isArray,
|
|
6
|
+
isUint8Array, throwOnNullish, hex, unixTime } from "@onrail-xyz/utils";
|
|
7
|
+
import type { Layout, DeriveType } from "@onrail-xyz/binary-layout";
|
|
8
|
+
import { serialize, deserialize } from "@onrail-xyz/binary-layout";
|
|
9
|
+
import type { SigNameOf } from "./layouting.js";
|
|
10
|
+
import { sigNameOf } from "./layouting.js";
|
|
11
|
+
|
|
12
|
+
// ---- ABI string type utilities ----
|
|
13
|
+
//signatures omit the `function` keyword: "balanceOf(address) view returns (uint256)"
|
|
14
|
+
|
|
15
|
+
//viem couldn't be bothered to think about the order of their type parameters and what their users
|
|
16
|
+
// actually need, so we have to drag in AbiStateMutability when we don't care about it
|
|
17
|
+
type ViemResolve<A extends Abi, N extends ContractFunctionName<A>, T extends "params" | "return"> =
|
|
18
|
+
T extends "params"
|
|
19
|
+
? ContractFunctionArgs<A, AbiStateMutability, N>
|
|
20
|
+
: ContractFunctionReturnType<A, AbiStateMutability, N>;
|
|
21
|
+
|
|
22
|
+
type AbiResolve<S extends string, T extends "params" | "return"> =
|
|
23
|
+
string extends S
|
|
24
|
+
? T extends "params"
|
|
25
|
+
? RoArray
|
|
26
|
+
: unknown
|
|
27
|
+
: ParseAbi<[`function ${S}`]> extends infer A extends Abi
|
|
28
|
+
? SigNameOf<S> extends infer N extends ContractFunctionName<A>
|
|
29
|
+
? ViemResolve<A, N, T>
|
|
30
|
+
: never
|
|
31
|
+
: never;
|
|
32
|
+
|
|
33
|
+
type AbiParameters<S extends string> = AbiResolve<S, "params">;
|
|
34
|
+
|
|
35
|
+
export type QueryAbiReturn<S extends string> = AbiResolve<S, "return">;
|
|
36
|
+
|
|
37
|
+
export type QueryAbiPair<S extends string = string> = RoPair<S, AbiParameters<S>>;
|
|
38
|
+
|
|
39
|
+
export type QueryLayoutTriple<CL extends Layout = Layout, RL extends Layout = Layout> =
|
|
40
|
+
readonly [CL, DeriveType<CL>, RL];
|
|
41
|
+
|
|
42
|
+
export type QueryCallData = RoUint8Array | QueryLayoutTriple | QueryAbiPair;
|
|
43
|
+
|
|
44
|
+
// ---- QueryCall ----
|
|
45
|
+
|
|
46
|
+
export type QueryCall<D extends QueryCallData = QueryCallData> =
|
|
47
|
+
{ readonly to: Address; readonly data: D; readonly allowFailure?: boolean };
|
|
48
|
+
|
|
49
|
+
type _CheckCall<C> =
|
|
50
|
+
C extends { data: infer D }
|
|
51
|
+
? D extends RoUint8Array
|
|
52
|
+
? RoUint8Array
|
|
53
|
+
: D extends QueryLayoutTriple<infer CL extends Layout, infer RL extends Layout>
|
|
54
|
+
? QueryLayoutTriple<CL, RL>
|
|
55
|
+
: D extends QueryAbiPair<infer S extends string>
|
|
56
|
+
? QueryAbiPair<S>
|
|
57
|
+
: never
|
|
58
|
+
: never;
|
|
59
|
+
|
|
60
|
+
type CheckCall<C> = Omit<C, "data"> & { data: _CheckCall<C> };
|
|
61
|
+
|
|
62
|
+
type CheckCalls<A> =
|
|
63
|
+
A extends RoArray<QueryCall>
|
|
64
|
+
? { [K in keyof A]: CheckCall<A[K]> }
|
|
65
|
+
: CheckCall<A>;
|
|
66
|
+
|
|
67
|
+
// ---- Result type mapping ----
|
|
68
|
+
|
|
69
|
+
type ResultOf<D> =
|
|
70
|
+
D extends RoUint8Array ? RoUint8Array :
|
|
71
|
+
D extends QueryLayoutTriple<Layout, infer RL extends Layout> ? DeriveType<RL> :
|
|
72
|
+
D extends QueryAbiPair<infer S extends string> ? QueryAbiReturn<S> :
|
|
73
|
+
never;
|
|
74
|
+
|
|
75
|
+
type WrappedResult<C extends QueryCall> =
|
|
76
|
+
{ success: true; data: ResultOf<C["data"]> } | { success: false; data: RoUint8Array };
|
|
77
|
+
|
|
78
|
+
type ByAllowFailure<F, C extends QueryCall> =
|
|
79
|
+
F extends true ? WrappedResult<C> : ResultOf<C["data"]>; //intentional distribution
|
|
80
|
+
|
|
81
|
+
type CallResult<C> = C extends QueryCall ? ByAllowFailure<C["allowFailure"], C> : never;
|
|
82
|
+
|
|
83
|
+
export type QueryResult<A extends MaybeArray<QueryCall>> =
|
|
84
|
+
A extends RoArray<QueryCall>
|
|
85
|
+
? { [K in keyof A]: CallResult<A[K]> }
|
|
86
|
+
: CallResult<A>;
|
|
87
|
+
|
|
88
|
+
export type QueryResultWithMeta<A extends MaybeArray<QueryCall>> =
|
|
89
|
+
[QueryResult<A>, bigint, RoUint8Array, Date];
|
|
90
|
+
|
|
91
|
+
// ---- Runtime encoding/decoding ----
|
|
92
|
+
|
|
93
|
+
const viemAbi = (sig: string) => ({
|
|
94
|
+
abi: parseAbi([`function ${sig}` as string]) as Abi,
|
|
95
|
+
functionName: sigNameOf(sig),
|
|
96
|
+
} as const);
|
|
97
|
+
|
|
98
|
+
const encodeCallData = (data: QueryCallData): Hex =>
|
|
99
|
+
!Array.isArray(data)
|
|
100
|
+
? hex.encode(data as RoUint8Array, true)
|
|
101
|
+
: typeof data[0] === "string"
|
|
102
|
+
? encodeFunctionData({ ...viemAbi(data[0]), args: data[1] })
|
|
103
|
+
: hex.encode(serialize(data[0], data[1]), true)
|
|
104
|
+
|
|
105
|
+
const decodeResult = (data: QueryCallData, returnData: Hex) =>
|
|
106
|
+
!Array.isArray(data)
|
|
107
|
+
? hex.decode(returnData)
|
|
108
|
+
: typeof data[0] === "string"
|
|
109
|
+
? decodeFunctionResult({ ...viemAbi(data[0]), data: returnData })
|
|
110
|
+
: deserialize(data[2], hex.decode(returnData));
|
|
111
|
+
|
|
112
|
+
const processResult = (
|
|
113
|
+
call: QueryCall,
|
|
114
|
+
raw: ViemResolve<typeof multicall3Abi, "aggregate3", "return">[number],
|
|
115
|
+
) => {
|
|
116
|
+
const { success, returnData } = raw;
|
|
117
|
+
const data = success ? decodeResult(call.data, returnData) : hex.decode(returnData);
|
|
118
|
+
|
|
119
|
+
if (!raw.success && !call.allowFailure)
|
|
120
|
+
throw new Error(`Call to ${call.to} reverted: ${hex.encode(data as RoUint8Array, true)}`);
|
|
121
|
+
|
|
122
|
+
return call.allowFailure ? { success, data } : data;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// ---- createQuery ----
|
|
126
|
+
|
|
127
|
+
//Multicall3's deterministic deployment puts it at the same address on nearly every chain, but a
|
|
128
|
+
// few (zkSync and friends) deviate, hence the chain config takes precedence when it has an entry
|
|
129
|
+
const CANONICAL_MULTICALL3 = "0xcA11bde05977b3631167028862bE2a173976CA11" as const;
|
|
130
|
+
const multicall3Of = (viemClient: PublicClient) =>
|
|
131
|
+
viemClient.chain?.contracts?.multicall3?.address ?? CANONICAL_MULTICALL3;
|
|
132
|
+
|
|
133
|
+
const encodeAggregate3 = (calls: RoArray<QueryCall>): Hex =>
|
|
134
|
+
encodeFunctionData({
|
|
135
|
+
abi: multicall3Abi,
|
|
136
|
+
functionName: "aggregate3",
|
|
137
|
+
args: [calls.map(c => ({
|
|
138
|
+
target: c.to,
|
|
139
|
+
allowFailure: true,
|
|
140
|
+
callData: encodeCallData(c.data),
|
|
141
|
+
}))],
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
async function aggregate3(viemClient: PublicClient, callData: Hex, blockHash: Hex) {
|
|
145
|
+
const returnData = await viemClient.request({
|
|
146
|
+
method: "eth_call",
|
|
147
|
+
params: [
|
|
148
|
+
{ to: multicall3Of(viemClient), data: callData },
|
|
149
|
+
{ blockHash, requireCanonical: true },
|
|
150
|
+
],
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return decodeFunctionResult({
|
|
154
|
+
abi: multicall3Abi,
|
|
155
|
+
functionName: "aggregate3",
|
|
156
|
+
data: returnData,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const MAX_RETRIES = 3;
|
|
161
|
+
|
|
162
|
+
export type BlockSpec<S extends "hash" | "ref" = "hash" | "ref"> =
|
|
163
|
+
S extends "hash"
|
|
164
|
+
? RoUint8Array
|
|
165
|
+
: bigint | "latest" | "finalized";
|
|
166
|
+
|
|
167
|
+
//resolves a block tag or number to a canonical block hash, retrying whenever the batch does not
|
|
168
|
+
// come back, which is what a reorged-out block looks like from here
|
|
169
|
+
async function resolveBlock(
|
|
170
|
+
viemClient: PublicClient,
|
|
171
|
+
callData: Hex,
|
|
172
|
+
block: BlockSpec<"ref">,
|
|
173
|
+
) {
|
|
174
|
+
const blockIdentifier = typeof block === "bigint" ? { blockNumber: block } : { blockTag: block };
|
|
175
|
+
let lastError: unknown;
|
|
176
|
+
for (let attempt = 0; attempt < MAX_RETRIES; ++attempt) {
|
|
177
|
+
const blockInfo = await viemClient.getBlock(blockIdentifier);
|
|
178
|
+
|
|
179
|
+
const blockHash = throwOnNullish(blockInfo.hash, "Block hash is null (pending block)");
|
|
180
|
+
|
|
181
|
+
const results = await aggregate3(viemClient, callData, blockHash)
|
|
182
|
+
.catch(error => { lastError = error; return undefined; });
|
|
183
|
+
if (results === undefined)
|
|
184
|
+
continue;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
results,
|
|
188
|
+
blockInfo: {
|
|
189
|
+
number: blockInfo.number,
|
|
190
|
+
hash: hex.decode(blockHash),
|
|
191
|
+
timestamp: unixTime.toDate(blockInfo.timestamp),
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Querying canonical block failed ${MAX_RETRIES} times in a row`,
|
|
197
|
+
{ cause: lastError },
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const _query = async (
|
|
202
|
+
viemClient: PublicClient,
|
|
203
|
+
callS: MaybeArray<QueryCall>,
|
|
204
|
+
block: BlockSpec,
|
|
205
|
+
) => {
|
|
206
|
+
const multi = isArray(callS);
|
|
207
|
+
const calls = multi ? callS : [callS];
|
|
208
|
+
//encoded before any block is resolved: a malformed argument is the caller's error, which the
|
|
209
|
+
// reorg retry must neither repeat nor report as a failed block
|
|
210
|
+
const callData = encodeAggregate3(calls);
|
|
211
|
+
|
|
212
|
+
return (isUint8Array(block)
|
|
213
|
+
? aggregate3(viemClient, callData, hex.encode(block, true))
|
|
214
|
+
: resolveBlock(viemClient, callData, block)
|
|
215
|
+
).then(raw => {
|
|
216
|
+
const [results, blockInfo] = "results" in raw
|
|
217
|
+
? [raw.results, raw.blockInfo]
|
|
218
|
+
: [raw, undefined];
|
|
219
|
+
|
|
220
|
+
const result = multi
|
|
221
|
+
? calls.map((c, i) => processResult(c, results[i]!))
|
|
222
|
+
: processResult(calls[0]!, results[0]!);
|
|
223
|
+
|
|
224
|
+
return blockInfo
|
|
225
|
+
? [result, blockInfo.number, blockInfo.hash, blockInfo.timestamp]
|
|
226
|
+
: result;
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export type Query = ReturnType<typeof createQuery>;
|
|
231
|
+
|
|
232
|
+
export const createQuery = (viemClient: PublicClient) => {
|
|
233
|
+
//query by blockhash -> only get actual result
|
|
234
|
+
function query<const A extends MaybeArray<QueryCall>>(
|
|
235
|
+
callS: A & CheckCalls<A>,
|
|
236
|
+
block: BlockSpec<"hash">,
|
|
237
|
+
): Promise<QueryResult<A>>;
|
|
238
|
+
//query by block ref -> get result + meta
|
|
239
|
+
function query<const A extends MaybeArray<QueryCall>>(
|
|
240
|
+
callS: A & CheckCalls<A>,
|
|
241
|
+
block?: BlockSpec<"ref">, //default = "latest"
|
|
242
|
+
): Promise<QueryResultWithMeta<A>>;
|
|
243
|
+
//convenience overload to allow both
|
|
244
|
+
function query<const A extends MaybeArray<QueryCall>>(
|
|
245
|
+
callS: A & CheckCalls<A>,
|
|
246
|
+
block: BlockSpec,
|
|
247
|
+
): Promise<QueryResult<A> | QueryResultWithMeta<A>>;
|
|
248
|
+
//impl
|
|
249
|
+
function query<const A extends MaybeArray<QueryCall>>(
|
|
250
|
+
callS: A & CheckCalls<A>,
|
|
251
|
+
block: BlockSpec = "latest",
|
|
252
|
+
) {
|
|
253
|
+
return _query(viemClient, callS, block);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return query;
|
|
257
|
+
};
|