@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/eip712.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { Address, Hex } from "viem";
|
|
2
|
+
import type { RoUint8Array, RoArray, StrRecord, Simplify, DeepRo } from "@onrail-xyz/utils";
|
|
3
|
+
import { bytes, hex, utf8 } from "@onrail-xyz/utils";
|
|
4
|
+
import type { Layout, Item, DeriveType } from "@onrail-xyz/binary-layout";
|
|
5
|
+
import { serialize } from "@onrail-xyz/binary-layout";
|
|
6
|
+
import { hashItem } from "@onrail-xyz/common";
|
|
7
|
+
import { keccak256 } from "./hashing.js";
|
|
8
|
+
import type { BytesNItem } from "./layouting.js";
|
|
9
|
+
import { wordSize, abiAddressItem, abiBoolItem, uint256Item, bytesNItem } from "./layouting.js";
|
|
10
|
+
|
|
11
|
+
// ---- EIP-712 Types ----
|
|
12
|
+
|
|
13
|
+
export type Eip712Field = { readonly name: string; readonly type: string };
|
|
14
|
+
|
|
15
|
+
//the value a field of the given Solidity type takes in a composed message; struct-typed fields
|
|
16
|
+
// would need the whole `types` record to resolve and are not covered. A type known only as
|
|
17
|
+
// `string` takes any of them, so the message of an unknown field list is a record of values
|
|
18
|
+
type Eip712Value<T extends string> =
|
|
19
|
+
string extends T
|
|
20
|
+
? Address | boolean | bigint | RoUint8Array | string
|
|
21
|
+
: T extends "address"
|
|
22
|
+
? Address
|
|
23
|
+
: T extends "bool"
|
|
24
|
+
? boolean
|
|
25
|
+
: T extends `uint${number}` | `int${number}`
|
|
26
|
+
? bigint
|
|
27
|
+
: T extends `bytes${number}` | "bytes"
|
|
28
|
+
? RoUint8Array
|
|
29
|
+
: T extends "string"
|
|
30
|
+
? string
|
|
31
|
+
: never;
|
|
32
|
+
|
|
33
|
+
//the message a struct's field list describes, so a struct is spelled once; the bare spelling
|
|
34
|
+
// is the message of some field list, which every derived message is assignable to
|
|
35
|
+
export type Eip712Message<F extends RoArray<Eip712Field> = RoArray<Eip712Field>> =
|
|
36
|
+
{ readonly [E in F[number] as E["name"]]: Eip712Value<E["type"]> };
|
|
37
|
+
|
|
38
|
+
const domainSeparatorFields = [
|
|
39
|
+
{ name: "name", type: "string" },
|
|
40
|
+
{ name: "version", type: "string" },
|
|
41
|
+
{ name: "chainId", type: "uint256" },
|
|
42
|
+
{ name: "verifyingContract", type: "address" },
|
|
43
|
+
{ name: "salt", type: "bytes32" },
|
|
44
|
+
] as const;
|
|
45
|
+
type DomainSeparatorField = typeof domainSeparatorFields[number];
|
|
46
|
+
type DomainSeparatorFieldName = DomainSeparatorField["name"];
|
|
47
|
+
|
|
48
|
+
//every field is optional: a domain uses whichever subset it declares
|
|
49
|
+
export type Eip712Domain = Partial<Eip712Message<typeof domainSeparatorFields>>;
|
|
50
|
+
|
|
51
|
+
export type Eip712Data<Message = StrRecord> = {
|
|
52
|
+
readonly types: StrRecord<RoArray<Eip712Field>>;
|
|
53
|
+
readonly primaryType: string;
|
|
54
|
+
readonly domain: Eip712Domain;
|
|
55
|
+
readonly message: DeepRo<Message>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type ViemTypedData<T> =
|
|
59
|
+
T extends RoUint8Array
|
|
60
|
+
? Hex
|
|
61
|
+
: T extends object
|
|
62
|
+
? { readonly [K in keyof T]: ViemTypedData<T[K]> }
|
|
63
|
+
: T;
|
|
64
|
+
|
|
65
|
+
//viem spells bytes values as hex where this package speaks bytes: every bytes leaf of domain and
|
|
66
|
+
// message is converted so that consumers hand the result straight to signTypedData/hashTypedData
|
|
67
|
+
export const toViemTypedData = <T>(value: T): ViemTypedData<T> => (
|
|
68
|
+
value instanceof Uint8Array
|
|
69
|
+
? hex.encode(value, true)
|
|
70
|
+
: Array.isArray(value)
|
|
71
|
+
? value.map(toViemTypedData)
|
|
72
|
+
: typeof value === "object" && value !== null
|
|
73
|
+
? Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toViemTypedData(v)]))
|
|
74
|
+
: value
|
|
75
|
+
) as ViemTypedData<T>;
|
|
76
|
+
|
|
77
|
+
// ---- encodeData ----
|
|
78
|
+
// A struct's encodeData puts each atomic field on a word, in field order, so a layout spelled
|
|
79
|
+
// from the field list serializes a message to exactly those bytes and reads one back - which
|
|
80
|
+
// is how a message is derived from data that already has a word-per-field layout. A dynamic
|
|
81
|
+
// (`bytes`, `string`), array or struct-typed field takes a word too, but holding a hash this
|
|
82
|
+
// layout does not compute, so such a field is rejected. Values ride at word width: a `uint8`
|
|
83
|
+
// field's range is its composer's to keep.
|
|
84
|
+
|
|
85
|
+
const int256Item = { binary: "int", size: wordSize } as const;
|
|
86
|
+
|
|
87
|
+
type WordItem<T extends string> =
|
|
88
|
+
T extends "bool"
|
|
89
|
+
? typeof abiBoolItem
|
|
90
|
+
: T extends "address"
|
|
91
|
+
? typeof abiAddressItem
|
|
92
|
+
: T extends `uint${number}`
|
|
93
|
+
? typeof uint256Item
|
|
94
|
+
: T extends `int${number}`
|
|
95
|
+
? typeof int256Item
|
|
96
|
+
: T extends `bytes${infer N extends number}`
|
|
97
|
+
? BytesNItem<N>
|
|
98
|
+
: never;
|
|
99
|
+
|
|
100
|
+
const wordItem = (type: string): Item => {
|
|
101
|
+
if (type === "bool")
|
|
102
|
+
return abiBoolItem;
|
|
103
|
+
if (type === "address")
|
|
104
|
+
return abiAddressItem;
|
|
105
|
+
if (/^uint\d+$/.test(type))
|
|
106
|
+
return uint256Item;
|
|
107
|
+
if (/^int\d+$/.test(type))
|
|
108
|
+
return int256Item;
|
|
109
|
+
|
|
110
|
+
const bytesN = /^bytes(\d+)$/.exec(type);
|
|
111
|
+
if (bytesN !== null)
|
|
112
|
+
return bytesNItem(Number(bytesN[1]));
|
|
113
|
+
|
|
114
|
+
throw new Error(`EIP-712 type '${type}' has no encodeData word - only atomic types are encoded`);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export type Eip712EncodeDataLayout<F extends RoArray<Eip712Field>> =
|
|
118
|
+
{ readonly [E in F[number] as E["name"]]: WordItem<E["type"]> };
|
|
119
|
+
|
|
120
|
+
export const eip712EncodeDataLayout =
|
|
121
|
+
<const F extends RoArray<Eip712Field>>(fields: F): Eip712EncodeDataLayout<F> =>
|
|
122
|
+
Object.fromEntries(fields.map(f => [f.name, wordItem(f.type)])) as Eip712EncodeDataLayout<F>;
|
|
123
|
+
|
|
124
|
+
// ---- Domain Separator ----
|
|
125
|
+
|
|
126
|
+
//the domain's type as viem derives it - the fields present, in the standard's order - so a message
|
|
127
|
+
// composed with it hashes identically on both sides
|
|
128
|
+
export const eip712DomainType = (domain: Eip712Domain) =>
|
|
129
|
+
domainSeparatorFields.filter(f => domain[f.name] !== undefined);
|
|
130
|
+
|
|
131
|
+
const associatedLayoutItem = {
|
|
132
|
+
name: hashItem,
|
|
133
|
+
version: hashItem,
|
|
134
|
+
chainId: { binary: "uint", size: wordSize },
|
|
135
|
+
verifyingContract: abiAddressItem,
|
|
136
|
+
salt: hashItem,
|
|
137
|
+
} as const satisfies Record<DomainSeparatorFieldName, Layout>;
|
|
138
|
+
|
|
139
|
+
const hashString = (s: string) => keccak256(utf8.encode(s));
|
|
140
|
+
|
|
141
|
+
const typeString = (fields: RoArray<DomainSeparatorFieldName>) =>
|
|
142
|
+
"EIP712Domain("
|
|
143
|
+
+ domainSeparatorFields //enforces correct order (iterating over fields would not)
|
|
144
|
+
.filter(dsf => fields.includes(dsf.name))
|
|
145
|
+
.map(dsf => `${dsf.type} ${dsf.name}`)
|
|
146
|
+
.join(",")
|
|
147
|
+
+ ")";
|
|
148
|
+
|
|
149
|
+
const typeHash = (fields: RoArray<DomainSeparatorFieldName>) => hashString(typeString(fields));
|
|
150
|
+
|
|
151
|
+
const typeHashItem = (fields: RoArray<DomainSeparatorFieldName>) =>
|
|
152
|
+
({ binary: "bytes", fixed: typeHash(fields) } as const);
|
|
153
|
+
|
|
154
|
+
//record key order is wire order, so keys are inserted in domainSeparatorFields order; the
|
|
155
|
+
// type is orderless either way (record types carry no key order)
|
|
156
|
+
type SeparatorLayout<F extends DomainSeparatorFieldName> = Simplify<
|
|
157
|
+
{ readonly typeHash: ReturnType<typeof typeHashItem> } &
|
|
158
|
+
{ readonly [K in F]: typeof associatedLayoutItem[K] }
|
|
159
|
+
>;
|
|
160
|
+
|
|
161
|
+
const separatorLayout =
|
|
162
|
+
<const F extends RoArray<DomainSeparatorFieldName>>(fields: F): SeparatorLayout<F[number]> => ({
|
|
163
|
+
typeHash: typeHashItem(fields),
|
|
164
|
+
...Object.fromEntries(
|
|
165
|
+
domainSeparatorFields //enforces correct order (iterating over fields would not)
|
|
166
|
+
.filter(dsf => fields.includes(dsf.name))
|
|
167
|
+
.map(dsf => [dsf.name, associatedLayoutItem[dsf.name]]),
|
|
168
|
+
),
|
|
169
|
+
} as any);
|
|
170
|
+
|
|
171
|
+
const matchesHash =
|
|
172
|
+
<const L extends Layout>(layout: L, candidate: DeriveType<L>, expected: RoUint8Array) =>
|
|
173
|
+
bytes.equals(keccak256(serialize(layout, candidate)), expected);
|
|
174
|
+
|
|
175
|
+
// ---- Domain Resolution ----
|
|
176
|
+
|
|
177
|
+
// Reconstructs the domain of tokens that only expose DOMAIN_SEPARATOR() by brute-forcing the
|
|
178
|
+
// version field against the on-chain hash. Covers the { name, chainId, verifyingContract } domain
|
|
179
|
+
// with an optional version, i.e. everything that follows EIP-2612's reference implementation.
|
|
180
|
+
|
|
181
|
+
const typicalDomainFields = ["name", "chainId", "verifyingContract"] as const;
|
|
182
|
+
const unversionedLayout = separatorLayout(typicalDomainFields);
|
|
183
|
+
const versionedLayout = separatorLayout([...typicalDomainFields, "version"]);
|
|
184
|
+
|
|
185
|
+
const guessDomainSeparatorVersion = (
|
|
186
|
+
known: DeriveType<typeof unversionedLayout>,
|
|
187
|
+
domainSeparator: RoUint8Array,
|
|
188
|
+
guesses: RoArray<string>,
|
|
189
|
+
) => {
|
|
190
|
+
for (const version of guesses)
|
|
191
|
+
if (matchesHash(versionedLayout, { ...known, version: hashString(version) }, domainSeparator))
|
|
192
|
+
return version;
|
|
193
|
+
|
|
194
|
+
throw new Error("Could not determine domain separator version");
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
//reconstructs the EIP-712 domain from on-chain data, guesses the version if needed
|
|
198
|
+
export const guessEip712Domain = (
|
|
199
|
+
name: string,
|
|
200
|
+
verifyingContract: Address,
|
|
201
|
+
chainId: bigint,
|
|
202
|
+
domainSeparator: RoUint8Array,
|
|
203
|
+
versionGuesses: RoArray<string> = ["1", "2", "0"],
|
|
204
|
+
) => {
|
|
205
|
+
const known = { name: hashString(name), chainId, verifyingContract } as const;
|
|
206
|
+
return {
|
|
207
|
+
name,
|
|
208
|
+
...(matchesHash(unversionedLayout, known, domainSeparator)
|
|
209
|
+
? {}
|
|
210
|
+
: { version: guessDomainSeparatorVersion(known, domainSeparator, versionGuesses) }
|
|
211
|
+
),
|
|
212
|
+
chainId,
|
|
213
|
+
verifyingContract,
|
|
214
|
+
} as const;
|
|
215
|
+
};
|
package/src/erc20.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Address } from "viem";
|
|
2
|
+
import type { OptionalArg } from "@onrail-xyz/utils";
|
|
3
|
+
import { utf8Conversion, withCustom } from "@onrail-xyz/binary-layout";
|
|
4
|
+
import type { KindWithAtomic } from "@onrail-xyz/amount";
|
|
5
|
+
import { paddedSlotLayout, evmAmountItem,
|
|
6
|
+
abiEncodedBytesItem, sigVariant, addressParam, amountParam } from "./layouting.js";
|
|
7
|
+
import type { ContractMethods } from "./client.js";
|
|
8
|
+
import { abiFunction, contractFromSpec } from "./client.js";
|
|
9
|
+
|
|
10
|
+
const abiStringItem = withCustom(abiEncodedBytesItem(), utf8Conversion);
|
|
11
|
+
const decimalsItem = paddedSlotLayout({ binary: "uint", size: 1 } as const);
|
|
12
|
+
|
|
13
|
+
const from = addressParam("from" );
|
|
14
|
+
const to = addressParam("to" );
|
|
15
|
+
const owner = addressParam("owner" );
|
|
16
|
+
const spender = addressParam("spender");
|
|
17
|
+
|
|
18
|
+
const valueParam = <const K extends KindWithAtomic | undefined = undefined>(
|
|
19
|
+
...kind: OptionalArg<K>
|
|
20
|
+
) =>
|
|
21
|
+
amountParam("value", "uint256", ...kind);
|
|
22
|
+
|
|
23
|
+
const erc20Spec = <const K extends KindWithAtomic | undefined = undefined>(
|
|
24
|
+
...kind: OptionalArg<K>
|
|
25
|
+
) => {
|
|
26
|
+
const value = valueParam(...kind);
|
|
27
|
+
const amountItem = evmAmountItem(...kind);
|
|
28
|
+
const abiF = abiFunction;
|
|
29
|
+
return [
|
|
30
|
+
abiF("name", [], () => ({}), abiStringItem),
|
|
31
|
+
abiF("symbol", [], () => ({}), abiStringItem),
|
|
32
|
+
abiF("decimals", [], () => ({}), decimalsItem),
|
|
33
|
+
abiF("totalSupply", [], () => ({}), amountItem),
|
|
34
|
+
abiF("balanceOf", [owner], owner => ({ owner }), amountItem),
|
|
35
|
+
abiF("allowance", [owner, spender], (owner, spender) => ({ owner, spender }), amountItem),
|
|
36
|
+
abiF("approve", [spender, value], (spender, value) => ({ spender, value }) ),
|
|
37
|
+
abiF("transfer", [to, value], (to, value) => ({ to, value }) ),
|
|
38
|
+
abiF("transferFrom", [from, to, value], (from, to, value) => ({ from, to, value }) ),
|
|
39
|
+
] as const;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const erc20 = <const K extends KindWithAtomic | undefined = undefined>(
|
|
43
|
+
contract: Address,
|
|
44
|
+
...kind: OptionalArg<K>
|
|
45
|
+
): ContractMethods<ReturnType<typeof erc20Spec<K>>> =>
|
|
46
|
+
contractFromSpec(contract, erc20Spec(...kind));
|
|
47
|
+
|
|
48
|
+
//`Transfer` and `Approval` as `buildParseEvent` variants; `allowanceAdjusters` emits the latter
|
|
49
|
+
export const erc20Events = <const K extends KindWithAtomic | undefined = undefined>(
|
|
50
|
+
...kind: OptionalArg<K>
|
|
51
|
+
) => {
|
|
52
|
+
const value = valueParam(...kind);
|
|
53
|
+
return [
|
|
54
|
+
sigVariant("Transfer", from, to, value),
|
|
55
|
+
sigVariant("Approval", owner, spender, value),
|
|
56
|
+
] as const;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const allowanceAdjustersSpec = <const K extends KindWithAtomic | undefined = undefined>(
|
|
60
|
+
...kind: OptionalArg<K>
|
|
61
|
+
) => {
|
|
62
|
+
const addedValue = amountParam("addedValue", "uint256", ...kind);
|
|
63
|
+
const subtractedValue = amountParam("subtractedValue", "uint256", ...kind);
|
|
64
|
+
return [
|
|
65
|
+
abiFunction("increaseAllowance", [spender, addedValue],
|
|
66
|
+
(spender, addedValue) => ({ spender, addedValue })),
|
|
67
|
+
abiFunction("decreaseAllowance", [spender, subtractedValue],
|
|
68
|
+
(spender, subtractedValue) => ({ spender, subtractedValue })),
|
|
69
|
+
] as const;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const allowanceAdjusters = <const K extends KindWithAtomic | undefined = undefined>(
|
|
73
|
+
contract: Address,
|
|
74
|
+
...kind: OptionalArg<K>
|
|
75
|
+
): ContractMethods<ReturnType<typeof allowanceAdjustersSpec<K>>> =>
|
|
76
|
+
contractFromSpec(contract, allowanceAdjustersSpec(...kind));
|
package/src/erc3009.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Address } from "viem";
|
|
2
|
+
import type { RoUint8Array, OptionalArg } from "@onrail-xyz/utils";
|
|
3
|
+
import type { KindWithAtomic } from "@onrail-xyz/amount";
|
|
4
|
+
import { type AmountOrAtomic, toAtomicIfAmount, hashItem } from "@onrail-xyz/common";
|
|
5
|
+
import { type Deadline, deadlineItem, evmTimestampItem, abiBoolItem,
|
|
6
|
+
abiSignatureItem, sigVariant, abiParam, amountParam, addressParam } from "./layouting.js";
|
|
7
|
+
import { type ContractMethods, abiFunction, contractFromSpec } from "./client.js";
|
|
8
|
+
import type { Eip712Domain, Eip712Data, Eip712Message } from "./eip712.js";
|
|
9
|
+
import { eip712DomainType } from "./eip712.js";
|
|
10
|
+
|
|
11
|
+
const from = addressParam("from" );
|
|
12
|
+
const to = addressParam("to" );
|
|
13
|
+
const authorizer = addressParam("authorizer");
|
|
14
|
+
//the standard's nonce is a caller-chosen bytes32, unrelated to EIP-2612's sequential uint256
|
|
15
|
+
const nonce = abiParam("nonce", "bytes32", hashItem );
|
|
16
|
+
const validAfter = abiParam("validAfter", "uint256", evmTimestampItem);
|
|
17
|
+
const validBefore = abiParam("validBefore", "uint256", deadlineItem );
|
|
18
|
+
const signature = abiParam("signature", "uint8,bytes32,bytes32", abiSignatureItem);
|
|
19
|
+
|
|
20
|
+
const erc3009Spec = <const K extends KindWithAtomic | undefined = undefined>(
|
|
21
|
+
...kind: OptionalArg<K>
|
|
22
|
+
) => {
|
|
23
|
+
const value = amountParam("value", "uint256", ...kind);
|
|
24
|
+
|
|
25
|
+
//one parameter list under two names: the receiving variant binds the submitter to be `to`
|
|
26
|
+
const withAuthorization = <N extends string>(name: N) =>
|
|
27
|
+
abiFunction(name, [from, to, value, validAfter, validBefore, nonce, signature],
|
|
28
|
+
(from, to, value, validAfter, validBefore, nonce, signature) =>
|
|
29
|
+
({ from, to, value, validAfter, validBefore, nonce, signature }));
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
abiFunction("authorizationState", [authorizer, nonce],
|
|
33
|
+
(authorizer, nonce) => ({ authorizer, nonce }), abiBoolItem),
|
|
34
|
+
withAuthorization("transferWithAuthorization"),
|
|
35
|
+
withAuthorization("receiveWithAuthorization"),
|
|
36
|
+
abiFunction("cancelAuthorization", [authorizer, nonce, signature],
|
|
37
|
+
(authorizer, nonce, signature) => ({ authorizer, nonce, signature })),
|
|
38
|
+
] as const;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const erc3009 = <const K extends KindWithAtomic | undefined = undefined>(
|
|
42
|
+
contract: Address,
|
|
43
|
+
...kind: OptionalArg<K>
|
|
44
|
+
): ContractMethods<ReturnType<typeof erc3009Spec<K>>> =>
|
|
45
|
+
contractFromSpec(contract, erc3009Spec(...kind));
|
|
46
|
+
|
|
47
|
+
//the standard's events as `buildParseEvent` variants; its reverts are left to implementations
|
|
48
|
+
export const erc3009Events = [
|
|
49
|
+
sigVariant("AuthorizationUsed", authorizer, nonce),
|
|
50
|
+
sigVariant("AuthorizationCanceled", authorizer, nonce),
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
// ---- EIP-3009 Authorization Messages ----
|
|
54
|
+
|
|
55
|
+
const authorizationType = [
|
|
56
|
+
{ name: "from", type: "address" },
|
|
57
|
+
{ name: "to", type: "address" },
|
|
58
|
+
{ name: "value", type: "uint256" },
|
|
59
|
+
{ name: "validAfter", type: "uint256" },
|
|
60
|
+
{ name: "validBefore", type: "uint256" },
|
|
61
|
+
{ name: "nonce", type: "bytes32" },
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
const cancelAuthorizationType = [
|
|
65
|
+
{ name: "authorizer", type: "address" },
|
|
66
|
+
{ name: "nonce", type: "bytes32" },
|
|
67
|
+
] as const;
|
|
68
|
+
|
|
69
|
+
export type Eip3009AuthorizationMessage = Eip712Message<typeof authorizationType>;
|
|
70
|
+
export type Eip3009CancelMessage = Eip712Message<typeof cancelAuthorizationType>;
|
|
71
|
+
|
|
72
|
+
export const randomAuthorizationNonce = (): Uint8Array =>
|
|
73
|
+
crypto.getRandomValues(new Uint8Array(32));
|
|
74
|
+
|
|
75
|
+
//one struct under two names: the receiving variant additionally binds the submitter to be `to`
|
|
76
|
+
const composeAuthorization = (
|
|
77
|
+
primaryType: "TransferWithAuthorization" | "ReceiveWithAuthorization",
|
|
78
|
+
) => (
|
|
79
|
+
from: Address,
|
|
80
|
+
to: Address,
|
|
81
|
+
value: AmountOrAtomic,
|
|
82
|
+
domain: Eip712Domain,
|
|
83
|
+
nonce: RoUint8Array,
|
|
84
|
+
validBefore: Deadline = "infinity",
|
|
85
|
+
validAfter: Date = new Date(0),
|
|
86
|
+
) => {
|
|
87
|
+
const atomic = toAtomicIfAmount(value);
|
|
88
|
+
if (atomic < 0n)
|
|
89
|
+
throw new Error("Value must not be negative");
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
types: { EIP712Domain: eip712DomainType(domain), [primaryType]: authorizationType },
|
|
93
|
+
primaryType,
|
|
94
|
+
domain,
|
|
95
|
+
message: { from,
|
|
96
|
+
to,
|
|
97
|
+
value: atomic,
|
|
98
|
+
validAfter: evmTimestampItem.custom.from(validAfter),
|
|
99
|
+
validBefore: deadlineItem.custom.from(validBefore),
|
|
100
|
+
nonce,
|
|
101
|
+
},
|
|
102
|
+
} as const satisfies Eip712Data<Eip3009AuthorizationMessage>;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
//compose an EIP-3009 authorization; `toViemTypedData` bridges it to viem's signing calls
|
|
106
|
+
export const composeTransferWithAuthorizationMsg =
|
|
107
|
+
composeAuthorization("TransferWithAuthorization");
|
|
108
|
+
export const composeReceiveWithAuthorizationMsg =
|
|
109
|
+
composeAuthorization("ReceiveWithAuthorization");
|
|
110
|
+
|
|
111
|
+
export const composeCancelAuthorizationMsg = (
|
|
112
|
+
authorizer: Address,
|
|
113
|
+
domain: Eip712Domain,
|
|
114
|
+
nonce: RoUint8Array,
|
|
115
|
+
) => ({
|
|
116
|
+
types: { EIP712Domain: eip712DomainType(domain),
|
|
117
|
+
CancelAuthorization: cancelAuthorizationType,
|
|
118
|
+
},
|
|
119
|
+
primaryType: "CancelAuthorization",
|
|
120
|
+
domain,
|
|
121
|
+
message: { authorizer, nonce },
|
|
122
|
+
} as const satisfies Eip712Data<Eip3009CancelMessage>);
|
package/src/hashing.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { keccak_256 as keccak256, sha3_256 } from "@noble/hashes/sha3.js";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./client.js";
|
|
2
|
+
export * from "./eip712.js";
|
|
3
|
+
export * from "./erc20.js";
|
|
4
|
+
export * from "./erc3009.js";
|
|
5
|
+
export * from "./hashing.js";
|
|
6
|
+
export * from "./layouting.js";
|
|
7
|
+
export * from "./parsing.js";
|
|
8
|
+
export * from "./permit.js";
|
|
9
|
+
export * from "./query.js";
|