@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.
@@ -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
+ };
@@ -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
+ };