kawasekit 0.1.0-beta.6 → 0.2.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/dist/asset-domain-4Ioxqn28.d.cts +348 -0
- package/dist/asset-domain-4Ioxqn28.d.ts +348 -0
- package/dist/{chunk-YMABXRCK.js → chunk-6CNAYQOL.js} +2 -2
- package/dist/chunk-6CNAYQOL.js.map +1 -0
- package/dist/{chunk-E2EG72U2.js → chunk-SMAZUZFO.js} +4 -9
- package/dist/chunk-SMAZUZFO.js.map +1 -0
- package/dist/{chunk-RUWCCP37.js → chunk-THTVJZ2Q.js} +5 -10
- package/dist/chunk-THTVJZ2Q.js.map +1 -0
- package/dist/{chunk-VPRR3TNA.js → chunk-VXZHS74W.js} +59 -51
- package/dist/chunk-VXZHS74W.js.map +1 -0
- package/dist/{chunk-UQ7WJY6O.js → chunk-XRSZTZVZ.js} +2 -2
- package/dist/{chunk-UQ7WJY6O.js.map → chunk-XRSZTZVZ.js.map} +1 -1
- package/dist/cli/index.cjs +1 -1
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +6 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/{index-Z6AL1MR_.d.cts → index-Cn6kg7KH.d.cts} +1 -1
- package/dist/{index-BaAOB0xd.d.ts → index-f-Xg86P9.d.ts} +1 -1
- package/dist/index.cjs +14 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -170
- package/dist/index.d.ts +5 -170
- package/dist/index.js +5 -5
- package/dist/policy/index.cjs.map +1 -1
- package/dist/policy/index.d.cts +1 -1
- package/dist/policy/index.d.ts +1 -1
- package/dist/policy/index.js +1 -1
- package/dist/signer/index.cjs +9 -6
- package/dist/signer/index.cjs.map +1 -1
- package/dist/signer/index.d.cts +2 -2
- package/dist/signer/index.d.ts +2 -2
- package/dist/signer/index.js +3 -3
- package/dist/{spending-policy-DZSNHqnD.d.ts → spending-policy-DKZN3Sg8.d.ts} +3 -2
- package/dist/{spending-policy-DqBRDUxx.d.cts → spending-policy-DaajDg9B.d.cts} +3 -2
- package/dist/x402/index.cjs +9 -6
- package/dist/x402/index.cjs.map +1 -1
- package/dist/x402/index.d.cts +2 -2
- package/dist/x402/index.d.ts +2 -2
- package/dist/x402/index.js +2 -2
- package/package.json +1 -1
- package/dist/asset-domain-CpJuDkI2.d.cts +0 -102
- package/dist/asset-domain-CpJuDkI2.d.ts +0 -102
- package/dist/chunk-E2EG72U2.js.map +0 -1
- package/dist/chunk-RUWCCP37.js.map +0 -1
- package/dist/chunk-VPRR3TNA.js.map +0 -1
- package/dist/chunk-YMABXRCK.js.map +0 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { Address, Hex, Account } from 'viem';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EIP-3009 typed-data builders and signing helpers.
|
|
5
|
+
*
|
|
6
|
+
* Token-agnostic: works for any EIP-3009-compliant token (JPYC, USDC, USDP,
|
|
7
|
+
* Centre FiatToken family). Pure off-chain construction — no chain RPC, no
|
|
8
|
+
* submission. Submission is the caller's job (M3 x402 flow, or arbitrary
|
|
9
|
+
* gas-paying relayer).
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* EIP-712 domain for an EIP-3009-compliant token.
|
|
16
|
+
*
|
|
17
|
+
* `name` and `version` MUST match the values the token used when computing
|
|
18
|
+
* its `DOMAIN_SEPARATOR`. For JPYC see {@link JPYC_EIP712_DOMAIN_HINT}.
|
|
19
|
+
*/
|
|
20
|
+
interface Eip3009Domain {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly version: string;
|
|
23
|
+
readonly chainId: number;
|
|
24
|
+
readonly verifyingContract: Address;
|
|
25
|
+
}
|
|
26
|
+
/** Message body for {@link signTransferWithAuthorization}. */
|
|
27
|
+
interface TransferWithAuthorizationMessage {
|
|
28
|
+
readonly from: Address;
|
|
29
|
+
readonly to: Address;
|
|
30
|
+
readonly value: bigint;
|
|
31
|
+
readonly validAfter: bigint;
|
|
32
|
+
readonly validBefore: bigint;
|
|
33
|
+
/** 32-byte random nonce. Generate with {@link generateAuthorizationNonce}. */
|
|
34
|
+
readonly nonce: Hex;
|
|
35
|
+
}
|
|
36
|
+
/** Message body for {@link signReceiveWithAuthorization}. */
|
|
37
|
+
type ReceiveWithAuthorizationMessage = TransferWithAuthorizationMessage;
|
|
38
|
+
/** Message body for {@link signCancelAuthorization}. */
|
|
39
|
+
interface CancelAuthorizationMessage {
|
|
40
|
+
readonly authorizer: Address;
|
|
41
|
+
readonly nonce: Hex;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A signed EIP-3009 authorization, ready to be passed to the token's
|
|
45
|
+
* `*WithAuthorization` entrypoint as `(v, r, s)`.
|
|
46
|
+
*/
|
|
47
|
+
interface SignedAuthorization<TMessage> {
|
|
48
|
+
readonly signature: Hex;
|
|
49
|
+
readonly v: number;
|
|
50
|
+
readonly r: Hex;
|
|
51
|
+
readonly s: Hex;
|
|
52
|
+
readonly domain: Eip3009Domain;
|
|
53
|
+
readonly message: TMessage;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Canonical EIP-712 `TransferWithAuthorization` type definition.
|
|
57
|
+
*
|
|
58
|
+
* This is the **single source of truth** for the typed-data structure (field
|
|
59
|
+
* names, types, and order) that EIP-3009 hashes and `ecrecover` verifies.
|
|
60
|
+
* Exported so out-of-process / cross-language consumers — notably the `mpc-2p`
|
|
61
|
+
* co-signer backend (RFC M6-1 §4.5, H1) — bind to this exact definition (or
|
|
62
|
+
* codegen from it) instead of re-declaring it, and so the digest the policy
|
|
63
|
+
* gates on is provably the digest the chain verifies (see the digest-conformance
|
|
64
|
+
* corpus in `__fixtures__/eip3009-digest.vectors.json`).
|
|
65
|
+
*/
|
|
66
|
+
declare const transferWithAuthorizationTypes: {
|
|
67
|
+
readonly TransferWithAuthorization: readonly [{
|
|
68
|
+
readonly name: "from";
|
|
69
|
+
readonly type: "address";
|
|
70
|
+
}, {
|
|
71
|
+
readonly name: "to";
|
|
72
|
+
readonly type: "address";
|
|
73
|
+
}, {
|
|
74
|
+
readonly name: "value";
|
|
75
|
+
readonly type: "uint256";
|
|
76
|
+
}, {
|
|
77
|
+
readonly name: "validAfter";
|
|
78
|
+
readonly type: "uint256";
|
|
79
|
+
}, {
|
|
80
|
+
readonly name: "validBefore";
|
|
81
|
+
readonly type: "uint256";
|
|
82
|
+
}, {
|
|
83
|
+
readonly name: "nonce";
|
|
84
|
+
readonly type: "bytes32";
|
|
85
|
+
}];
|
|
86
|
+
};
|
|
87
|
+
/** Canonical EIP-712 `ReceiveWithAuthorization` types. See {@link transferWithAuthorizationTypes}. */
|
|
88
|
+
declare const receiveWithAuthorizationTypes: {
|
|
89
|
+
readonly ReceiveWithAuthorization: readonly [{
|
|
90
|
+
readonly name: "from";
|
|
91
|
+
readonly type: "address";
|
|
92
|
+
}, {
|
|
93
|
+
readonly name: "to";
|
|
94
|
+
readonly type: "address";
|
|
95
|
+
}, {
|
|
96
|
+
readonly name: "value";
|
|
97
|
+
readonly type: "uint256";
|
|
98
|
+
}, {
|
|
99
|
+
readonly name: "validAfter";
|
|
100
|
+
readonly type: "uint256";
|
|
101
|
+
}, {
|
|
102
|
+
readonly name: "validBefore";
|
|
103
|
+
readonly type: "uint256";
|
|
104
|
+
}, {
|
|
105
|
+
readonly name: "nonce";
|
|
106
|
+
readonly type: "bytes32";
|
|
107
|
+
}];
|
|
108
|
+
};
|
|
109
|
+
/** Canonical EIP-712 `CancelAuthorization` types. See {@link transferWithAuthorizationTypes}. */
|
|
110
|
+
declare const cancelAuthorizationTypes: {
|
|
111
|
+
readonly CancelAuthorization: readonly [{
|
|
112
|
+
readonly name: "authorizer";
|
|
113
|
+
readonly type: "address";
|
|
114
|
+
}, {
|
|
115
|
+
readonly name: "nonce";
|
|
116
|
+
readonly type: "bytes32";
|
|
117
|
+
}];
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Generates a cryptographically random 32-byte EIP-3009 nonce.
|
|
121
|
+
*
|
|
122
|
+
* The nonce only needs to be unique per `(authorizer, contract)` — duplicates
|
|
123
|
+
* across different tokens are harmless because the contract scopes them.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* import { generateAuthorizationNonce } from "kawasekit";
|
|
128
|
+
*
|
|
129
|
+
* const nonce = generateAuthorizationNonce();
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare function generateAuthorizationNonce(): Hex;
|
|
133
|
+
/**
|
|
134
|
+
* Derives a **deterministic** 32-byte EIP-3009 nonce from a reasoning-step
|
|
135
|
+
* idempotency key, scoped to `(from, verifyingContract, chainId)` so the same
|
|
136
|
+
* key never collides across tokens or chains (M5-1, Half B).
|
|
137
|
+
*
|
|
138
|
+
* `nonce = keccak256(DOMAIN_TAG ‖ idempotencyKey ‖ from ‖ verifyingContract ‖
|
|
139
|
+
* chainId)`. **No shared secret**: determinism across replicas / sub-agents
|
|
140
|
+
* needs only a shared `conversationId` (the source of the key), not secret
|
|
141
|
+
* distribution. A replayed key ⇒ identical nonce ⇒ the token contract's
|
|
142
|
+
* `authorizationState` rejects the second settlement — the on-chain last line of
|
|
143
|
+
* defence against re-signed same-intent duplicate payments. Use in place of
|
|
144
|
+
* {@link generateAuthorizationNonce} only when a key is available.
|
|
145
|
+
*
|
|
146
|
+
* `chainId` is in the preimage, so the same JPYC address on Polygon / Avalanche
|
|
147
|
+
* / Kaia / Ethereum yields distinct nonces (cross-chain replay safety).
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* import { deriveAuthorizationNonce } from "kawasekit";
|
|
152
|
+
*
|
|
153
|
+
* const nonce = deriveAuthorizationNonce(
|
|
154
|
+
* { idempotencyKey },
|
|
155
|
+
* { from: account.address, verifyingContract, chainId },
|
|
156
|
+
* );
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
declare function deriveAuthorizationNonce(input: {
|
|
160
|
+
readonly idempotencyKey: string;
|
|
161
|
+
}, scope: {
|
|
162
|
+
readonly from: Address;
|
|
163
|
+
readonly verifyingContract: Address;
|
|
164
|
+
readonly chainId: number;
|
|
165
|
+
}): Hex;
|
|
166
|
+
/**
|
|
167
|
+
* Returns a `validBefore` UNIX timestamp `seconds` in the future.
|
|
168
|
+
*
|
|
169
|
+
* @param seconds - Lifetime of the authorization, in seconds.
|
|
170
|
+
* @param nowSec - Optional override of "now" (defaults to {@link Date.now}).
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* import { authorizationDeadlineFromNow } from "kawasekit";
|
|
175
|
+
*
|
|
176
|
+
* const validBefore = authorizationDeadlineFromNow(60 * 5); // 5 minutes
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
declare function authorizationDeadlineFromNow(seconds: number, nowSec?: bigint): bigint;
|
|
180
|
+
/**
|
|
181
|
+
* Signs an EIP-3009 `TransferWithAuthorization` message.
|
|
182
|
+
*
|
|
183
|
+
* The signing account MUST equal `message.from` — EIP-3009 rejects signatures
|
|
184
|
+
* from anyone else (the on-chain check is pure `ecrecover` against `from`).
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* import { privateKeyToAccount } from "viem/accounts";
|
|
189
|
+
* import {
|
|
190
|
+
* authorizationDeadlineFromNow,
|
|
191
|
+
* generateAuthorizationNonce,
|
|
192
|
+
* JPYC_EIP712_DOMAIN_HINT,
|
|
193
|
+
* polygon,
|
|
194
|
+
* signTransferWithAuthorization,
|
|
195
|
+
* } from "kawasekit";
|
|
196
|
+
*
|
|
197
|
+
* const account = privateKeyToAccount("0x...");
|
|
198
|
+
* const signed = await signTransferWithAuthorization(account, {
|
|
199
|
+
* ...JPYC_EIP712_DOMAIN_HINT,
|
|
200
|
+
* chainId: polygon.id,
|
|
201
|
+
* verifyingContract: "0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29",
|
|
202
|
+
* }, {
|
|
203
|
+
* from: account.address,
|
|
204
|
+
* to: "0xBeef...",
|
|
205
|
+
* value: 100n * 10n ** 18n,
|
|
206
|
+
* validAfter: 0n,
|
|
207
|
+
* validBefore: authorizationDeadlineFromNow(300),
|
|
208
|
+
* nonce: generateAuthorizationNonce(),
|
|
209
|
+
* });
|
|
210
|
+
* // → submit (v, r, s) to token.transferWithAuthorization(...)
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
declare function signTransferWithAuthorization(account: Account, domain: Eip3009Domain, message: TransferWithAuthorizationMessage): Promise<SignedAuthorization<TransferWithAuthorizationMessage>>;
|
|
214
|
+
/**
|
|
215
|
+
* Signs an EIP-3009 `ReceiveWithAuthorization` message.
|
|
216
|
+
*
|
|
217
|
+
* Differs from {@link signTransferWithAuthorization} in two ways:
|
|
218
|
+
* 1. Uses the `ReceiveWithAuthorization` EIP-712 type.
|
|
219
|
+
* 2. The contract additionally enforces `msg.sender == to` at submission
|
|
220
|
+
* time, so only `to` (or a relayer impersonating `to` — impossible in
|
|
221
|
+
* practice) can land the tx.
|
|
222
|
+
*/
|
|
223
|
+
declare function signReceiveWithAuthorization(account: Account, domain: Eip3009Domain, message: ReceiveWithAuthorizationMessage): Promise<SignedAuthorization<ReceiveWithAuthorizationMessage>>;
|
|
224
|
+
/**
|
|
225
|
+
* Signs an EIP-3009 `CancelAuthorization` message.
|
|
226
|
+
*
|
|
227
|
+
* Cancelling consumes the nonce so a later `transferWithAuthorization` or
|
|
228
|
+
* `receiveWithAuthorization` with the same nonce will revert.
|
|
229
|
+
*/
|
|
230
|
+
declare function signCancelAuthorization(account: Account, domain: Eip3009Domain, message: CancelAuthorizationMessage): Promise<SignedAuthorization<CancelAuthorizationMessage>>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Known-asset registry for {@link createX402PaymentSigner}'s
|
|
234
|
+
* `asset: { kind: "known", id }` discriminated-union branch.
|
|
235
|
+
*
|
|
236
|
+
* kawasekit only ships pinned EIP-712 domain definitions for assets it has
|
|
237
|
+
* verified empirically against the deployed contracts. Adding a new entry
|
|
238
|
+
* here requires citing the source-file + line reference for the contract
|
|
239
|
+
* that owns the `name` / `version` (so the next reviewer can spot-check the
|
|
240
|
+
* claim, the same discipline `docs/THREAT_MODEL.md` §0 demands of any ✅
|
|
241
|
+
* verdict that delegates to an out-of-scope component).
|
|
242
|
+
*
|
|
243
|
+
* @packageDocumentation
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
/** Known asset identifiers. New entries must update this union AND the table. */
|
|
247
|
+
type KnownAssetId = "jpyc-v2";
|
|
248
|
+
/** Fully-pinned EIP-712 domain for a known asset. */
|
|
249
|
+
interface KnownAssetDomain {
|
|
250
|
+
readonly id: KnownAssetId;
|
|
251
|
+
readonly name: string;
|
|
252
|
+
readonly version: string;
|
|
253
|
+
readonly verifyingContract: Address;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Look up a known asset's pinned EIP-712 domain by id.
|
|
257
|
+
*
|
|
258
|
+
* @returns The domain, or `undefined` if the id is not in the registry.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { getKnownAssetDomain } from "kawasekit";
|
|
263
|
+
*
|
|
264
|
+
* const jpyc = getKnownAssetDomain("jpyc-v2");
|
|
265
|
+
* if (jpyc === undefined) throw new Error("unreachable");
|
|
266
|
+
* console.log(jpyc.verifyingContract); // 0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
declare function getKnownAssetDomain(id: KnownAssetId): KnownAssetDomain | undefined;
|
|
270
|
+
/** List every known asset id (for diagnostics / error messages). */
|
|
271
|
+
declare function listKnownAssetIds(): readonly KnownAssetId[];
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* EIP-712 asset-domain resolution for x402 / EIP-3009 signing.
|
|
275
|
+
*
|
|
276
|
+
* Construction-time pinning of the EIP-712 domain (`name` / `version` /
|
|
277
|
+
* `verifyingContract`) a signer will use. The integrator declares an
|
|
278
|
+
* {@link X402AssetParam} — either a kawasekit-maintained `known` asset or a
|
|
279
|
+
* loud `unsafeOverride` — and {@link resolveAssetParam} resolves it to a pinned
|
|
280
|
+
* {@link ResolvedAsset}. The signer then trusts only this pinned domain and
|
|
281
|
+
* refuses to sign for a mismatched advertised asset (Threat 1.4: misadvertised
|
|
282
|
+
* EIP-712 domain).
|
|
283
|
+
*
|
|
284
|
+
* Token-domain concern, reused by both the x402 signer (`src/x402/client.ts`)
|
|
285
|
+
* and the M6 PolicyGatedSigner (`src/signer/`).
|
|
286
|
+
*
|
|
287
|
+
* @packageDocumentation
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
/** EIP-712 token domain `name` / `version` pair. */
|
|
291
|
+
interface X402TokenDomain {
|
|
292
|
+
readonly name: string;
|
|
293
|
+
readonly version: string;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Asset binding for {@link createX402PaymentSigner} and the M6 PolicyGatedSigner.
|
|
297
|
+
* Required, discriminated.
|
|
298
|
+
*
|
|
299
|
+
* **Default-on whitelist**: integrators MUST declare which asset they intend
|
|
300
|
+
* to sign for. The `known` branch references a kawasekit-maintained
|
|
301
|
+
* whitelist (see `src/tokens/known-assets.ts`); the `unsafeOverride` branch
|
|
302
|
+
* is the deliberate escape hatch for any other asset and is named loudly so
|
|
303
|
+
* it survives a code review. Either way, the signer pins the EIP-712 domain
|
|
304
|
+
* at construction time and refuses to sign if `paymentRequirements.asset`
|
|
305
|
+
* disagrees with the pinned `verifyingContract`.
|
|
306
|
+
*
|
|
307
|
+
* Closes Threat 1.4 (misadvertised EIP-712 domain): the server's advertised
|
|
308
|
+
* `extra.name` / `extra.version` and `asset` are all ignored for signing
|
|
309
|
+
* purposes — the signer trusts only what the integrator declared here.
|
|
310
|
+
*/
|
|
311
|
+
type X402AssetParam = {
|
|
312
|
+
/** Use a kawasekit-maintained pinned EIP-712 domain. */
|
|
313
|
+
readonly kind: "known";
|
|
314
|
+
/** The asset id to pin. See {@link KnownAssetId} for the registry. */
|
|
315
|
+
readonly id: KnownAssetId;
|
|
316
|
+
} | {
|
|
317
|
+
/**
|
|
318
|
+
* Use a caller-supplied EIP-712 domain for an asset NOT on the
|
|
319
|
+
* kawasekit whitelist. The name is deliberately loud — pick this
|
|
320
|
+
* branch only when you have separately audited the contract and its
|
|
321
|
+
* `eip712Domain()` output.
|
|
322
|
+
*/
|
|
323
|
+
readonly kind: "unsafeOverride";
|
|
324
|
+
readonly domain: {
|
|
325
|
+
readonly name: string;
|
|
326
|
+
readonly version: string;
|
|
327
|
+
readonly verifyingContract: Address;
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
/** Construction-time resolution of an {@link X402AssetParam} to a pinned domain. */
|
|
331
|
+
interface ResolvedAsset {
|
|
332
|
+
readonly name: string;
|
|
333
|
+
readonly version: string;
|
|
334
|
+
readonly verifyingContract: Address;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Assemble the EIP-712 {@link Eip3009Domain} from a construction-time pinned
|
|
338
|
+
* {@link ResolvedAsset} and the runtime `chainId`.
|
|
339
|
+
*
|
|
340
|
+
* The single place that maps `(pinned asset, chainId) -> domain`, so every
|
|
341
|
+
* signing path (`src/x402/client.ts`, `src/signer/`) builds the domain
|
|
342
|
+
* identically — the domain half of the EIP-712 single-source-of-truth the
|
|
343
|
+
* `mpc-2p` backend relies on (RFC M6-1 §4.5, H1). `name` / `version` /
|
|
344
|
+
* `verifyingContract` come from the pinned asset; only `chainId` is per-request.
|
|
345
|
+
*/
|
|
346
|
+
declare function resolvedAssetToEip3009Domain(asset: ResolvedAsset, chainId: number): Eip3009Domain;
|
|
347
|
+
|
|
348
|
+
export { type CancelAuthorizationMessage as C, type Eip3009Domain as E, type KnownAssetDomain as K, type ReceiveWithAuthorizationMessage as R, type SignedAuthorization as S, type TransferWithAuthorizationMessage as T, type X402AssetParam as X, type KnownAssetId as a, type ResolvedAsset as b, type X402TokenDomain as c, authorizationDeadlineFromNow as d, cancelAuthorizationTypes as e, deriveAuthorizationNonce as f, generateAuthorizationNonce as g, getKnownAssetDomain as h, resolvedAssetToEip3009Domain as i, signReceiveWithAuthorization as j, signTransferWithAuthorization as k, listKnownAssetIds as l, receiveWithAuthorizationTypes as r, signCancelAuthorization as s, transferWithAuthorizationTypes as t };
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { Address, Hex, Account } from 'viem';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EIP-3009 typed-data builders and signing helpers.
|
|
5
|
+
*
|
|
6
|
+
* Token-agnostic: works for any EIP-3009-compliant token (JPYC, USDC, USDP,
|
|
7
|
+
* Centre FiatToken family). Pure off-chain construction — no chain RPC, no
|
|
8
|
+
* submission. Submission is the caller's job (M3 x402 flow, or arbitrary
|
|
9
|
+
* gas-paying relayer).
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* EIP-712 domain for an EIP-3009-compliant token.
|
|
16
|
+
*
|
|
17
|
+
* `name` and `version` MUST match the values the token used when computing
|
|
18
|
+
* its `DOMAIN_SEPARATOR`. For JPYC see {@link JPYC_EIP712_DOMAIN_HINT}.
|
|
19
|
+
*/
|
|
20
|
+
interface Eip3009Domain {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly version: string;
|
|
23
|
+
readonly chainId: number;
|
|
24
|
+
readonly verifyingContract: Address;
|
|
25
|
+
}
|
|
26
|
+
/** Message body for {@link signTransferWithAuthorization}. */
|
|
27
|
+
interface TransferWithAuthorizationMessage {
|
|
28
|
+
readonly from: Address;
|
|
29
|
+
readonly to: Address;
|
|
30
|
+
readonly value: bigint;
|
|
31
|
+
readonly validAfter: bigint;
|
|
32
|
+
readonly validBefore: bigint;
|
|
33
|
+
/** 32-byte random nonce. Generate with {@link generateAuthorizationNonce}. */
|
|
34
|
+
readonly nonce: Hex;
|
|
35
|
+
}
|
|
36
|
+
/** Message body for {@link signReceiveWithAuthorization}. */
|
|
37
|
+
type ReceiveWithAuthorizationMessage = TransferWithAuthorizationMessage;
|
|
38
|
+
/** Message body for {@link signCancelAuthorization}. */
|
|
39
|
+
interface CancelAuthorizationMessage {
|
|
40
|
+
readonly authorizer: Address;
|
|
41
|
+
readonly nonce: Hex;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A signed EIP-3009 authorization, ready to be passed to the token's
|
|
45
|
+
* `*WithAuthorization` entrypoint as `(v, r, s)`.
|
|
46
|
+
*/
|
|
47
|
+
interface SignedAuthorization<TMessage> {
|
|
48
|
+
readonly signature: Hex;
|
|
49
|
+
readonly v: number;
|
|
50
|
+
readonly r: Hex;
|
|
51
|
+
readonly s: Hex;
|
|
52
|
+
readonly domain: Eip3009Domain;
|
|
53
|
+
readonly message: TMessage;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Canonical EIP-712 `TransferWithAuthorization` type definition.
|
|
57
|
+
*
|
|
58
|
+
* This is the **single source of truth** for the typed-data structure (field
|
|
59
|
+
* names, types, and order) that EIP-3009 hashes and `ecrecover` verifies.
|
|
60
|
+
* Exported so out-of-process / cross-language consumers — notably the `mpc-2p`
|
|
61
|
+
* co-signer backend (RFC M6-1 §4.5, H1) — bind to this exact definition (or
|
|
62
|
+
* codegen from it) instead of re-declaring it, and so the digest the policy
|
|
63
|
+
* gates on is provably the digest the chain verifies (see the digest-conformance
|
|
64
|
+
* corpus in `__fixtures__/eip3009-digest.vectors.json`).
|
|
65
|
+
*/
|
|
66
|
+
declare const transferWithAuthorizationTypes: {
|
|
67
|
+
readonly TransferWithAuthorization: readonly [{
|
|
68
|
+
readonly name: "from";
|
|
69
|
+
readonly type: "address";
|
|
70
|
+
}, {
|
|
71
|
+
readonly name: "to";
|
|
72
|
+
readonly type: "address";
|
|
73
|
+
}, {
|
|
74
|
+
readonly name: "value";
|
|
75
|
+
readonly type: "uint256";
|
|
76
|
+
}, {
|
|
77
|
+
readonly name: "validAfter";
|
|
78
|
+
readonly type: "uint256";
|
|
79
|
+
}, {
|
|
80
|
+
readonly name: "validBefore";
|
|
81
|
+
readonly type: "uint256";
|
|
82
|
+
}, {
|
|
83
|
+
readonly name: "nonce";
|
|
84
|
+
readonly type: "bytes32";
|
|
85
|
+
}];
|
|
86
|
+
};
|
|
87
|
+
/** Canonical EIP-712 `ReceiveWithAuthorization` types. See {@link transferWithAuthorizationTypes}. */
|
|
88
|
+
declare const receiveWithAuthorizationTypes: {
|
|
89
|
+
readonly ReceiveWithAuthorization: readonly [{
|
|
90
|
+
readonly name: "from";
|
|
91
|
+
readonly type: "address";
|
|
92
|
+
}, {
|
|
93
|
+
readonly name: "to";
|
|
94
|
+
readonly type: "address";
|
|
95
|
+
}, {
|
|
96
|
+
readonly name: "value";
|
|
97
|
+
readonly type: "uint256";
|
|
98
|
+
}, {
|
|
99
|
+
readonly name: "validAfter";
|
|
100
|
+
readonly type: "uint256";
|
|
101
|
+
}, {
|
|
102
|
+
readonly name: "validBefore";
|
|
103
|
+
readonly type: "uint256";
|
|
104
|
+
}, {
|
|
105
|
+
readonly name: "nonce";
|
|
106
|
+
readonly type: "bytes32";
|
|
107
|
+
}];
|
|
108
|
+
};
|
|
109
|
+
/** Canonical EIP-712 `CancelAuthorization` types. See {@link transferWithAuthorizationTypes}. */
|
|
110
|
+
declare const cancelAuthorizationTypes: {
|
|
111
|
+
readonly CancelAuthorization: readonly [{
|
|
112
|
+
readonly name: "authorizer";
|
|
113
|
+
readonly type: "address";
|
|
114
|
+
}, {
|
|
115
|
+
readonly name: "nonce";
|
|
116
|
+
readonly type: "bytes32";
|
|
117
|
+
}];
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Generates a cryptographically random 32-byte EIP-3009 nonce.
|
|
121
|
+
*
|
|
122
|
+
* The nonce only needs to be unique per `(authorizer, contract)` — duplicates
|
|
123
|
+
* across different tokens are harmless because the contract scopes them.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* import { generateAuthorizationNonce } from "kawasekit";
|
|
128
|
+
*
|
|
129
|
+
* const nonce = generateAuthorizationNonce();
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare function generateAuthorizationNonce(): Hex;
|
|
133
|
+
/**
|
|
134
|
+
* Derives a **deterministic** 32-byte EIP-3009 nonce from a reasoning-step
|
|
135
|
+
* idempotency key, scoped to `(from, verifyingContract, chainId)` so the same
|
|
136
|
+
* key never collides across tokens or chains (M5-1, Half B).
|
|
137
|
+
*
|
|
138
|
+
* `nonce = keccak256(DOMAIN_TAG ‖ idempotencyKey ‖ from ‖ verifyingContract ‖
|
|
139
|
+
* chainId)`. **No shared secret**: determinism across replicas / sub-agents
|
|
140
|
+
* needs only a shared `conversationId` (the source of the key), not secret
|
|
141
|
+
* distribution. A replayed key ⇒ identical nonce ⇒ the token contract's
|
|
142
|
+
* `authorizationState` rejects the second settlement — the on-chain last line of
|
|
143
|
+
* defence against re-signed same-intent duplicate payments. Use in place of
|
|
144
|
+
* {@link generateAuthorizationNonce} only when a key is available.
|
|
145
|
+
*
|
|
146
|
+
* `chainId` is in the preimage, so the same JPYC address on Polygon / Avalanche
|
|
147
|
+
* / Kaia / Ethereum yields distinct nonces (cross-chain replay safety).
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* import { deriveAuthorizationNonce } from "kawasekit";
|
|
152
|
+
*
|
|
153
|
+
* const nonce = deriveAuthorizationNonce(
|
|
154
|
+
* { idempotencyKey },
|
|
155
|
+
* { from: account.address, verifyingContract, chainId },
|
|
156
|
+
* );
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
declare function deriveAuthorizationNonce(input: {
|
|
160
|
+
readonly idempotencyKey: string;
|
|
161
|
+
}, scope: {
|
|
162
|
+
readonly from: Address;
|
|
163
|
+
readonly verifyingContract: Address;
|
|
164
|
+
readonly chainId: number;
|
|
165
|
+
}): Hex;
|
|
166
|
+
/**
|
|
167
|
+
* Returns a `validBefore` UNIX timestamp `seconds` in the future.
|
|
168
|
+
*
|
|
169
|
+
* @param seconds - Lifetime of the authorization, in seconds.
|
|
170
|
+
* @param nowSec - Optional override of "now" (defaults to {@link Date.now}).
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* import { authorizationDeadlineFromNow } from "kawasekit";
|
|
175
|
+
*
|
|
176
|
+
* const validBefore = authorizationDeadlineFromNow(60 * 5); // 5 minutes
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
declare function authorizationDeadlineFromNow(seconds: number, nowSec?: bigint): bigint;
|
|
180
|
+
/**
|
|
181
|
+
* Signs an EIP-3009 `TransferWithAuthorization` message.
|
|
182
|
+
*
|
|
183
|
+
* The signing account MUST equal `message.from` — EIP-3009 rejects signatures
|
|
184
|
+
* from anyone else (the on-chain check is pure `ecrecover` against `from`).
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* import { privateKeyToAccount } from "viem/accounts";
|
|
189
|
+
* import {
|
|
190
|
+
* authorizationDeadlineFromNow,
|
|
191
|
+
* generateAuthorizationNonce,
|
|
192
|
+
* JPYC_EIP712_DOMAIN_HINT,
|
|
193
|
+
* polygon,
|
|
194
|
+
* signTransferWithAuthorization,
|
|
195
|
+
* } from "kawasekit";
|
|
196
|
+
*
|
|
197
|
+
* const account = privateKeyToAccount("0x...");
|
|
198
|
+
* const signed = await signTransferWithAuthorization(account, {
|
|
199
|
+
* ...JPYC_EIP712_DOMAIN_HINT,
|
|
200
|
+
* chainId: polygon.id,
|
|
201
|
+
* verifyingContract: "0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29",
|
|
202
|
+
* }, {
|
|
203
|
+
* from: account.address,
|
|
204
|
+
* to: "0xBeef...",
|
|
205
|
+
* value: 100n * 10n ** 18n,
|
|
206
|
+
* validAfter: 0n,
|
|
207
|
+
* validBefore: authorizationDeadlineFromNow(300),
|
|
208
|
+
* nonce: generateAuthorizationNonce(),
|
|
209
|
+
* });
|
|
210
|
+
* // → submit (v, r, s) to token.transferWithAuthorization(...)
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
declare function signTransferWithAuthorization(account: Account, domain: Eip3009Domain, message: TransferWithAuthorizationMessage): Promise<SignedAuthorization<TransferWithAuthorizationMessage>>;
|
|
214
|
+
/**
|
|
215
|
+
* Signs an EIP-3009 `ReceiveWithAuthorization` message.
|
|
216
|
+
*
|
|
217
|
+
* Differs from {@link signTransferWithAuthorization} in two ways:
|
|
218
|
+
* 1. Uses the `ReceiveWithAuthorization` EIP-712 type.
|
|
219
|
+
* 2. The contract additionally enforces `msg.sender == to` at submission
|
|
220
|
+
* time, so only `to` (or a relayer impersonating `to` — impossible in
|
|
221
|
+
* practice) can land the tx.
|
|
222
|
+
*/
|
|
223
|
+
declare function signReceiveWithAuthorization(account: Account, domain: Eip3009Domain, message: ReceiveWithAuthorizationMessage): Promise<SignedAuthorization<ReceiveWithAuthorizationMessage>>;
|
|
224
|
+
/**
|
|
225
|
+
* Signs an EIP-3009 `CancelAuthorization` message.
|
|
226
|
+
*
|
|
227
|
+
* Cancelling consumes the nonce so a later `transferWithAuthorization` or
|
|
228
|
+
* `receiveWithAuthorization` with the same nonce will revert.
|
|
229
|
+
*/
|
|
230
|
+
declare function signCancelAuthorization(account: Account, domain: Eip3009Domain, message: CancelAuthorizationMessage): Promise<SignedAuthorization<CancelAuthorizationMessage>>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Known-asset registry for {@link createX402PaymentSigner}'s
|
|
234
|
+
* `asset: { kind: "known", id }` discriminated-union branch.
|
|
235
|
+
*
|
|
236
|
+
* kawasekit only ships pinned EIP-712 domain definitions for assets it has
|
|
237
|
+
* verified empirically against the deployed contracts. Adding a new entry
|
|
238
|
+
* here requires citing the source-file + line reference for the contract
|
|
239
|
+
* that owns the `name` / `version` (so the next reviewer can spot-check the
|
|
240
|
+
* claim, the same discipline `docs/THREAT_MODEL.md` §0 demands of any ✅
|
|
241
|
+
* verdict that delegates to an out-of-scope component).
|
|
242
|
+
*
|
|
243
|
+
* @packageDocumentation
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
/** Known asset identifiers. New entries must update this union AND the table. */
|
|
247
|
+
type KnownAssetId = "jpyc-v2";
|
|
248
|
+
/** Fully-pinned EIP-712 domain for a known asset. */
|
|
249
|
+
interface KnownAssetDomain {
|
|
250
|
+
readonly id: KnownAssetId;
|
|
251
|
+
readonly name: string;
|
|
252
|
+
readonly version: string;
|
|
253
|
+
readonly verifyingContract: Address;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Look up a known asset's pinned EIP-712 domain by id.
|
|
257
|
+
*
|
|
258
|
+
* @returns The domain, or `undefined` if the id is not in the registry.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { getKnownAssetDomain } from "kawasekit";
|
|
263
|
+
*
|
|
264
|
+
* const jpyc = getKnownAssetDomain("jpyc-v2");
|
|
265
|
+
* if (jpyc === undefined) throw new Error("unreachable");
|
|
266
|
+
* console.log(jpyc.verifyingContract); // 0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
declare function getKnownAssetDomain(id: KnownAssetId): KnownAssetDomain | undefined;
|
|
270
|
+
/** List every known asset id (for diagnostics / error messages). */
|
|
271
|
+
declare function listKnownAssetIds(): readonly KnownAssetId[];
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* EIP-712 asset-domain resolution for x402 / EIP-3009 signing.
|
|
275
|
+
*
|
|
276
|
+
* Construction-time pinning of the EIP-712 domain (`name` / `version` /
|
|
277
|
+
* `verifyingContract`) a signer will use. The integrator declares an
|
|
278
|
+
* {@link X402AssetParam} — either a kawasekit-maintained `known` asset or a
|
|
279
|
+
* loud `unsafeOverride` — and {@link resolveAssetParam} resolves it to a pinned
|
|
280
|
+
* {@link ResolvedAsset}. The signer then trusts only this pinned domain and
|
|
281
|
+
* refuses to sign for a mismatched advertised asset (Threat 1.4: misadvertised
|
|
282
|
+
* EIP-712 domain).
|
|
283
|
+
*
|
|
284
|
+
* Token-domain concern, reused by both the x402 signer (`src/x402/client.ts`)
|
|
285
|
+
* and the M6 PolicyGatedSigner (`src/signer/`).
|
|
286
|
+
*
|
|
287
|
+
* @packageDocumentation
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
/** EIP-712 token domain `name` / `version` pair. */
|
|
291
|
+
interface X402TokenDomain {
|
|
292
|
+
readonly name: string;
|
|
293
|
+
readonly version: string;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Asset binding for {@link createX402PaymentSigner} and the M6 PolicyGatedSigner.
|
|
297
|
+
* Required, discriminated.
|
|
298
|
+
*
|
|
299
|
+
* **Default-on whitelist**: integrators MUST declare which asset they intend
|
|
300
|
+
* to sign for. The `known` branch references a kawasekit-maintained
|
|
301
|
+
* whitelist (see `src/tokens/known-assets.ts`); the `unsafeOverride` branch
|
|
302
|
+
* is the deliberate escape hatch for any other asset and is named loudly so
|
|
303
|
+
* it survives a code review. Either way, the signer pins the EIP-712 domain
|
|
304
|
+
* at construction time and refuses to sign if `paymentRequirements.asset`
|
|
305
|
+
* disagrees with the pinned `verifyingContract`.
|
|
306
|
+
*
|
|
307
|
+
* Closes Threat 1.4 (misadvertised EIP-712 domain): the server's advertised
|
|
308
|
+
* `extra.name` / `extra.version` and `asset` are all ignored for signing
|
|
309
|
+
* purposes — the signer trusts only what the integrator declared here.
|
|
310
|
+
*/
|
|
311
|
+
type X402AssetParam = {
|
|
312
|
+
/** Use a kawasekit-maintained pinned EIP-712 domain. */
|
|
313
|
+
readonly kind: "known";
|
|
314
|
+
/** The asset id to pin. See {@link KnownAssetId} for the registry. */
|
|
315
|
+
readonly id: KnownAssetId;
|
|
316
|
+
} | {
|
|
317
|
+
/**
|
|
318
|
+
* Use a caller-supplied EIP-712 domain for an asset NOT on the
|
|
319
|
+
* kawasekit whitelist. The name is deliberately loud — pick this
|
|
320
|
+
* branch only when you have separately audited the contract and its
|
|
321
|
+
* `eip712Domain()` output.
|
|
322
|
+
*/
|
|
323
|
+
readonly kind: "unsafeOverride";
|
|
324
|
+
readonly domain: {
|
|
325
|
+
readonly name: string;
|
|
326
|
+
readonly version: string;
|
|
327
|
+
readonly verifyingContract: Address;
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
/** Construction-time resolution of an {@link X402AssetParam} to a pinned domain. */
|
|
331
|
+
interface ResolvedAsset {
|
|
332
|
+
readonly name: string;
|
|
333
|
+
readonly version: string;
|
|
334
|
+
readonly verifyingContract: Address;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Assemble the EIP-712 {@link Eip3009Domain} from a construction-time pinned
|
|
338
|
+
* {@link ResolvedAsset} and the runtime `chainId`.
|
|
339
|
+
*
|
|
340
|
+
* The single place that maps `(pinned asset, chainId) -> domain`, so every
|
|
341
|
+
* signing path (`src/x402/client.ts`, `src/signer/`) builds the domain
|
|
342
|
+
* identically — the domain half of the EIP-712 single-source-of-truth the
|
|
343
|
+
* `mpc-2p` backend relies on (RFC M6-1 §4.5, H1). `name` / `version` /
|
|
344
|
+
* `verifyingContract` come from the pinned asset; only `chainId` is per-request.
|
|
345
|
+
*/
|
|
346
|
+
declare function resolvedAssetToEip3009Domain(asset: ResolvedAsset, chainId: number): Eip3009Domain;
|
|
347
|
+
|
|
348
|
+
export { type CancelAuthorizationMessage as C, type Eip3009Domain as E, type KnownAssetDomain as K, type ReceiveWithAuthorizationMessage as R, type SignedAuthorization as S, type TransferWithAuthorizationMessage as T, type X402AssetParam as X, type KnownAssetId as a, type ResolvedAsset as b, type X402TokenDomain as c, authorizationDeadlineFromNow as d, cancelAuthorizationTypes as e, deriveAuthorizationNonce as f, generateAuthorizationNonce as g, getKnownAssetDomain as h, resolvedAssetToEip3009Domain as i, signReceiveWithAuthorization as j, signTransferWithAuthorization as k, listKnownAssetIds as l, receiveWithAuthorizationTypes as r, signCancelAuthorization as s, transferWithAuthorizationTypes as t };
|