@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/dist/permit.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Address } from "viem";
|
|
2
|
+
import type { OptionalArg } from "@onrail-xyz/utils";
|
|
3
|
+
import type { KindWithAtomic } from "@onrail-xyz/amount";
|
|
4
|
+
import { type AmountOrAtomic } from "@onrail-xyz/common";
|
|
5
|
+
import { type Deadline } from "./layouting.js";
|
|
6
|
+
import { type ContractMethods } from "./client.js";
|
|
7
|
+
import type { Eip712Domain, Eip712Data, Eip712Message } from "./eip712.js";
|
|
8
|
+
declare const permitSpec: <const K extends KindWithAtomic | undefined = undefined>(...kind: OptionalArg<K>) => readonly [import("./client.js").FuncSpec<"DOMAIN_SEPARATOR", readonly [], () => {}, {
|
|
9
|
+
readonly binary: "bytes";
|
|
10
|
+
readonly size: 32;
|
|
11
|
+
}>, import("./client.js").FuncSpec<"nonces", readonly [import("./layouting.js").AbiParam<"owner", "address", import("./layouting.js").AddressItem, `0x${string}`>], (owner: `0x${string}`) => {
|
|
12
|
+
owner: `0x${string}`;
|
|
13
|
+
}, {
|
|
14
|
+
readonly binary: "uint";
|
|
15
|
+
readonly size: 32;
|
|
16
|
+
}>, import("./client.js").FuncSpec<"permit", readonly [import("./layouting.js").AbiParam<"owner", "address", import("./layouting.js").AddressItem, `0x${string}`>, import("./layouting.js").AbiParam<"spender", "address", import("./layouting.js").AddressItem, `0x${string}`>, import("./layouting.js").AbiParam<"value", "uint256", import("@onrail-xyz/common").AmountItem<32, K>, import("@onrail-xyz/common").AmountOr<K, bigint>>, import("./layouting.js").AbiParam<"deadline", "uint256", import("@onrail-xyz/binary-layout").TimestampItem<{
|
|
17
|
+
readonly size: 32;
|
|
18
|
+
readonly saturateAs: "infinity";
|
|
19
|
+
}>, "infinity" | Date>, import("./layouting.js").AbiParam<"signature", "uint8,bytes32,bytes32", Readonly<{
|
|
20
|
+
binary: "bytes";
|
|
21
|
+
layout: {
|
|
22
|
+
readonly v: Readonly<{
|
|
23
|
+
binary: "bytes";
|
|
24
|
+
layout: {
|
|
25
|
+
readonly _padding: {
|
|
26
|
+
readonly binary: "bytes";
|
|
27
|
+
readonly fixed: Uint8Array<ArrayBuffer>;
|
|
28
|
+
};
|
|
29
|
+
readonly item: {
|
|
30
|
+
readonly binary: "uint";
|
|
31
|
+
readonly size: 1;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
custom: import("@onrail-xyz/binary-layout").Conversion<{
|
|
35
|
+
readonly item: number;
|
|
36
|
+
}, number>;
|
|
37
|
+
}>;
|
|
38
|
+
readonly r: {
|
|
39
|
+
readonly binary: "bytes";
|
|
40
|
+
readonly size: 32;
|
|
41
|
+
};
|
|
42
|
+
readonly s: {
|
|
43
|
+
readonly binary: "bytes";
|
|
44
|
+
readonly size: 32;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
custom: import("@onrail-xyz/binary-layout").Conversion<{
|
|
48
|
+
readonly v: number;
|
|
49
|
+
readonly r: import("@onrail-xyz/utils").RoUint8Array;
|
|
50
|
+
readonly s: import("@onrail-xyz/utils").RoUint8Array;
|
|
51
|
+
}, import("@onrail-xyz/utils").RoUint8Array>;
|
|
52
|
+
}>, import("@onrail-xyz/utils").RoUint8Array>], (owner: `0x${string}`, spender: `0x${string}`, value: import("@onrail-xyz/common").AmountOr<K, bigint>, deadline: "infinity" | Date, signature: import("@onrail-xyz/utils").RoUint8Array) => {
|
|
53
|
+
owner: `0x${string}`;
|
|
54
|
+
spender: `0x${string}`;
|
|
55
|
+
value: import("@onrail-xyz/common").AmountOr<K, bigint>;
|
|
56
|
+
deadline: "infinity" | Date;
|
|
57
|
+
signature: import("@onrail-xyz/utils").RoUint8Array;
|
|
58
|
+
}, undefined>];
|
|
59
|
+
export declare const permit: <const K extends KindWithAtomic | undefined = undefined>(contract: Address, ...kind: OptionalArg<K>) => ContractMethods<ReturnType<typeof permitSpec<K>>>;
|
|
60
|
+
declare const permitType: readonly [{
|
|
61
|
+
readonly name: "owner";
|
|
62
|
+
readonly type: "address";
|
|
63
|
+
}, {
|
|
64
|
+
readonly name: "spender";
|
|
65
|
+
readonly type: "address";
|
|
66
|
+
}, {
|
|
67
|
+
readonly name: "value";
|
|
68
|
+
readonly type: "uint256";
|
|
69
|
+
}, {
|
|
70
|
+
readonly name: "nonce";
|
|
71
|
+
readonly type: "uint256";
|
|
72
|
+
}, {
|
|
73
|
+
readonly name: "deadline";
|
|
74
|
+
readonly type: "uint256";
|
|
75
|
+
}];
|
|
76
|
+
export type Eip2612Message = Eip712Message<typeof permitType>;
|
|
77
|
+
export type Eip2612Data = Eip712Data<Eip2612Message>;
|
|
78
|
+
export declare const composePermitMsg: (owner: Address, spender: Address, value: AmountOrAtomic, domain: Eip712Domain, nonce: number | bigint, deadline?: Deadline) => {
|
|
79
|
+
readonly types: {
|
|
80
|
+
readonly EIP712Domain: ({
|
|
81
|
+
readonly name: "name";
|
|
82
|
+
readonly type: "string";
|
|
83
|
+
} | {
|
|
84
|
+
readonly name: "version";
|
|
85
|
+
readonly type: "string";
|
|
86
|
+
} | {
|
|
87
|
+
readonly name: "chainId";
|
|
88
|
+
readonly type: "uint256";
|
|
89
|
+
} | {
|
|
90
|
+
readonly name: "verifyingContract";
|
|
91
|
+
readonly type: "address";
|
|
92
|
+
} | {
|
|
93
|
+
readonly name: "salt";
|
|
94
|
+
readonly type: "bytes32";
|
|
95
|
+
})[];
|
|
96
|
+
readonly Permit: readonly [{
|
|
97
|
+
readonly name: "owner";
|
|
98
|
+
readonly type: "address";
|
|
99
|
+
}, {
|
|
100
|
+
readonly name: "spender";
|
|
101
|
+
readonly type: "address";
|
|
102
|
+
}, {
|
|
103
|
+
readonly name: "value";
|
|
104
|
+
readonly type: "uint256";
|
|
105
|
+
}, {
|
|
106
|
+
readonly name: "nonce";
|
|
107
|
+
readonly type: "uint256";
|
|
108
|
+
}, {
|
|
109
|
+
readonly name: "deadline";
|
|
110
|
+
readonly type: "uint256";
|
|
111
|
+
}];
|
|
112
|
+
};
|
|
113
|
+
readonly primaryType: "Permit";
|
|
114
|
+
readonly domain: Partial<Eip712Message<readonly [{
|
|
115
|
+
readonly name: "name";
|
|
116
|
+
readonly type: "string";
|
|
117
|
+
}, {
|
|
118
|
+
readonly name: "version";
|
|
119
|
+
readonly type: "string";
|
|
120
|
+
}, {
|
|
121
|
+
readonly name: "chainId";
|
|
122
|
+
readonly type: "uint256";
|
|
123
|
+
}, {
|
|
124
|
+
readonly name: "verifyingContract";
|
|
125
|
+
readonly type: "address";
|
|
126
|
+
}, {
|
|
127
|
+
readonly name: "salt";
|
|
128
|
+
readonly type: "bytes32";
|
|
129
|
+
}]>>;
|
|
130
|
+
readonly message: {
|
|
131
|
+
readonly owner: `0x${string}`;
|
|
132
|
+
readonly spender: `0x${string}`;
|
|
133
|
+
readonly value: bigint;
|
|
134
|
+
readonly nonce: bigint;
|
|
135
|
+
readonly deadline: bigint;
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
export {};
|
|
139
|
+
//# sourceMappingURL=permit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permit.d.ts","sourceRoot":"","sources":["../src/permit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,KAAK,cAAc,EAA8B,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,KAAK,QAAQ,EAC0C,MAAM,gBAAgB,CAAC;AACvF,OAAO,EAAE,KAAK,eAAe,EAAiC,MAAM,aAAa,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQ3E,QAAA,MAAM,UAAU,GAAI,KAAK,CAAC,CAAC,SAAS,cAAc,GAAG,SAAS,GAAG,SAAS,WAC/D,WAAW,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAUxB,CAAC;AAEF,eAAO,MAAM,MAAM,GAAI,KAAK,CAAC,CAAC,SAAS,cAAc,GAAG,SAAS,GAAG,SAAS,YACjE,OAAO,WACP,WAAW,CAAC,CAAC,CAAC,KACvB,eAAe,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CACF,CAAC;AAElD,QAAA,MAAM,UAAU;aACZ,IAAI,EAAE,OAAO;aAAK,IAAI,EAAE,SAAS;;aACjC,IAAI,EAAE,SAAS;aAAG,IAAI,EAAE,SAAS;;aACjC,IAAI,EAAE,OAAO;aAAK,IAAI,EAAE,SAAS;;aACjC,IAAI,EAAE,OAAO;aAAK,IAAI,EAAE,SAAS;;aACjC,IAAI,EAAE,UAAU;aAAE,IAAI,EAAE,SAAS;EAC3B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,UAAU,CAAC,CAAC;AAC9D,MAAM,MAAM,WAAW,GAAM,UAAU,CAAC,cAAc,CAAC,CAAC;AAGxD,eAAO,MAAM,gBAAgB,UACjB,OAAO,WACP,OAAO,SACP,cAAc,UACd,YAAY,SACZ,MAAM,GAAG,MAAM,aACf,QAAQ;;iBAUD,YAAY;;;;;;;;;;;;;;;;iBAA4B,MAAM;2BA3BvD,OAAO;2BAAW,SAAS;;2BAC3B,SAAS;2BAAS,SAAS;;2BAC3B,OAAO;2BAAW,SAAS;;2BAC3B,OAAO;2BAAW,SAAS;;2BAC3B,UAAU;2BAAQ,SAAS;;;0BAwBpB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;CAIxB,CAAC"}
|
package/dist/permit.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { toAtomicIfAmount, hashItem } from "@onrail-xyz/common";
|
|
2
|
+
import { uint256Item, deadlineItem, abiSignatureItem, abiParam, addressParam, amountParam } from "./layouting.js";
|
|
3
|
+
import { abiFunction, contractFromSpec } from "./client.js";
|
|
4
|
+
import { eip712DomainType } from "./eip712.js";
|
|
5
|
+
const owner = addressParam("owner");
|
|
6
|
+
const spender = addressParam("spender");
|
|
7
|
+
const deadline = abiParam("deadline", "uint256", deadlineItem);
|
|
8
|
+
const signature = abiParam("signature", "uint8,bytes32,bytes32", abiSignatureItem);
|
|
9
|
+
const permitSpec = (...kind) => {
|
|
10
|
+
const value = amountParam("value", "uint256", ...kind);
|
|
11
|
+
return [
|
|
12
|
+
abiFunction("DOMAIN_SEPARATOR", [], () => ({}), hashItem),
|
|
13
|
+
abiFunction("nonces", [owner], owner => ({ owner }), uint256Item),
|
|
14
|
+
abiFunction("permit", [owner, spender, value, deadline, signature], (owner, spender, value, deadline, signature) => ({ owner, spender, value, deadline, signature })),
|
|
15
|
+
];
|
|
16
|
+
};
|
|
17
|
+
export const permit = (contract, ...kind) => contractFromSpec(contract, permitSpec(...kind));
|
|
18
|
+
const permitType = [
|
|
19
|
+
{ name: "owner", type: "address" },
|
|
20
|
+
{ name: "spender", type: "address" },
|
|
21
|
+
{ name: "value", type: "uint256" },
|
|
22
|
+
{ name: "nonce", type: "uint256" },
|
|
23
|
+
{ name: "deadline", type: "uint256" },
|
|
24
|
+
];
|
|
25
|
+
//compose an EIP-2612 permit message; `toViemTypedData` bridges it to viem's signing calls
|
|
26
|
+
export const composePermitMsg = (owner, spender, value, domain, nonce, deadline = "infinity") => {
|
|
27
|
+
const atomic = toAtomicIfAmount(value);
|
|
28
|
+
if (atomic < 0n)
|
|
29
|
+
throw new Error("Value must not be negative");
|
|
30
|
+
const unixDeadline = deadlineItem.custom.from(deadline);
|
|
31
|
+
const bigNonce = typeof nonce === "number" ? BigInt(nonce) : nonce;
|
|
32
|
+
return {
|
|
33
|
+
types: { EIP712Domain: eip712DomainType(domain), Permit: permitType },
|
|
34
|
+
primaryType: "Permit",
|
|
35
|
+
domain,
|
|
36
|
+
message: { owner, spender, value: atomic, nonce: bigNonce, deadline: unixDeadline },
|
|
37
|
+
};
|
|
38
|
+
};
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Abi, AbiStateMutability, Address, PublicClient, ParseAbi, ContractFunctionName, ContractFunctionArgs, ContractFunctionReturnType } from "viem";
|
|
2
|
+
import type { RoUint8Array, RoArray, RoPair } from "@onrail-xyz/utils";
|
|
3
|
+
import { type MaybeArray } from "@onrail-xyz/utils";
|
|
4
|
+
import type { Layout, DeriveType } from "@onrail-xyz/binary-layout";
|
|
5
|
+
import type { SigNameOf } from "./layouting.js";
|
|
6
|
+
type ViemResolve<A extends Abi, N extends ContractFunctionName<A>, T extends "params" | "return"> = T extends "params" ? ContractFunctionArgs<A, AbiStateMutability, N> : ContractFunctionReturnType<A, AbiStateMutability, N>;
|
|
7
|
+
type AbiResolve<S extends string, T extends "params" | "return"> = string extends S ? T extends "params" ? RoArray : unknown : ParseAbi<[`function ${S}`]> extends infer A extends Abi ? SigNameOf<S> extends infer N extends ContractFunctionName<A> ? ViemResolve<A, N, T> : never : never;
|
|
8
|
+
type AbiParameters<S extends string> = AbiResolve<S, "params">;
|
|
9
|
+
export type QueryAbiReturn<S extends string> = AbiResolve<S, "return">;
|
|
10
|
+
export type QueryAbiPair<S extends string = string> = RoPair<S, AbiParameters<S>>;
|
|
11
|
+
export type QueryLayoutTriple<CL extends Layout = Layout, RL extends Layout = Layout> = readonly [CL, DeriveType<CL>, RL];
|
|
12
|
+
export type QueryCallData = RoUint8Array | QueryLayoutTriple | QueryAbiPair;
|
|
13
|
+
export type QueryCall<D extends QueryCallData = QueryCallData> = {
|
|
14
|
+
readonly to: Address;
|
|
15
|
+
readonly data: D;
|
|
16
|
+
readonly allowFailure?: boolean;
|
|
17
|
+
};
|
|
18
|
+
type _CheckCall<C> = C extends {
|
|
19
|
+
data: infer D;
|
|
20
|
+
} ? D extends RoUint8Array ? RoUint8Array : D extends QueryLayoutTriple<infer CL extends Layout, infer RL extends Layout> ? QueryLayoutTriple<CL, RL> : D extends QueryAbiPair<infer S extends string> ? QueryAbiPair<S> : never : never;
|
|
21
|
+
type CheckCall<C> = Omit<C, "data"> & {
|
|
22
|
+
data: _CheckCall<C>;
|
|
23
|
+
};
|
|
24
|
+
type CheckCalls<A> = A extends RoArray<QueryCall> ? {
|
|
25
|
+
[K in keyof A]: CheckCall<A[K]>;
|
|
26
|
+
} : CheckCall<A>;
|
|
27
|
+
type ResultOf<D> = D extends RoUint8Array ? RoUint8Array : D extends QueryLayoutTriple<Layout, infer RL extends Layout> ? DeriveType<RL> : D extends QueryAbiPair<infer S extends string> ? QueryAbiReturn<S> : never;
|
|
28
|
+
type WrappedResult<C extends QueryCall> = {
|
|
29
|
+
success: true;
|
|
30
|
+
data: ResultOf<C["data"]>;
|
|
31
|
+
} | {
|
|
32
|
+
success: false;
|
|
33
|
+
data: RoUint8Array;
|
|
34
|
+
};
|
|
35
|
+
type ByAllowFailure<F, C extends QueryCall> = F extends true ? WrappedResult<C> : ResultOf<C["data"]>;
|
|
36
|
+
type CallResult<C> = C extends QueryCall ? ByAllowFailure<C["allowFailure"], C> : never;
|
|
37
|
+
export type QueryResult<A extends MaybeArray<QueryCall>> = A extends RoArray<QueryCall> ? {
|
|
38
|
+
[K in keyof A]: CallResult<A[K]>;
|
|
39
|
+
} : CallResult<A>;
|
|
40
|
+
export type QueryResultWithMeta<A extends MaybeArray<QueryCall>> = [
|
|
41
|
+
QueryResult<A>,
|
|
42
|
+
bigint,
|
|
43
|
+
RoUint8Array,
|
|
44
|
+
Date
|
|
45
|
+
];
|
|
46
|
+
export type BlockSpec<S extends "hash" | "ref" = "hash" | "ref"> = S extends "hash" ? RoUint8Array : bigint | "latest" | "finalized";
|
|
47
|
+
export type Query = ReturnType<typeof createQuery>;
|
|
48
|
+
export declare const createQuery: (viemClient: PublicClient) => {
|
|
49
|
+
<const A extends MaybeArray<QueryCall>>(callS: A & CheckCalls<A>, block: BlockSpec<"hash">): Promise<QueryResult<A>>;
|
|
50
|
+
<const A extends MaybeArray<QueryCall>>(callS: A & CheckCalls<A>, block?: BlockSpec<"ref">): Promise<QueryResultWithMeta<A>>;
|
|
51
|
+
<const A extends MaybeArray<QueryCall>>(callS: A & CheckCalls<A>, block: BlockSpec): Promise<QueryResult<A> | QueryResultWithMeta<A>>;
|
|
52
|
+
};
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
package/dist/query.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { multicall3Abi, encodeFunctionData, decodeFunctionResult, parseAbi } from "viem";
|
|
2
|
+
import { isArray, isUint8Array, throwOnNullish, hex, unixTime } from "@onrail-xyz/utils";
|
|
3
|
+
import { serialize, deserialize } from "@onrail-xyz/binary-layout";
|
|
4
|
+
import { sigNameOf } from "./layouting.js";
|
|
5
|
+
// ---- Runtime encoding/decoding ----
|
|
6
|
+
const viemAbi = (sig) => ({
|
|
7
|
+
abi: parseAbi([`function ${sig}`]),
|
|
8
|
+
functionName: sigNameOf(sig),
|
|
9
|
+
});
|
|
10
|
+
const encodeCallData = (data) => !Array.isArray(data)
|
|
11
|
+
? hex.encode(data, true)
|
|
12
|
+
: typeof data[0] === "string"
|
|
13
|
+
? encodeFunctionData({ ...viemAbi(data[0]), args: data[1] })
|
|
14
|
+
: hex.encode(serialize(data[0], data[1]), true);
|
|
15
|
+
const decodeResult = (data, returnData) => !Array.isArray(data)
|
|
16
|
+
? hex.decode(returnData)
|
|
17
|
+
: typeof data[0] === "string"
|
|
18
|
+
? decodeFunctionResult({ ...viemAbi(data[0]), data: returnData })
|
|
19
|
+
: deserialize(data[2], hex.decode(returnData));
|
|
20
|
+
const processResult = (call, raw) => {
|
|
21
|
+
const { success, returnData } = raw;
|
|
22
|
+
const data = success ? decodeResult(call.data, returnData) : hex.decode(returnData);
|
|
23
|
+
if (!raw.success && !call.allowFailure)
|
|
24
|
+
throw new Error(`Call to ${call.to} reverted: ${hex.encode(data, true)}`);
|
|
25
|
+
return call.allowFailure ? { success, data } : data;
|
|
26
|
+
};
|
|
27
|
+
// ---- createQuery ----
|
|
28
|
+
//Multicall3's deterministic deployment puts it at the same address on nearly every chain, but a
|
|
29
|
+
// few (zkSync and friends) deviate, hence the chain config takes precedence when it has an entry
|
|
30
|
+
const CANONICAL_MULTICALL3 = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
31
|
+
const multicall3Of = (viemClient) => viemClient.chain?.contracts?.multicall3?.address ?? CANONICAL_MULTICALL3;
|
|
32
|
+
const encodeAggregate3 = (calls) => encodeFunctionData({
|
|
33
|
+
abi: multicall3Abi,
|
|
34
|
+
functionName: "aggregate3",
|
|
35
|
+
args: [calls.map(c => ({
|
|
36
|
+
target: c.to,
|
|
37
|
+
allowFailure: true,
|
|
38
|
+
callData: encodeCallData(c.data),
|
|
39
|
+
}))],
|
|
40
|
+
});
|
|
41
|
+
async function aggregate3(viemClient, callData, blockHash) {
|
|
42
|
+
const returnData = await viemClient.request({
|
|
43
|
+
method: "eth_call",
|
|
44
|
+
params: [
|
|
45
|
+
{ to: multicall3Of(viemClient), data: callData },
|
|
46
|
+
{ blockHash, requireCanonical: true },
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
return decodeFunctionResult({
|
|
50
|
+
abi: multicall3Abi,
|
|
51
|
+
functionName: "aggregate3",
|
|
52
|
+
data: returnData,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const MAX_RETRIES = 3;
|
|
56
|
+
//resolves a block tag or number to a canonical block hash, retrying whenever the batch does not
|
|
57
|
+
// come back, which is what a reorged-out block looks like from here
|
|
58
|
+
async function resolveBlock(viemClient, callData, block) {
|
|
59
|
+
const blockIdentifier = typeof block === "bigint" ? { blockNumber: block } : { blockTag: block };
|
|
60
|
+
let lastError;
|
|
61
|
+
for (let attempt = 0; attempt < MAX_RETRIES; ++attempt) {
|
|
62
|
+
const blockInfo = await viemClient.getBlock(blockIdentifier);
|
|
63
|
+
const blockHash = throwOnNullish(blockInfo.hash, "Block hash is null (pending block)");
|
|
64
|
+
const results = await aggregate3(viemClient, callData, blockHash)
|
|
65
|
+
.catch(error => { lastError = error; return undefined; });
|
|
66
|
+
if (results === undefined)
|
|
67
|
+
continue;
|
|
68
|
+
return {
|
|
69
|
+
results,
|
|
70
|
+
blockInfo: {
|
|
71
|
+
number: blockInfo.number,
|
|
72
|
+
hash: hex.decode(blockHash),
|
|
73
|
+
timestamp: unixTime.toDate(blockInfo.timestamp),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`Querying canonical block failed ${MAX_RETRIES} times in a row`, { cause: lastError });
|
|
78
|
+
}
|
|
79
|
+
const _query = async (viemClient, callS, block) => {
|
|
80
|
+
const multi = isArray(callS);
|
|
81
|
+
const calls = multi ? callS : [callS];
|
|
82
|
+
//encoded before any block is resolved: a malformed argument is the caller's error, which the
|
|
83
|
+
// reorg retry must neither repeat nor report as a failed block
|
|
84
|
+
const callData = encodeAggregate3(calls);
|
|
85
|
+
return (isUint8Array(block)
|
|
86
|
+
? aggregate3(viemClient, callData, hex.encode(block, true))
|
|
87
|
+
: resolveBlock(viemClient, callData, block)).then(raw => {
|
|
88
|
+
const [results, blockInfo] = "results" in raw
|
|
89
|
+
? [raw.results, raw.blockInfo]
|
|
90
|
+
: [raw, undefined];
|
|
91
|
+
const result = multi
|
|
92
|
+
? calls.map((c, i) => processResult(c, results[i]))
|
|
93
|
+
: processResult(calls[0], results[0]);
|
|
94
|
+
return blockInfo
|
|
95
|
+
? [result, blockInfo.number, blockInfo.hash, blockInfo.timestamp]
|
|
96
|
+
: result;
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
export const createQuery = (viemClient) => {
|
|
100
|
+
//impl
|
|
101
|
+
function query(callS, block = "latest") {
|
|
102
|
+
return _query(viemClient, callS, block);
|
|
103
|
+
}
|
|
104
|
+
return query;
|
|
105
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "@onrail-xyz/evm",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "nonergodic",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"description": "EVM utilities built on viem: declarative contract specs, ERC20, EIP-2612 permit and EIP-3009 authorization interactions, Multicall3 batched reads, and word-aligned binary layouts",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"ethereum",
|
|
10
|
+
"evm",
|
|
11
|
+
"viem",
|
|
12
|
+
"erc20",
|
|
13
|
+
"permit",
|
|
14
|
+
"eip-2612",
|
|
15
|
+
"eip-3009",
|
|
16
|
+
"eip-712",
|
|
17
|
+
"multicall",
|
|
18
|
+
"abi",
|
|
19
|
+
"layout"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/Onrail/ts-sdk.git",
|
|
24
|
+
"directory": "packages/evm"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/Onrail/ts-sdk/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/Onrail/ts-sdk/tree/main/packages/evm#readme",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"type": "module",
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js",
|
|
39
|
+
"default": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"src",
|
|
44
|
+
"README.md"
|
|
45
|
+
],
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@onrail-xyz/utils": "^1.0.0",
|
|
48
|
+
"@onrail-xyz/binary-layout": "^1.0.0",
|
|
49
|
+
"@onrail-xyz/amount": "^1.0.0",
|
|
50
|
+
"@onrail-xyz/common": "^1.0.0",
|
|
51
|
+
"@noble/hashes": "^1.8.0 || ^2.0.0",
|
|
52
|
+
"viem": "^2"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@noble/hashes": "^2.2.0",
|
|
56
|
+
"viem": "^2.55.11",
|
|
57
|
+
"@onrail-xyz/utils": "1.0.0",
|
|
58
|
+
"@onrail-xyz/binary-layout": "1.0.0",
|
|
59
|
+
"@onrail-xyz/common": "1.0.0",
|
|
60
|
+
"@onrail-xyz/amount": "1.0.0"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=25.0.0"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "pnpm clean && tsc",
|
|
67
|
+
"test": "tsc -p tsconfig.test.json && tsx --test tests/**/*.test.ts",
|
|
68
|
+
"clean": "rm -rf ./dist"
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { Address, AccessList, Hex } from "viem";
|
|
2
|
+
import type { RoUint8Array, RoArray, RoTuple, Function, OptionalArg } from "@onrail-xyz/utils";
|
|
3
|
+
import { hex, argOf } from "@onrail-xyz/utils";
|
|
4
|
+
import type { Layout, Struct } from "@onrail-xyz/binary-layout";
|
|
5
|
+
import { serialize, isItem, isOmitted, calcStaticSize } from "@onrail-xyz/binary-layout";
|
|
6
|
+
import type { KindWithAtomic } from "@onrail-xyz/amount";
|
|
7
|
+
import { type AmountOrAtomic, toAtomicIfAmount } from "@onrail-xyz/common";
|
|
8
|
+
import type { AbiParam, AbiParamValue, DynamicAbiParam, PaddedSlotLayout } from "./layouting.js";
|
|
9
|
+
import { wordSize, selectorLayout,
|
|
10
|
+
paddedSlotLayout, abiEncodedBytesItem, signatureOf } from "./layouting.js";
|
|
11
|
+
import type { QueryLayoutTriple } from "./query.js";
|
|
12
|
+
|
|
13
|
+
//the marker `trailingBytesParam` sets, read only here - nothing outside the spec builder asks
|
|
14
|
+
// whether a param is the dynamic one
|
|
15
|
+
const isDynamicParam = (param: AbiParam): param is DynamicAbiParam<string, Layout | undefined> =>
|
|
16
|
+
"dynamic" in param;
|
|
17
|
+
|
|
18
|
+
//the default spans the whole domain, so the bare `ContractTx` takes an amount of any kind
|
|
19
|
+
export type ContractTx<K extends KindWithAtomic | undefined = KindWithAtomic | undefined> = {
|
|
20
|
+
to: Address;
|
|
21
|
+
from?: Address;
|
|
22
|
+
value?: AmountOrAtomic<K>;
|
|
23
|
+
data: RoUint8Array;
|
|
24
|
+
accessList?: AccessList;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ViemTx = {
|
|
28
|
+
readonly to: Address;
|
|
29
|
+
readonly data: Hex;
|
|
30
|
+
readonly account?: Address;
|
|
31
|
+
readonly value?: bigint;
|
|
32
|
+
readonly accessList?: AccessList;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
//viem spells call data as hex and values as bigint where this package speaks bytes and amounts,
|
|
36
|
+
// and calls the sender `account` because it also accepts a whole Account: every field is
|
|
37
|
+
// converted so that consumers hand the result straight to sendTransaction/estimateGas
|
|
38
|
+
export const toViemTx = (tx: ContractTx): ViemTx => ({
|
|
39
|
+
to: tx.to,
|
|
40
|
+
data: hex.encode(tx.data, true),
|
|
41
|
+
...(tx.from !== undefined && { account: tx.from }),
|
|
42
|
+
...(tx.value !== undefined && { value: toAtomicIfAmount(tx.value) }),
|
|
43
|
+
...(tx.accessList !== undefined && { accessList: tx.accessList }),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ---- Contract spec builder ----
|
|
47
|
+
// A spec is a tuple of `abiFunction` rows, [name, params, composer, outputLayout | undefined]. The
|
|
48
|
+
// signature and the input layout - each param left-padded onto its ABI slot - derive from the
|
|
49
|
+
// params; the composer turns positional args into the params object, which methods also accept
|
|
50
|
+
// directly, and exists for the parameter names, which nothing else can carry.
|
|
51
|
+
// Solidity overloads share an ABI name, so a row whose method key must differ from it names
|
|
52
|
+
// both, key first: `["safeTransferFromWithData", "safeTransferFrom"]`.
|
|
53
|
+
// Read calls (outputLayout defined) return a layout triple for the query system.
|
|
54
|
+
// Write calls (outputLayout undefined) return pre-serialized call data; who sends it is the
|
|
55
|
+
// transaction's business, not the spec's.
|
|
56
|
+
// Every param is static but for a trailing `trailingBytesParam`, whose offset the parameter
|
|
57
|
+
// count fixes; two dynamic params would need offsets computed from the tails ahead of them,
|
|
58
|
+
// which is an encoding pass rather than a layout.
|
|
59
|
+
|
|
60
|
+
export type ParamsStruct<P extends RoTuple<AbiParam>> =
|
|
61
|
+
{ readonly [E in P[number] as E["name"]]: E["item"] };
|
|
62
|
+
|
|
63
|
+
//each param's field of the call's layout: static ones left-padded onto their slot, a trailing
|
|
64
|
+
// dynamic one already ABI-shaped (offset, length, payload, padding)
|
|
65
|
+
export type ParamsLayout<P extends RoTuple<AbiParam>> = {
|
|
66
|
+
readonly [E in P[number] as E["name"]]:
|
|
67
|
+
E extends { readonly dynamic: object } ? E["item"] : PaddedSlotLayout<E["item"]>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//the params' values, positionally and by name
|
|
71
|
+
type ParamValues<P extends RoTuple<AbiParam>> =
|
|
72
|
+
{ [K in keyof P]: P[K] extends AbiParam ? AbiParamValue<P[K]> : never };
|
|
73
|
+
export type ParamsRecord<P extends RoTuple<AbiParam>> =
|
|
74
|
+
{ readonly [E in P[number] as E["name"]]: AbiParamValue<E> };
|
|
75
|
+
|
|
76
|
+
export type FuncName = string | readonly [key: string, abiName: string];
|
|
77
|
+
export type FuncKey<N extends FuncName> =
|
|
78
|
+
N extends readonly [infer K extends string, string] ? K : N;
|
|
79
|
+
|
|
80
|
+
export type FuncSpec<
|
|
81
|
+
N extends FuncName = FuncName,
|
|
82
|
+
P extends RoTuple<AbiParam> = RoTuple<AbiParam>,
|
|
83
|
+
C extends Function<any> = Function<any>,
|
|
84
|
+
R extends Layout | undefined = Layout | undefined,
|
|
85
|
+
> = readonly [N, P, C, R];
|
|
86
|
+
type ContractSpec = RoTuple<FuncSpec>;
|
|
87
|
+
|
|
88
|
+
//the composer is contextually typed from the params, so it is written with names only
|
|
89
|
+
export const abiFunction = <
|
|
90
|
+
const N extends FuncName,
|
|
91
|
+
const P extends RoTuple<AbiParam>,
|
|
92
|
+
C extends (...args: ParamValues<P>) => ParamsRecord<P>,
|
|
93
|
+
const R extends Layout | undefined = undefined,
|
|
94
|
+
>(name: N, params: P, composer: C, ...output: OptionalArg<R>): FuncSpec<N, P, C, R> =>
|
|
95
|
+
[name, params, composer, argOf(output)];
|
|
96
|
+
|
|
97
|
+
type ReadResult<P extends RoTuple<AbiParam>, OL extends Layout> = {
|
|
98
|
+
readonly to: Address;
|
|
99
|
+
readonly data: QueryLayoutTriple<ParamsLayout<P>, OL>;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
type WriteResult = Readonly<{
|
|
103
|
+
to: Address;
|
|
104
|
+
data: Uint8Array;
|
|
105
|
+
}>;
|
|
106
|
+
|
|
107
|
+
//object form first, positional form last: `infer` (and `Parameters`/`ReturnType`) bind to an
|
|
108
|
+
// overloaded function's last signature, and the positional signature is the one reflection
|
|
109
|
+
// needs — the object form's parameter is just the params' derived type, recoverable from them
|
|
110
|
+
// without going through the function type
|
|
111
|
+
type ContractMethodOf<P extends RoTuple<AbiParam>, OL, C extends Function<any>> =
|
|
112
|
+
OL extends Layout
|
|
113
|
+
? ((params: ParamsRecord<P>) => ReadResult<P, OL>) &
|
|
114
|
+
((...args: Parameters<C>) => ReadResult<P, OL>)
|
|
115
|
+
: ((params: ParamsRecord<P>) => WriteResult) &
|
|
116
|
+
((...args: Parameters<C>) => WriteResult);
|
|
117
|
+
|
|
118
|
+
export type ContractMethods<S extends RoTuple<any>> = {
|
|
119
|
+
readonly [E in S[number] as E extends FuncSpec<infer N> ? FuncKey<N> : never]:
|
|
120
|
+
E extends FuncSpec<FuncName, infer P, infer C, infer OL>
|
|
121
|
+
? ContractMethodOf<P, OL, C>
|
|
122
|
+
: never;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
function resolveArgs(
|
|
126
|
+
layout: Struct,
|
|
127
|
+
composer: Function<any>,
|
|
128
|
+
args: RoArray,
|
|
129
|
+
): unknown {
|
|
130
|
+
//For 0 or 2+ args, it's unambiguously the composer (positional) form.
|
|
131
|
+
//For a single object arg, it's ambiguous only when the layout has exactly one non-omitted
|
|
132
|
+
// field — the arg could be the object form { name: value } or a struct passed positionally.
|
|
133
|
+
// We assume object form if the object has exactly one key matching that field's name.
|
|
134
|
+
// For 2+ non-omitted fields, a single arg must be object form (positional would have multiple).
|
|
135
|
+
if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) {
|
|
136
|
+
let nonOmittedCount = 0;
|
|
137
|
+
let firstName: string | undefined;
|
|
138
|
+
for (const [name, field] of Object.entries(layout)) {
|
|
139
|
+
if (isItem(field) && isOmitted(field))
|
|
140
|
+
continue;
|
|
141
|
+
if (++nonOmittedCount > 1)
|
|
142
|
+
return args[0];
|
|
143
|
+
firstName = name;
|
|
144
|
+
}
|
|
145
|
+
//single non-omitted field: assume object form if the object has exactly one matching key
|
|
146
|
+
const keys = Object.keys(args[0]);
|
|
147
|
+
if (keys.length === 1 && keys[0] === firstName)
|
|
148
|
+
return args[0];
|
|
149
|
+
}
|
|
150
|
+
return composer(...args);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const contractFromSpec = <const S extends ContractSpec>(
|
|
154
|
+
contract: Address,
|
|
155
|
+
spec: S,
|
|
156
|
+
): ContractMethods<S> => {
|
|
157
|
+
const result: Record<string, Function<any>> = {};
|
|
158
|
+
for (const [name, params, composer, outputLayout] of spec) {
|
|
159
|
+
const [key, abiName] = typeof name === "string" ? [name, name] : name;
|
|
160
|
+
const sig = signatureOf(abiName, params);
|
|
161
|
+
const dynamicIndex = params.findIndex(isDynamicParam);
|
|
162
|
+
//a second dynamic param cannot be last either, so the one check covers both
|
|
163
|
+
if (dynamicIndex >= 0 && dynamicIndex !== params.length - 1)
|
|
164
|
+
throw new Error(`${sig}: a dynamic parameter must be the only one, and come last`);
|
|
165
|
+
|
|
166
|
+
//the dynamic payload follows the whole head, which is not one word per preceding param:
|
|
167
|
+
// a static tuple param spans as many slots as it has members
|
|
168
|
+
const staticFields = params.filter(param => !isDynamicParam(param))
|
|
169
|
+
.map(param => paddedSlotLayout(param.item));
|
|
170
|
+
const headSize = staticFields.reduce((sum, field) => sum + calcStaticSize(field)!, wordSize);
|
|
171
|
+
|
|
172
|
+
let staticIndex = 0;
|
|
173
|
+
const inputLayout = Object.fromEntries(params.map(param => [
|
|
174
|
+
param.name,
|
|
175
|
+
isDynamicParam(param)
|
|
176
|
+
? abiEncodedBytesItem({ layout: param.dynamic.layout, offset: headSize })
|
|
177
|
+
: staticFields[staticIndex++]!,
|
|
178
|
+
]));
|
|
179
|
+
const layout = selectorLayout(sig)(inputLayout);
|
|
180
|
+
|
|
181
|
+
const method = (...args: any[]) => ({
|
|
182
|
+
to: contract,
|
|
183
|
+
data: outputLayout !== undefined
|
|
184
|
+
? [layout, resolveArgs(inputLayout, composer, args), outputLayout]
|
|
185
|
+
: serialize(layout, resolveArgs(inputLayout, composer, args) as any),
|
|
186
|
+
});
|
|
187
|
+
//shape-dispatching methods would report length 0; stamp the positional arity so it stays
|
|
188
|
+
// observable, e.g. for arity-driven call batching
|
|
189
|
+
Object.defineProperty(method, "length", { value: composer.length });
|
|
190
|
+
if (Object.hasOwn(result, key))
|
|
191
|
+
throw new Error(`duplicate method key: ${key}`);
|
|
192
|
+
|
|
193
|
+
result[key] = method;
|
|
194
|
+
}
|
|
195
|
+
return result as any;
|
|
196
|
+
};
|