@pay-skill/sdk 0.1.1

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.
Files changed (53) hide show
  1. package/README.md +154 -0
  2. package/dist/auth.d.ts +47 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +121 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/client.d.ts +93 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +391 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/errors.d.ts +24 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +42 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/index.d.ts +13 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +7 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/models.d.ts +69 -0
  19. package/dist/models.d.ts.map +1 -0
  20. package/dist/models.js +2 -0
  21. package/dist/models.js.map +1 -0
  22. package/dist/ows-signer.d.ts +75 -0
  23. package/dist/ows-signer.d.ts.map +1 -0
  24. package/dist/ows-signer.js +130 -0
  25. package/dist/ows-signer.js.map +1 -0
  26. package/dist/signer.d.ts +46 -0
  27. package/dist/signer.d.ts.map +1 -0
  28. package/dist/signer.js +112 -0
  29. package/dist/signer.js.map +1 -0
  30. package/dist/wallet.d.ts +121 -0
  31. package/dist/wallet.d.ts.map +1 -0
  32. package/dist/wallet.js +328 -0
  33. package/dist/wallet.js.map +1 -0
  34. package/eslint.config.js +22 -0
  35. package/package.json +44 -0
  36. package/src/auth.ts +200 -0
  37. package/src/client.ts +644 -0
  38. package/src/eip3009.ts +79 -0
  39. package/src/errors.ts +48 -0
  40. package/src/index.ts +51 -0
  41. package/src/models.ts +77 -0
  42. package/src/ows-signer.ts +223 -0
  43. package/src/signer.ts +147 -0
  44. package/src/wallet.ts +445 -0
  45. package/tests/test_auth_rejection.ts +154 -0
  46. package/tests/test_crypto.ts +251 -0
  47. package/tests/test_e2e.ts +158 -0
  48. package/tests/test_errors.ts +36 -0
  49. package/tests/test_ows_integration.ts +92 -0
  50. package/tests/test_ows_signer.ts +365 -0
  51. package/tests/test_signer.ts +47 -0
  52. package/tests/test_validation.ts +66 -0
  53. package/tsconfig.json +19 -0
package/dist/wallet.js ADDED
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Wallet — high-level write client for agents.
3
+ * Wraps PayClient with private key signing and balance tracking.
4
+ *
5
+ * This is the primary entry point for the playground and agent integrations.
6
+ * The PayClient is lower-level (HTTP only); Wallet adds signing + state.
7
+ */
8
+ import { privateKeyToAccount } from "viem/accounts";
9
+ import { buildAuthHeaders } from "./auth.js";
10
+ /** Map well-known chain names to numeric IDs. */
11
+ const CHAIN_IDS = {
12
+ "base": 8453,
13
+ "base-sepolia": 84532,
14
+ };
15
+ export class Wallet {
16
+ address;
17
+ _privateKey;
18
+ _apiUrl;
19
+ _chain;
20
+ _chainId;
21
+ _routerAddress;
22
+ _account;
23
+ /** URL path prefix extracted from apiUrl (e.g., "/api/v1"). */
24
+ _basePath;
25
+ /** Cached contracts response. */
26
+ _contractsCache = null;
27
+ constructor(options) {
28
+ this._privateKey = normalizeKey(options.privateKey);
29
+ this._apiUrl = options.apiUrl;
30
+ this._chain = options.chain;
31
+ this._chainId = options.chainId ?? CHAIN_IDS[options.chain] ?? (parseInt(options.chain, 10) || 8453);
32
+ this._routerAddress = options.routerAddress;
33
+ this._account = privateKeyToAccount(this._privateKey);
34
+ this.address = this._account.address;
35
+ try {
36
+ this._basePath = new URL(options.apiUrl).pathname.replace(/\/+$/, "");
37
+ }
38
+ catch {
39
+ this._basePath = "";
40
+ }
41
+ }
42
+ get _authConfig() {
43
+ return {
44
+ chainId: this._chainId,
45
+ routerAddress: this._routerAddress,
46
+ };
47
+ }
48
+ /** Build authenticated fetch headers for an API request. */
49
+ async _authFetch(path, init = {}) {
50
+ const method = (init.method ?? "GET").toUpperCase();
51
+ // Sign only the path portion (no query string) — server verifies against uri.path().
52
+ const pathOnly = path.split("?")[0];
53
+ const signPath = this._basePath + pathOnly;
54
+ const authHeaders = await buildAuthHeaders(this._privateKey, method, signPath, this._authConfig);
55
+ const resp = await fetch(`${this._apiUrl}${path}`, {
56
+ ...init,
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ ...authHeaders,
60
+ ...init.headers,
61
+ },
62
+ });
63
+ return resp;
64
+ }
65
+ /** Get USDC balance in human-readable units (e.g., 142.50). */
66
+ async balance() {
67
+ const resp = await this._authFetch("/status");
68
+ if (!resp.ok)
69
+ throw new Error(`balance fetch failed: ${resp.status}`);
70
+ const data = (await resp.json());
71
+ if (!data.balance_usdc)
72
+ return 0;
73
+ const raw = parseFloat(data.balance_usdc);
74
+ // Server returns raw micro-units (USDC 6 decimals). Convert to dollars.
75
+ return raw / 1_000_000;
76
+ }
77
+ /**
78
+ * Sign an EIP-2612 permit for a given spender and amount.
79
+ * Uses the server's /permit/prepare endpoint to get the nonce and hash,
80
+ * then signs the hash locally.
81
+ * @param flow — "direct" or "tab" (used to look up the spender contract address)
82
+ * @param amount — micro-USDC amount
83
+ */
84
+ async signPermit(flow, amount) {
85
+ const contracts = await this.getContracts();
86
+ const spender = flow === "tab" ? contracts.tab : contracts.direct;
87
+ // Server prepares the permit: reads nonce via its own RPC, computes EIP-712 hash
88
+ const prepResp = await this._authFetch("/permit/prepare", {
89
+ method: "POST",
90
+ body: JSON.stringify({ amount, spender }),
91
+ });
92
+ if (!prepResp.ok) {
93
+ const err = (await prepResp.json().catch(() => ({})));
94
+ throw new Error(err.error ?? `permit/prepare failed: ${prepResp.status}`);
95
+ }
96
+ const prep = (await prepResp.json());
97
+ // Sign the pre-computed hash locally
98
+ const signature = await this._account.signMessage({
99
+ message: { raw: prep.hash },
100
+ });
101
+ // Parse 65-byte signature into v, r, s
102
+ const sigClean = signature.startsWith("0x") ? signature.slice(2) : signature;
103
+ const r = "0x" + sigClean.slice(0, 64);
104
+ const s = "0x" + sigClean.slice(64, 128);
105
+ const v = parseInt(sigClean.slice(128, 130), 16);
106
+ return { nonce: prep.nonce, deadline: prep.deadline, v, r, s };
107
+ }
108
+ /** Send a direct payment. Auto-signs permit if not provided. */
109
+ async payDirect(to, amount, memo, options) {
110
+ const microAmount = Math.round(amount * 1_000_000);
111
+ // Auto-sign permit if not provided
112
+ let permit = options?.permit;
113
+ if (!permit) {
114
+ permit = await this.signPermit("direct", microAmount);
115
+ }
116
+ const resp = await this._authFetch("/direct", {
117
+ method: "POST",
118
+ body: JSON.stringify({
119
+ to,
120
+ amount: microAmount,
121
+ memo,
122
+ permit,
123
+ }),
124
+ });
125
+ if (!resp.ok) {
126
+ const err = (await resp.json().catch(() => ({})));
127
+ throw new Error(err.error ?? `payDirect failed: ${resp.status}`);
128
+ }
129
+ return (await resp.json());
130
+ }
131
+ /** Create a one-time fund link via the server. Returns the dashboard URL. */
132
+ async createFundLink(options) {
133
+ const resp = await this._authFetch("/links/fund", {
134
+ method: "POST",
135
+ body: JSON.stringify({
136
+ messages: options?.messages ?? [],
137
+ agent_name: options?.agentName,
138
+ }),
139
+ });
140
+ if (!resp.ok) {
141
+ const err = (await resp.json().catch(() => ({})));
142
+ throw new Error(err.error ?? `createFundLink failed: ${resp.status}`);
143
+ }
144
+ const data = (await resp.json());
145
+ return data.url;
146
+ }
147
+ /** Create a one-time withdraw link via the server. Returns the dashboard URL. */
148
+ async createWithdrawLink(options) {
149
+ const resp = await this._authFetch("/links/withdraw", {
150
+ method: "POST",
151
+ body: JSON.stringify({
152
+ messages: options?.messages ?? [],
153
+ agent_name: options?.agentName,
154
+ }),
155
+ });
156
+ if (!resp.ok) {
157
+ const err = (await resp.json().catch(() => ({})));
158
+ throw new Error(err.error ?? `createWithdrawLink failed: ${resp.status}`);
159
+ }
160
+ const data = (await resp.json());
161
+ return data.url;
162
+ }
163
+ /** Register a webhook for this wallet. */
164
+ async registerWebhook(url, events, secret) {
165
+ const payload = { url, events };
166
+ if (secret)
167
+ payload.secret = secret;
168
+ const resp = await this._authFetch("/webhooks", {
169
+ method: "POST",
170
+ body: JSON.stringify(payload),
171
+ });
172
+ if (!resp.ok)
173
+ throw new Error(`registerWebhook failed: ${resp.status}`);
174
+ return (await resp.json());
175
+ }
176
+ /** Open a tab with a provider (positional or object form). */
177
+ async openTab(providerOrOpts, amount, maxChargePerCall, options) {
178
+ let provider;
179
+ let amt;
180
+ let maxCharge;
181
+ let permit;
182
+ if (typeof providerOrOpts === "string") {
183
+ provider = providerOrOpts;
184
+ amt = amount;
185
+ maxCharge = maxChargePerCall;
186
+ permit = options?.permit;
187
+ }
188
+ else {
189
+ provider = providerOrOpts.to;
190
+ amt = providerOrOpts.limit;
191
+ maxCharge = providerOrOpts.perUnit;
192
+ permit = providerOrOpts.permit;
193
+ }
194
+ const microAmount = Math.round(amt * 1_000_000);
195
+ // Auto-sign permit if not provided
196
+ if (!permit) {
197
+ permit = await this.signPermit("tab", microAmount);
198
+ }
199
+ const resp = await this._authFetch("/tabs", {
200
+ method: "POST",
201
+ body: JSON.stringify({
202
+ provider,
203
+ amount: microAmount,
204
+ max_charge_per_call: Math.round(maxCharge * 1_000_000),
205
+ permit,
206
+ }),
207
+ });
208
+ if (!resp.ok)
209
+ throw new Error(`openTab failed: ${resp.status}`);
210
+ const data = (await resp.json());
211
+ return { id: data.tab_id, tab_id: data.tab_id };
212
+ }
213
+ /** Charge a tab (provider-side). */
214
+ async chargeTab(tabId, amountOrOpts) {
215
+ const body = typeof amountOrOpts === "number"
216
+ ? { amount: Math.round(amountOrOpts * 1_000_000) }
217
+ : {
218
+ amount: Math.round(amountOrOpts.amount * 1_000_000),
219
+ cumulative: Math.round(amountOrOpts.cumulative * 1_000_000),
220
+ call_count: amountOrOpts.callCount,
221
+ provider_sig: amountOrOpts.providerSig,
222
+ };
223
+ const resp = await this._authFetch(`/tabs/${tabId}/charge`, {
224
+ method: "POST",
225
+ body: JSON.stringify(body),
226
+ });
227
+ if (!resp.ok)
228
+ throw new Error(`chargeTab failed: ${resp.status}`);
229
+ return (await resp.json());
230
+ }
231
+ /** Close a tab. */
232
+ async closeTab(tabId, options) {
233
+ const body = {};
234
+ if (options?.finalAmount !== undefined)
235
+ body.final_amount = Math.round(options.finalAmount * 1_000_000);
236
+ if (options?.providerSig)
237
+ body.provider_sig = options.providerSig;
238
+ const resp = await this._authFetch(`/tabs/${tabId}/close`, {
239
+ method: "POST",
240
+ body: JSON.stringify(body),
241
+ });
242
+ if (!resp.ok)
243
+ throw new Error(`closeTab failed: ${resp.status}`);
244
+ return (await resp.json());
245
+ }
246
+ /** Fetch contract addresses from the API (public, no auth). */
247
+ async getContracts() {
248
+ if (this._contractsCache)
249
+ return this._contractsCache;
250
+ const resp = await fetch(`${this._apiUrl}/contracts`);
251
+ if (!resp.ok)
252
+ throw new Error(`getContracts failed: ${resp.status}`);
253
+ const data = (await resp.json());
254
+ this._contractsCache = {
255
+ router: data.router ?? "",
256
+ tab: data.tab ?? "",
257
+ direct: data.direct ?? "",
258
+ fee: data.fee ?? "",
259
+ usdc: data.usdc ?? "",
260
+ chainId: data.chain_id ?? 0,
261
+ };
262
+ return this._contractsCache;
263
+ }
264
+ /** Sign a tab charge (provider-side EIP-712 signature). */
265
+ async signTabCharge(contractAddr, tabId, cumulativeUnits, callCount) {
266
+ return this._account.signTypedData({
267
+ domain: {
268
+ name: "pay",
269
+ version: "0.1",
270
+ chainId: this._chainId,
271
+ verifyingContract: contractAddr,
272
+ },
273
+ types: {
274
+ TabCharge: [
275
+ { name: "tabId", type: "string" },
276
+ { name: "cumulativeUnits", type: "uint256" },
277
+ { name: "callCount", type: "uint256" },
278
+ ],
279
+ },
280
+ primaryType: "TabCharge",
281
+ message: {
282
+ tabId,
283
+ cumulativeUnits: BigInt(cumulativeUnits),
284
+ callCount: BigInt(callCount),
285
+ },
286
+ });
287
+ }
288
+ /** Sign a raw hash with the wallet's private key. */
289
+ async _signHash(hash) {
290
+ const signature = await this._account.signMessage({
291
+ message: { raw: hash },
292
+ });
293
+ // Parse 65-byte signature into v, r, s
294
+ const sigClean = signature.startsWith("0x")
295
+ ? signature.slice(2)
296
+ : signature;
297
+ const r = "0x" + sigClean.slice(0, 64);
298
+ const s = "0x" + sigClean.slice(64, 128);
299
+ const v = parseInt(sigClean.slice(128, 130), 16);
300
+ return { v, r, s };
301
+ }
302
+ }
303
+ /** PrivateKeySigner — for manual EIP-712 signing in the playground. */
304
+ export class PrivateKeySigner {
305
+ _account;
306
+ address;
307
+ constructor(privateKey) {
308
+ const key = normalizeKey(privateKey);
309
+ this._account = privateKeyToAccount(key);
310
+ this.address = this._account.address;
311
+ }
312
+ /** Sign EIP-712 typed data. Returns hex signature. */
313
+ async signTypedData(domain, types, message) {
314
+ return this._account.signTypedData({
315
+ domain: domain,
316
+ types: types,
317
+ primaryType: Object.keys(types)[0],
318
+ message,
319
+ });
320
+ }
321
+ }
322
+ /** Normalize a private key to 0x-prefixed Hex. */
323
+ function normalizeKey(key) {
324
+ if (key.startsWith("0x"))
325
+ return key;
326
+ return ("0x" + key);
327
+ }
328
+ //# sourceMappingURL=wallet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet.js","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import eslint from "@eslint/js";
2
+ import tseslint from "typescript-eslint";
3
+ import globals from "globals";
4
+
5
+ export default tseslint.config(
6
+ eslint.configs.recommended,
7
+ ...tseslint.configs.strict,
8
+ {
9
+ languageOptions: {
10
+ globals: globals.node,
11
+ },
12
+ rules: {
13
+ "@typescript-eslint/no-unused-vars": [
14
+ "error",
15
+ { argsIgnorePattern: "^_" },
16
+ ],
17
+ },
18
+ },
19
+ {
20
+ ignores: ["dist/", "tests/", "node_modules/"],
21
+ }
22
+ );
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@pay-skill/sdk",
3
+ "version": "0.1.1",
4
+ "description": "TypeScript SDK for pay — payment infrastructure for AI agents",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "node --import tsx --test tests/*.ts",
17
+ "typecheck": "tsc --noEmit",
18
+ "lint": "eslint src"
19
+ },
20
+ "dependencies": {
21
+ "viem": "^2.0.0"
22
+ },
23
+ "peerDependencies": {
24
+ "@open-wallet-standard/core": ">=1.2.0"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "@open-wallet-standard/core": {
28
+ "optional": true
29
+ }
30
+ },
31
+ "devDependencies": {
32
+ "@eslint/js": "^10.0.0",
33
+ "@types/node": "^25.5.2",
34
+ "eslint": "^10.2.0",
35
+ "globals": "^17.0.0",
36
+ "tsx": "^4.0.0",
37
+ "typescript": "^5.0.0",
38
+ "typescript-eslint": "^8.0.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=20.0.0"
42
+ },
43
+ "license": "MIT"
44
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,200 @@
1
+ /**
2
+ * EIP-712 authentication for pay API requests.
3
+ *
4
+ * Every authenticated request includes four headers:
5
+ * X-Pay-Agent — wallet address (0x-prefixed, checksummed)
6
+ * X-Pay-Signature — EIP-712 signature (0x-prefixed hex, 65 bytes)
7
+ * X-Pay-Timestamp — unix timestamp in seconds
8
+ * X-Pay-Nonce — random 32-byte hex (0x-prefixed)
9
+ *
10
+ * The EIP-712 domain:
11
+ * name: "pay"
12
+ * version: "0.1"
13
+ * chainId: <from config>
14
+ * verifyingContract: <router address>
15
+ *
16
+ * The typed data:
17
+ * APIRequest(string method, string path, uint256 timestamp, bytes32 nonce)
18
+ */
19
+
20
+ import { type Hex, keccak256, encodePacked, type Address } from "viem";
21
+ import { privateKeyToAccount } from "viem/accounts";
22
+ import { randomBytes } from "node:crypto";
23
+
24
+ export interface AuthConfig {
25
+ chainId: number;
26
+ routerAddress: Address;
27
+ }
28
+
29
+ export interface AuthHeaders {
30
+ "X-Pay-Agent": string;
31
+ "X-Pay-Signature": string;
32
+ "X-Pay-Timestamp": string;
33
+ "X-Pay-Nonce": string;
34
+ }
35
+
36
+ const EIP712_DOMAIN = {
37
+ name: "pay",
38
+ version: "0.1",
39
+ } as const;
40
+
41
+ const API_REQUEST_TYPES = {
42
+ APIRequest: [
43
+ { name: "method", type: "string" },
44
+ { name: "path", type: "string" },
45
+ { name: "timestamp", type: "uint256" },
46
+ { name: "nonce", type: "bytes32" },
47
+ ],
48
+ } as const;
49
+
50
+ /**
51
+ * Build auth headers for an API request using a private key.
52
+ */
53
+ export async function buildAuthHeaders(
54
+ privateKey: Hex,
55
+ method: string,
56
+ path: string,
57
+ config: AuthConfig
58
+ ): Promise<AuthHeaders> {
59
+ const account = privateKeyToAccount(privateKey);
60
+ const timestamp = BigInt(Math.floor(Date.now() / 1000));
61
+ const nonce = ("0x" + randomBytes(32).toString("hex")) as Hex;
62
+
63
+ const signature = await account.signTypedData({
64
+ domain: {
65
+ ...EIP712_DOMAIN,
66
+ chainId: config.chainId,
67
+ verifyingContract: config.routerAddress,
68
+ },
69
+ types: API_REQUEST_TYPES,
70
+ primaryType: "APIRequest",
71
+ message: {
72
+ method: method.toUpperCase(),
73
+ path,
74
+ timestamp,
75
+ nonce,
76
+ },
77
+ });
78
+
79
+ return {
80
+ "X-Pay-Agent": account.address,
81
+ "X-Pay-Signature": signature,
82
+ "X-Pay-Timestamp": timestamp.toString(),
83
+ "X-Pay-Nonce": nonce,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Build auth headers using a generic signer (Signer interface).
89
+ * Computes the EIP-712 hash manually and delegates signing to the signer.
90
+ */
91
+ export function buildAuthHeadersWithSigner(
92
+ signer: { sign(hash: Uint8Array): Uint8Array; address: string },
93
+ method: string,
94
+ path: string,
95
+ config: AuthConfig
96
+ ): AuthHeaders {
97
+ const timestamp = BigInt(Math.floor(Date.now() / 1000));
98
+ const nonce = ("0x" + randomBytes(32).toString("hex")) as Hex;
99
+
100
+ const hash = computeEip712Hash(
101
+ method.toUpperCase(),
102
+ path,
103
+ timestamp,
104
+ nonce,
105
+ config.chainId,
106
+ config.routerAddress
107
+ );
108
+
109
+ const sigBytes = signer.sign(hash);
110
+ const signature = ("0x" + Buffer.from(sigBytes).toString("hex")) as Hex;
111
+
112
+ return {
113
+ "X-Pay-Agent": signer.address,
114
+ "X-Pay-Signature": signature,
115
+ "X-Pay-Timestamp": timestamp.toString(),
116
+ "X-Pay-Nonce": nonce,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Compute the EIP-712 hash for an APIRequest.
122
+ * Must match the server's computation exactly.
123
+ */
124
+ export function computeEip712Hash(
125
+ method: string,
126
+ path: string,
127
+ timestamp: bigint,
128
+ nonce: Hex,
129
+ chainId: number,
130
+ verifyingContract: Address
131
+ ): Uint8Array {
132
+ // Type hashes
133
+ const domainTypehash = keccak256(
134
+ encodePacked(
135
+ ["string"],
136
+ [
137
+ "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
138
+ ]
139
+ )
140
+ );
141
+ const structTypehash = keccak256(
142
+ encodePacked(
143
+ ["string"],
144
+ [
145
+ "APIRequest(string method,string path,uint256 timestamp,bytes32 nonce)",
146
+ ]
147
+ )
148
+ );
149
+
150
+ // Domain separator
151
+ const nameHash = keccak256(encodePacked(["string"], ["pay"]));
152
+ const versionHash = keccak256(encodePacked(["string"], ["0.1"]));
153
+
154
+ const domainSeparator = keccak256(
155
+ encodePacked(
156
+ ["bytes32", "bytes32", "bytes32", "uint256", "bytes32"],
157
+ [
158
+ domainTypehash,
159
+ nameHash,
160
+ versionHash,
161
+ BigInt(chainId),
162
+ ("0x000000000000000000000000" + verifyingContract.slice(2)) as Hex,
163
+ ]
164
+ )
165
+ );
166
+
167
+ // Struct hash
168
+ const methodHash = keccak256(encodePacked(["string"], [method]));
169
+ const pathHash = keccak256(encodePacked(["string"], [path]));
170
+
171
+ // Pad nonce to bytes32
172
+ const nonceClean = nonce.startsWith("0x") ? nonce.slice(2) : nonce;
173
+ const noncePadded = ("0x" + nonceClean.padEnd(64, "0")) as Hex;
174
+
175
+ const structHash = keccak256(
176
+ encodePacked(
177
+ ["bytes32", "bytes32", "bytes32", "uint256", "bytes32"],
178
+ [structTypehash, methodHash, pathHash, timestamp, noncePadded]
179
+ )
180
+ );
181
+
182
+ // Final hash: keccak256("\x19\x01" || domainSeparator || structHash)
183
+ const finalHash = keccak256(
184
+ encodePacked(
185
+ ["bytes2", "bytes32", "bytes32"],
186
+ ["0x1901" as Hex, domainSeparator, structHash]
187
+ )
188
+ );
189
+
190
+ return hexToBytes(finalHash);
191
+ }
192
+
193
+ function hexToBytes(hex: string): Uint8Array {
194
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
195
+ const bytes = new Uint8Array(clean.length / 2);
196
+ for (let i = 0; i < bytes.length; i++) {
197
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
198
+ }
199
+ return bytes;
200
+ }