@lacasoft/openrelay-sdk 0.1.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 +118 -0
- package/README.md +135 -0
- package/dist/index.d.mts +277 -0
- package/dist/index.d.ts +277 -0
- package/dist/index.js +554 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +529 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthError: () => import_openrelay_protocol5.AuthError,
|
|
24
|
+
NetworkError: () => import_openrelay_protocol5.NetworkError,
|
|
25
|
+
OpenRelay: () => OpenRelay,
|
|
26
|
+
OpenRelaySDKError: () => import_openrelay_protocol5.OpenRelaySDKError,
|
|
27
|
+
RoutingError: () => import_openrelay_protocol5.RoutingError,
|
|
28
|
+
USDC_ADDRESSES: () => import_openrelay_protocol.USDC_ADDRESSES,
|
|
29
|
+
ValidationError: () => import_openrelay_protocol5.ValidationError,
|
|
30
|
+
buildReceiveAuthorizationTypedData: () => buildReceiveAuthorizationTypedData,
|
|
31
|
+
classifyError: () => import_openrelay_protocol5.classifyError,
|
|
32
|
+
generateNonce: () => generateNonce,
|
|
33
|
+
signReceiveAuthorization: () => signReceiveAuthorization,
|
|
34
|
+
splitSignature: () => splitSignature
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/resources/payment-intents.ts
|
|
39
|
+
var import_openrelay_protocol3 = require("@lacasoft/openrelay-protocol");
|
|
40
|
+
|
|
41
|
+
// src/lib/eip712.ts
|
|
42
|
+
var import_node_crypto = require("crypto");
|
|
43
|
+
var import_openrelay_protocol = require("@lacasoft/openrelay-protocol");
|
|
44
|
+
var import_viem = require("viem");
|
|
45
|
+
var import_accounts = require("viem/accounts");
|
|
46
|
+
var DEFAULT_VALIDITY_WINDOW_SECONDS = 30 * 60;
|
|
47
|
+
function buildReceiveAuthorizationTypedData(params) {
|
|
48
|
+
const nowSeconds = BigInt(Math.floor(Date.now() / 1e3));
|
|
49
|
+
const validAfter = params.validAfter ?? 0n;
|
|
50
|
+
const validBefore = params.validBefore ?? nowSeconds + BigInt(DEFAULT_VALIDITY_WINDOW_SECONDS);
|
|
51
|
+
const nonce = params.nonce ?? generateNonce();
|
|
52
|
+
return {
|
|
53
|
+
domain: {
|
|
54
|
+
name: import_openrelay_protocol.USDC_DOMAIN_NAMES[params.chain],
|
|
55
|
+
version: import_openrelay_protocol.USDC_DOMAIN_VERSION,
|
|
56
|
+
chainId: import_openrelay_protocol.CHAIN_IDS[params.chain],
|
|
57
|
+
verifyingContract: import_openrelay_protocol.USDC_ADDRESSES[params.chain]
|
|
58
|
+
},
|
|
59
|
+
types: {
|
|
60
|
+
EIP712Domain: [
|
|
61
|
+
{ name: "name", type: "string" },
|
|
62
|
+
{ name: "version", type: "string" },
|
|
63
|
+
{ name: "chainId", type: "uint256" },
|
|
64
|
+
{ name: "verifyingContract", type: "address" }
|
|
65
|
+
],
|
|
66
|
+
ReceiveWithAuthorization: import_openrelay_protocol.RECEIVE_WITH_AUTHORIZATION_TYPES.ReceiveWithAuthorization
|
|
67
|
+
},
|
|
68
|
+
primaryType: "ReceiveWithAuthorization",
|
|
69
|
+
message: {
|
|
70
|
+
from: params.payer,
|
|
71
|
+
to: params.settlementHub,
|
|
72
|
+
value: params.amount,
|
|
73
|
+
validAfter,
|
|
74
|
+
validBefore,
|
|
75
|
+
nonce
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function signReceiveAuthorization(params, privateKey) {
|
|
80
|
+
const typed = buildReceiveAuthorizationTypedData(params);
|
|
81
|
+
const digest = hashTypedData(typed);
|
|
82
|
+
const sig = await (0, import_accounts.sign)({ hash: digest, privateKey });
|
|
83
|
+
return {
|
|
84
|
+
payer: typed.message.from,
|
|
85
|
+
validAfter: typed.message.validAfter,
|
|
86
|
+
validBefore: typed.message.validBefore,
|
|
87
|
+
nonce: typed.message.nonce,
|
|
88
|
+
v: Number(sig.v),
|
|
89
|
+
r: sig.r,
|
|
90
|
+
s: sig.s
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function splitSignature(signature) {
|
|
94
|
+
const bytes = (0, import_viem.hexToBytes)(signature);
|
|
95
|
+
if (bytes.length !== 65) {
|
|
96
|
+
throw new Error(`Invalid signature length: ${bytes.length} bytes (expected 65)`);
|
|
97
|
+
}
|
|
98
|
+
const r = (0, import_viem.toHex)(bytes.slice(0, 32));
|
|
99
|
+
const s = (0, import_viem.toHex)(bytes.slice(32, 64));
|
|
100
|
+
let v = bytes[64];
|
|
101
|
+
if (v < 27) v += 27;
|
|
102
|
+
return { v, r, s };
|
|
103
|
+
}
|
|
104
|
+
function generateNonce() {
|
|
105
|
+
return (0, import_viem.toHex)((0, import_node_crypto.randomBytes)(32));
|
|
106
|
+
}
|
|
107
|
+
var EIP712DOMAIN_TYPEHASH = (0, import_viem.keccak256)(
|
|
108
|
+
(0, import_viem.toHex)("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
|
|
109
|
+
);
|
|
110
|
+
var RECEIVE_WITH_AUTHORIZATION_TYPEHASH = (0, import_viem.keccak256)(
|
|
111
|
+
(0, import_viem.toHex)(
|
|
112
|
+
"ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
function hashTypedData(typed) {
|
|
116
|
+
const domainSeparator = (0, import_viem.keccak256)(
|
|
117
|
+
encodeAbi(
|
|
118
|
+
["bytes32", "bytes32", "bytes32", "uint256", "address"],
|
|
119
|
+
[
|
|
120
|
+
EIP712DOMAIN_TYPEHASH,
|
|
121
|
+
(0, import_viem.keccak256)((0, import_viem.toHex)(typed.domain.name)),
|
|
122
|
+
(0, import_viem.keccak256)((0, import_viem.toHex)(typed.domain.version)),
|
|
123
|
+
BigInt(typed.domain.chainId),
|
|
124
|
+
typed.domain.verifyingContract
|
|
125
|
+
]
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
const m = typed.message;
|
|
129
|
+
const structHash = (0, import_viem.keccak256)(
|
|
130
|
+
encodeAbi(
|
|
131
|
+
["bytes32", "address", "address", "uint256", "uint256", "uint256", "bytes32"],
|
|
132
|
+
[
|
|
133
|
+
RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
|
|
134
|
+
m.from,
|
|
135
|
+
m.to,
|
|
136
|
+
m.value,
|
|
137
|
+
m.validAfter,
|
|
138
|
+
m.validBefore,
|
|
139
|
+
m.nonce
|
|
140
|
+
]
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
return (0, import_viem.keccak256)(`0x1901${domainSeparator.slice(2)}${structHash.slice(2)}`);
|
|
144
|
+
}
|
|
145
|
+
function encodeAbi(types, values) {
|
|
146
|
+
const parts = [];
|
|
147
|
+
for (let i = 0; i < types.length; i++) {
|
|
148
|
+
const t = types[i];
|
|
149
|
+
const v = values[i];
|
|
150
|
+
if (t === "bytes32") {
|
|
151
|
+
parts.push(v.slice(2).padStart(64, "0"));
|
|
152
|
+
} else if (t === "uint256") {
|
|
153
|
+
parts.push(v.toString(16).padStart(64, "0"));
|
|
154
|
+
} else if (t === "address") {
|
|
155
|
+
parts.push(v.slice(2).toLowerCase().padStart(64, "0"));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return `0x${parts.join("")}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/lib/types.ts
|
|
162
|
+
var import_node_crypto2 = require("crypto");
|
|
163
|
+
var import_openrelay_protocol2 = require("@lacasoft/openrelay-protocol");
|
|
164
|
+
async function request(config, opts) {
|
|
165
|
+
const url = `${config.baseUrl}/v1${opts.path}`;
|
|
166
|
+
const requestId = (0, import_node_crypto2.randomUUID)();
|
|
167
|
+
const start = Date.now();
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timer = setTimeout(() => controller.abort(), config.timeout ?? 3e4);
|
|
170
|
+
let res;
|
|
171
|
+
try {
|
|
172
|
+
res = await fetch(url, {
|
|
173
|
+
method: opts.method,
|
|
174
|
+
headers: {
|
|
175
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
176
|
+
// Only declare a JSON content-type when we actually send a body.
|
|
177
|
+
// A bodyless POST (e.g. /cancel) that still claims application/json
|
|
178
|
+
// makes Fastify reject it with "Body cannot be empty when
|
|
179
|
+
// content-type is set to 'application/json'".
|
|
180
|
+
...opts.body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
181
|
+
"OpenRelay-Version": "0.1",
|
|
182
|
+
"X-Request-Id": requestId,
|
|
183
|
+
...opts.idempotencyKey ? { "Idempotency-Key": opts.idempotencyKey } : {}
|
|
184
|
+
},
|
|
185
|
+
...opts.body !== void 0 && { body: JSON.stringify(opts.body) },
|
|
186
|
+
signal: controller.signal
|
|
187
|
+
});
|
|
188
|
+
} catch (err) {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
const isAbort = err instanceof Error && err.name === "AbortError";
|
|
191
|
+
throw new import_openrelay_protocol2.NetworkError(isAbort ? `Request timed out: ${url}` : `Network error: ${url}`, err);
|
|
192
|
+
} finally {
|
|
193
|
+
clearTimeout(timer);
|
|
194
|
+
}
|
|
195
|
+
const data = await res.json();
|
|
196
|
+
config.logger?.({
|
|
197
|
+
request_id: requestId,
|
|
198
|
+
method: opts.method,
|
|
199
|
+
path: opts.path,
|
|
200
|
+
status: res.status,
|
|
201
|
+
latency_ms: Date.now() - start,
|
|
202
|
+
node_route: typeof data.node_operator === "string" ? data.node_operator : null
|
|
203
|
+
});
|
|
204
|
+
if (!res.ok) {
|
|
205
|
+
throw (0, import_openrelay_protocol2.classifyError)(data.error);
|
|
206
|
+
}
|
|
207
|
+
return data;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/resources/payment-intents.ts
|
|
211
|
+
var PaymentIntents = class {
|
|
212
|
+
constructor(config) {
|
|
213
|
+
this.config = config;
|
|
214
|
+
}
|
|
215
|
+
config;
|
|
216
|
+
/**
|
|
217
|
+
* Create a new payment intent.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* const intent = await relay.paymentIntents.create({
|
|
221
|
+
* amount: 1000, // $0.001000 USDC (6 decimals)
|
|
222
|
+
* currency: 'usdc',
|
|
223
|
+
* chain: 'base',
|
|
224
|
+
* metadata: { orderId: 'order_123' }
|
|
225
|
+
* })
|
|
226
|
+
*/
|
|
227
|
+
async create(params) {
|
|
228
|
+
const { idempotency_key, ...body } = params;
|
|
229
|
+
return request(this.config, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
path: "/payment_intents",
|
|
232
|
+
body,
|
|
233
|
+
...idempotency_key ? { idempotencyKey: idempotency_key } : {}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Retrieve a payment intent by ID.
|
|
238
|
+
*/
|
|
239
|
+
async retrieve(id) {
|
|
240
|
+
return request(this.config, {
|
|
241
|
+
method: "GET",
|
|
242
|
+
path: `/payment_intents/${id}`
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Cancel a payment intent (only valid while it is in 'created' state).
|
|
247
|
+
*/
|
|
248
|
+
async cancel(id) {
|
|
249
|
+
return request(this.config, {
|
|
250
|
+
method: "POST",
|
|
251
|
+
path: `/payment_intents/${id}/cancel`
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* List payment intents for the authenticated merchant.
|
|
256
|
+
*/
|
|
257
|
+
async list(params) {
|
|
258
|
+
const qs = new URLSearchParams();
|
|
259
|
+
if (params?.limit) qs.set("limit", String(params.limit));
|
|
260
|
+
if (params?.starting_after) qs.set("starting_after", params.starting_after);
|
|
261
|
+
return request(this.config, {
|
|
262
|
+
method: "GET",
|
|
263
|
+
path: `/payment_intents?${qs.toString()}`
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
// ── Gasless settlement (ERC-3009 / ADR-003) ────────────────────────
|
|
267
|
+
/**
|
|
268
|
+
* Build the EIP-712 typed-data structure for `ReceiveWithAuthorization`
|
|
269
|
+
* that USDC verifies on `payIntentWithAuthorization`. Hand the result to
|
|
270
|
+
* a wallet (viem `walletClient.signTypedData`, `window.ethereum`, etc.)
|
|
271
|
+
* to obtain a signature client-side.
|
|
272
|
+
*
|
|
273
|
+
* For server-side signing with a private key, prefer `signAuthorization`.
|
|
274
|
+
*
|
|
275
|
+
* @example (browser/wallet flow)
|
|
276
|
+
* const typed = relay.paymentIntents.buildAuthorizationTypedData({
|
|
277
|
+
* payer: '0xPayer...',
|
|
278
|
+
* amount: 5_000_000n, // 5 USDC (6 decimals)
|
|
279
|
+
* settlementHub: '0xHub...',
|
|
280
|
+
* chain: 'base-sepolia',
|
|
281
|
+
* })
|
|
282
|
+
* const signature = await walletClient.signTypedData(typed)
|
|
283
|
+
* const { v, r, s } = relay.paymentIntents.splitSignature(signature)
|
|
284
|
+
* await relay.paymentIntents.submitAuthorization(intentId, {
|
|
285
|
+
* payer: typed.message.from,
|
|
286
|
+
* validAfter: typed.message.validAfter,
|
|
287
|
+
* validBefore: typed.message.validBefore,
|
|
288
|
+
* nonce: typed.message.nonce,
|
|
289
|
+
* v, r, s,
|
|
290
|
+
* })
|
|
291
|
+
*/
|
|
292
|
+
buildAuthorizationTypedData(params) {
|
|
293
|
+
return buildReceiveAuthorizationTypedData(params);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Server-side convenience: build + sign the `ReceiveWithAuthorization`
|
|
297
|
+
* message in one call. Returns everything needed for `submitAuthorization`.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* const auth = await relay.paymentIntents.signAuthorization(
|
|
301
|
+
* { payer, amount: 5_000_000n, settlementHub, chain: 'base-sepolia' },
|
|
302
|
+
* privateKey,
|
|
303
|
+
* )
|
|
304
|
+
* await relay.paymentIntents.submitAuthorization(intentId, auth)
|
|
305
|
+
*/
|
|
306
|
+
async signAuthorization(params, privateKey) {
|
|
307
|
+
return signReceiveAuthorization(params, privateKey);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Submit a signed authorization for a registered intent. The API queues
|
|
311
|
+
* it for the assigned operator to settle on-chain (via
|
|
312
|
+
* `SettlementHub.payIntentWithAuthorization`).
|
|
313
|
+
*
|
|
314
|
+
* The endpoint is implemented in Phase B3; this client method targets
|
|
315
|
+
* the documented path so SDK + API can be developed in parallel.
|
|
316
|
+
*/
|
|
317
|
+
async submitAuthorization(intentId, auth) {
|
|
318
|
+
return request(this.config, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
path: `/payment_intents/${intentId}/authorize`,
|
|
321
|
+
body: serializeAuthorization(auth)
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Submit multiple signed authorizations in one HTTP request. The operator
|
|
326
|
+
* may settle them via `SettlementHub.payIntentBatchWithAuthorization`
|
|
327
|
+
* (skip-on-failure). Useful for x402 micropayment flows where many small
|
|
328
|
+
* payments are aggregated.
|
|
329
|
+
*
|
|
330
|
+
* Hard cap: `MAX_BATCH_SIZE` (matches `SettlementHub.MAX_BATCH_SIZE`,
|
|
331
|
+
* imported from `@lacasoft/openrelay-protocol` SSOT — never hardcode the literal).
|
|
332
|
+
*/
|
|
333
|
+
async submitAuthorizationBatch(items) {
|
|
334
|
+
if (items.length === 0) {
|
|
335
|
+
return { results: [], queued: 0, rejected: 0 };
|
|
336
|
+
}
|
|
337
|
+
if (items.length > import_openrelay_protocol3.MAX_BATCH_SIZE) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
`Batch too large: ${items.length} authorizations (max ${import_openrelay_protocol3.MAX_BATCH_SIZE}, see SettlementHub.MAX_BATCH_SIZE)`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
return request(this.config, {
|
|
343
|
+
method: "POST",
|
|
344
|
+
path: "/payment_intents/batch/authorize",
|
|
345
|
+
body: {
|
|
346
|
+
items: items.map((it) => ({
|
|
347
|
+
intent_id: it.intent_id,
|
|
348
|
+
authorization: serializeAuthorization(it.authorization)
|
|
349
|
+
}))
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Helper to split a 65-byte signature (e.g. from `walletClient.signTypedData`)
|
|
355
|
+
* into the {v, r, s} triple that `submitAuthorization` expects.
|
|
356
|
+
*/
|
|
357
|
+
splitSignature(signature) {
|
|
358
|
+
return splitSignature(signature);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
function serializeAuthorization(auth) {
|
|
362
|
+
return {
|
|
363
|
+
payer: auth.payer,
|
|
364
|
+
valid_after: auth.validAfter.toString(),
|
|
365
|
+
valid_before: auth.validBefore.toString(),
|
|
366
|
+
nonce: auth.nonce,
|
|
367
|
+
v: auth.v,
|
|
368
|
+
r: auth.r,
|
|
369
|
+
s: auth.s
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/resources/webhooks.ts
|
|
374
|
+
var import_node_crypto3 = require("crypto");
|
|
375
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
376
|
+
var Webhooks = class {
|
|
377
|
+
constructor(config) {
|
|
378
|
+
this.config = config;
|
|
379
|
+
}
|
|
380
|
+
config;
|
|
381
|
+
async register(url, events) {
|
|
382
|
+
return request(this.config, {
|
|
383
|
+
method: "POST",
|
|
384
|
+
path: "/webhooks",
|
|
385
|
+
body: { url, events }
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Verify a webhook payload signature.
|
|
390
|
+
* Call this in your webhook handler to ensure the request is from OpenRelay.
|
|
391
|
+
*
|
|
392
|
+
* Validates: (1) signature format, (2) timestamp freshness against
|
|
393
|
+
* `toleranceSeconds` to mitigate replay, (3) HMAC equality with
|
|
394
|
+
* timing-safe compare.
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* const event = relay.webhooks.verify(rawBody, req.headers['openrelay-signature'], secret)
|
|
398
|
+
*/
|
|
399
|
+
verify(payload, signature, secret, toleranceSeconds = DEFAULT_TOLERANCE_SECONDS) {
|
|
400
|
+
const parts = signature.split(",");
|
|
401
|
+
const ts = parts.find((p) => p.startsWith("t="))?.slice(2);
|
|
402
|
+
const sig = parts.find((p) => p.startsWith("v1="))?.slice(3);
|
|
403
|
+
if (!ts || !sig) throw new Error("Invalid signature format");
|
|
404
|
+
const timestamp = Number(ts);
|
|
405
|
+
if (!Number.isFinite(timestamp) || timestamp <= 0) {
|
|
406
|
+
throw new Error("Invalid signature format");
|
|
407
|
+
}
|
|
408
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
409
|
+
if (Math.abs(nowSeconds - timestamp) > toleranceSeconds) {
|
|
410
|
+
throw new Error("Signature timestamp outside tolerance window");
|
|
411
|
+
}
|
|
412
|
+
const expected = (0, import_node_crypto3.createHmac)("sha256", secret).update(`${ts}.${payload}`).digest("hex");
|
|
413
|
+
if (sig.length !== expected.length) {
|
|
414
|
+
throw new Error("Signature verification failed");
|
|
415
|
+
}
|
|
416
|
+
if (!(0, import_node_crypto3.timingSafeEqual)(Buffer.from(sig), Buffer.from(expected))) {
|
|
417
|
+
throw new Error("Signature verification failed");
|
|
418
|
+
}
|
|
419
|
+
return JSON.parse(payload);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// src/x402/middleware.ts
|
|
424
|
+
var import_openrelay_protocol4 = require("@lacasoft/openrelay-protocol");
|
|
425
|
+
var X402 = class {
|
|
426
|
+
constructor(config) {
|
|
427
|
+
this.config = config;
|
|
428
|
+
}
|
|
429
|
+
config;
|
|
430
|
+
/**
|
|
431
|
+
* Returns a Fastify preHandler hook that requires x402 payment.
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* app.addHook('preHandler', relay.x402.middleware({
|
|
435
|
+
* price: 1000, // $0.001 USDC
|
|
436
|
+
* currency: 'usdc',
|
|
437
|
+
* chain: 'base',
|
|
438
|
+
* }))
|
|
439
|
+
*/
|
|
440
|
+
middleware(opts) {
|
|
441
|
+
return async (req, _reply) => {
|
|
442
|
+
const challenge = await this.gate(req, opts);
|
|
443
|
+
return challenge ?? void 0;
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Returns a Next.js App Router compatible handler that wraps a route with x402.
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* export const GET = relay.x402.handler({
|
|
451
|
+
* price: 1000,
|
|
452
|
+
* handler: async (req) => Response.json({ data: 'protected' })
|
|
453
|
+
* })
|
|
454
|
+
*/
|
|
455
|
+
handler(opts) {
|
|
456
|
+
return async (req) => {
|
|
457
|
+
const challenge = await this.gate(req, opts);
|
|
458
|
+
if (challenge) return challenge;
|
|
459
|
+
return opts.handler(req);
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Shared gate: extract X-PAYMENT, verify against the API, and either
|
|
464
|
+
* return a 402 Response (challenge or rejection) or null when payment
|
|
465
|
+
* is valid and the caller should proceed.
|
|
466
|
+
*/
|
|
467
|
+
async gate(req, opts) {
|
|
468
|
+
const paymentHeader = req.headers instanceof Headers ? req.headers.get("x-payment") : req.headers["x-payment"];
|
|
469
|
+
if (!paymentHeader) {
|
|
470
|
+
const body = this.buildPaymentRequired(opts, req.url);
|
|
471
|
+
return new Response(JSON.stringify(body), {
|
|
472
|
+
status: 402,
|
|
473
|
+
headers: { "Content-Type": "application/json" }
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
const valid = await this.verify(paymentHeader, opts);
|
|
477
|
+
if (!valid) {
|
|
478
|
+
return new Response(JSON.stringify({ error: "Payment verification failed" }), {
|
|
479
|
+
status: 402,
|
|
480
|
+
headers: { "Content-Type": "application/json" }
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
buildPaymentRequired(opts, resource) {
|
|
486
|
+
return {
|
|
487
|
+
x402Version: 1,
|
|
488
|
+
accepts: [
|
|
489
|
+
{
|
|
490
|
+
scheme: "exact",
|
|
491
|
+
network: opts.chain === "base" ? "base" : opts.chain,
|
|
492
|
+
maxAmountRequired: String(opts.price),
|
|
493
|
+
resource,
|
|
494
|
+
description: opts.description ?? "API access",
|
|
495
|
+
mimeType: "application/json",
|
|
496
|
+
payTo: this.config.merchantWallet ?? "",
|
|
497
|
+
maxTimeoutSeconds: 300,
|
|
498
|
+
asset: import_openrelay_protocol4.USDC_BASE_ADDRESS,
|
|
499
|
+
extra: { name: "USDC", version: "2" }
|
|
500
|
+
}
|
|
501
|
+
]
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
async verify(paymentHeader, opts) {
|
|
505
|
+
try {
|
|
506
|
+
await request(this.config, {
|
|
507
|
+
method: "POST",
|
|
508
|
+
path: "/x402/verify",
|
|
509
|
+
body: { payment: paymentHeader, amount: opts.price, chain: opts.chain }
|
|
510
|
+
});
|
|
511
|
+
return true;
|
|
512
|
+
} catch {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// src/index.ts
|
|
519
|
+
var import_openrelay_protocol5 = require("@lacasoft/openrelay-protocol");
|
|
520
|
+
var OpenRelay = class {
|
|
521
|
+
config;
|
|
522
|
+
paymentIntents;
|
|
523
|
+
webhooks;
|
|
524
|
+
x402;
|
|
525
|
+
constructor(config) {
|
|
526
|
+
if (!config.apiKey) throw new Error("OpenRelay: apiKey is required");
|
|
527
|
+
this.config = {
|
|
528
|
+
baseUrl: config.baseUrl ?? "https://api.openrelay.site",
|
|
529
|
+
apiKey: config.apiKey,
|
|
530
|
+
timeout: config.timeout ?? 3e4,
|
|
531
|
+
...config.merchantWallet !== void 0 && { merchantWallet: config.merchantWallet },
|
|
532
|
+
...config.logger !== void 0 && { logger: config.logger }
|
|
533
|
+
};
|
|
534
|
+
this.paymentIntents = new PaymentIntents(this.config);
|
|
535
|
+
this.webhooks = new Webhooks(this.config);
|
|
536
|
+
this.x402 = new X402(this.config);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
540
|
+
0 && (module.exports = {
|
|
541
|
+
AuthError,
|
|
542
|
+
NetworkError,
|
|
543
|
+
OpenRelay,
|
|
544
|
+
OpenRelaySDKError,
|
|
545
|
+
RoutingError,
|
|
546
|
+
USDC_ADDRESSES,
|
|
547
|
+
ValidationError,
|
|
548
|
+
buildReceiveAuthorizationTypedData,
|
|
549
|
+
classifyError,
|
|
550
|
+
generateNonce,
|
|
551
|
+
signReceiveAuthorization,
|
|
552
|
+
splitSignature
|
|
553
|
+
});
|
|
554
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/resources/payment-intents.ts","../src/lib/eip712.ts","../src/lib/types.ts","../src/resources/webhooks.ts","../src/x402/middleware.ts"],"sourcesContent":["import type { OpenRelayConfig } from './lib/types'\nimport { PaymentIntents } from './resources/payment-intents'\nimport { Webhooks } from './resources/webhooks'\nimport { X402 } from './x402/middleware'\n\nexport class OpenRelay {\n private config: OpenRelayConfig\n\n readonly paymentIntents: PaymentIntents\n readonly webhooks: Webhooks\n readonly x402: X402\n\n constructor(config: OpenRelayConfig) {\n if (!config.apiKey) throw new Error('OpenRelay: apiKey is required')\n\n this.config = {\n baseUrl: config.baseUrl ?? 'https://api.openrelay.site',\n apiKey: config.apiKey,\n timeout: config.timeout ?? 30_000,\n ...(config.merchantWallet !== undefined && { merchantWallet: config.merchantWallet }),\n ...(config.logger !== undefined && { logger: config.logger }),\n }\n\n this.paymentIntents = new PaymentIntents(this.config)\n this.webhooks = new Webhooks(this.config)\n this.x402 = new X402(this.config)\n }\n}\n\nexport {\n OpenRelaySDKError,\n AuthError,\n ValidationError,\n RoutingError,\n NetworkError,\n classifyError,\n} from '@lacasoft/openrelay-protocol'\nexport type {\n PaymentIntent,\n CreatePaymentIntentParams,\n WebhookEvent,\n X402MiddlewareOptions,\n OpenRelayError,\n OpenRelayErrorCode,\n} from '@lacasoft/openrelay-protocol'\nexport type { LogEntry, OpenRelayConfig } from './lib/types'\n\n// ── ERC-3009 / ADR-003 gasless settlement ────────────────────\nexport {\n USDC_ADDRESSES,\n buildReceiveAuthorizationTypedData,\n signReceiveAuthorization,\n splitSignature,\n generateNonce,\n} from './lib/eip712'\nexport type {\n AuthorizationMessage,\n AuthorizationTypedData,\n BuildAuthorizationParams,\n SignedAuthorization,\n SupportedChain,\n} from './lib/eip712'\nexport type {\n SubmitAuthorizationResponse,\n SubmitAuthorizationBatchResponse,\n} from './resources/payment-intents'\n","import { MAX_BATCH_SIZE } from '@lacasoft/openrelay-protocol'\nimport type { CreatePaymentIntentParams, PaymentIntent } from '@lacasoft/openrelay-protocol'\nimport type { Address, Hex } from 'viem'\nimport {\n type AuthorizationTypedData,\n type BuildAuthorizationParams,\n type SignedAuthorization,\n splitSignature as _splitSignature,\n buildReceiveAuthorizationTypedData,\n signReceiveAuthorization,\n} from '../lib/eip712'\nimport type { OpenRelayConfig } from '../lib/types'\nimport { request } from '../lib/types'\n\nexport interface SubmitAuthorizationResponse {\n /** Echoed intent id. */\n intent_id: string\n /** Server-side estimate of when the operator will submit it on-chain. */\n estimated_settlement_at: string | null\n /** Authorization persisted server-side, awaiting operator pickup. */\n status: 'queued'\n}\n\nexport interface SubmitAuthorizationBatchResponse {\n /** Per-authorization queueing result. Order matches the input. */\n results: Array<{ intent_id: string; status: 'queued' | 'rejected'; reason?: string }>\n queued: number\n rejected: number\n}\n\nexport class PaymentIntents {\n constructor(private config: OpenRelayConfig) {}\n\n /**\n * Create a new payment intent.\n *\n * @example\n * const intent = await relay.paymentIntents.create({\n * amount: 1000, // $0.001000 USDC (6 decimals)\n * currency: 'usdc',\n * chain: 'base',\n * metadata: { orderId: 'order_123' }\n * })\n */\n async create(params: CreatePaymentIntentParams): Promise<PaymentIntent> {\n const { idempotency_key, ...body } = params\n return request<PaymentIntent>(this.config, {\n method: 'POST',\n path: '/payment_intents',\n body,\n ...(idempotency_key ? { idempotencyKey: idempotency_key } : {}),\n })\n }\n\n /**\n * Retrieve a payment intent by ID.\n */\n async retrieve(id: string): Promise<PaymentIntent> {\n return request<PaymentIntent>(this.config, {\n method: 'GET',\n path: `/payment_intents/${id}`,\n })\n }\n\n /**\n * Cancel a payment intent (only valid while it is in 'created' state).\n */\n async cancel(id: string): Promise<PaymentIntent> {\n return request<PaymentIntent>(this.config, {\n method: 'POST',\n path: `/payment_intents/${id}/cancel`,\n })\n }\n\n /**\n * List payment intents for the authenticated merchant.\n */\n async list(params?: { limit?: number; starting_after?: string }): Promise<{\n data: PaymentIntent[]\n has_more: boolean\n }> {\n const qs = new URLSearchParams()\n if (params?.limit) qs.set('limit', String(params.limit))\n if (params?.starting_after) qs.set('starting_after', params.starting_after)\n\n return request(this.config, {\n method: 'GET',\n path: `/payment_intents?${qs.toString()}`,\n })\n }\n\n // ── Gasless settlement (ERC-3009 / ADR-003) ────────────────────────\n\n /**\n * Build the EIP-712 typed-data structure for `ReceiveWithAuthorization`\n * that USDC verifies on `payIntentWithAuthorization`. Hand the result to\n * a wallet (viem `walletClient.signTypedData`, `window.ethereum`, etc.)\n * to obtain a signature client-side.\n *\n * For server-side signing with a private key, prefer `signAuthorization`.\n *\n * @example (browser/wallet flow)\n * const typed = relay.paymentIntents.buildAuthorizationTypedData({\n * payer: '0xPayer...',\n * amount: 5_000_000n, // 5 USDC (6 decimals)\n * settlementHub: '0xHub...',\n * chain: 'base-sepolia',\n * })\n * const signature = await walletClient.signTypedData(typed)\n * const { v, r, s } = relay.paymentIntents.splitSignature(signature)\n * await relay.paymentIntents.submitAuthorization(intentId, {\n * payer: typed.message.from,\n * validAfter: typed.message.validAfter,\n * validBefore: typed.message.validBefore,\n * nonce: typed.message.nonce,\n * v, r, s,\n * })\n */\n buildAuthorizationTypedData(params: BuildAuthorizationParams): AuthorizationTypedData {\n return buildReceiveAuthorizationTypedData(params)\n }\n\n /**\n * Server-side convenience: build + sign the `ReceiveWithAuthorization`\n * message in one call. Returns everything needed for `submitAuthorization`.\n *\n * @example\n * const auth = await relay.paymentIntents.signAuthorization(\n * { payer, amount: 5_000_000n, settlementHub, chain: 'base-sepolia' },\n * privateKey,\n * )\n * await relay.paymentIntents.submitAuthorization(intentId, auth)\n */\n async signAuthorization(\n params: BuildAuthorizationParams,\n privateKey: Hex,\n ): Promise<SignedAuthorization> {\n return signReceiveAuthorization(params, privateKey)\n }\n\n /**\n * Submit a signed authorization for a registered intent. The API queues\n * it for the assigned operator to settle on-chain (via\n * `SettlementHub.payIntentWithAuthorization`).\n *\n * The endpoint is implemented in Phase B3; this client method targets\n * the documented path so SDK + API can be developed in parallel.\n */\n async submitAuthorization(\n intentId: string,\n auth: SignedAuthorization,\n ): Promise<SubmitAuthorizationResponse> {\n return request<SubmitAuthorizationResponse>(this.config, {\n method: 'POST',\n path: `/payment_intents/${intentId}/authorize`,\n body: serializeAuthorization(auth),\n })\n }\n\n /**\n * Submit multiple signed authorizations in one HTTP request. The operator\n * may settle them via `SettlementHub.payIntentBatchWithAuthorization`\n * (skip-on-failure). Useful for x402 micropayment flows where many small\n * payments are aggregated.\n *\n * Hard cap: `MAX_BATCH_SIZE` (matches `SettlementHub.MAX_BATCH_SIZE`,\n * imported from `@lacasoft/openrelay-protocol` SSOT — never hardcode the literal).\n */\n async submitAuthorizationBatch(\n items: Array<{ intent_id: string; authorization: SignedAuthorization }>,\n ): Promise<SubmitAuthorizationBatchResponse> {\n if (items.length === 0) {\n return { results: [], queued: 0, rejected: 0 }\n }\n if (items.length > MAX_BATCH_SIZE) {\n throw new Error(\n `Batch too large: ${items.length} authorizations (max ${MAX_BATCH_SIZE}, see SettlementHub.MAX_BATCH_SIZE)`,\n )\n }\n return request<SubmitAuthorizationBatchResponse>(this.config, {\n method: 'POST',\n path: '/payment_intents/batch/authorize',\n body: {\n items: items.map((it) => ({\n intent_id: it.intent_id,\n authorization: serializeAuthorization(it.authorization),\n })),\n },\n })\n }\n\n /**\n * Helper to split a 65-byte signature (e.g. from `walletClient.signTypedData`)\n * into the {v, r, s} triple that `submitAuthorization` expects.\n */\n splitSignature(signature: Hex): { v: number; r: Hex; s: Hex } {\n return _splitSignature(signature)\n }\n}\n\n// ── Wire helpers ────────────────────────────────────────────────────────\n\n/** Convert SignedAuthorization (with bigint fields) to JSON-safe shape. */\nfunction serializeAuthorization(auth: SignedAuthorization): {\n payer: Address\n valid_after: string\n valid_before: string\n nonce: Hex\n v: number\n r: Hex\n s: Hex\n} {\n return {\n payer: auth.payer,\n valid_after: auth.validAfter.toString(),\n valid_before: auth.validBefore.toString(),\n nonce: auth.nonce,\n v: auth.v,\n r: auth.r,\n s: auth.s,\n }\n}\n","import { randomBytes } from 'node:crypto'\n// EIP-712 / ERC-3009 helpers for the OpenRelay SDK.\n//\n// Used to construct the `ReceiveWithAuthorization` typed-data message that\n// USDC v2.x on Base verifies when the SettlementHub calls\n// `usdc.receiveWithAuthorization(payer, hub, amount, ..., v, r, s)`.\n//\n// Two consumption modes:\n// 1. Server-side / automation: pass a private key to `signReceiveAuthorization`\n// and get back {v, r, s} directly.\n// 2. Client-side / browser wallet: call `buildReceiveAuthorizationTypedData`\n// to get the EIP-712 structure, hand to viem's walletClient.signTypedData\n// (or window.ethereum), parse the resulting 65-byte signature with\n// `splitSignature`.\nimport {\n CHAIN_IDS,\n RECEIVE_WITH_AUTHORIZATION_TYPES,\n type SupportedChain,\n USDC_ADDRESSES,\n USDC_DOMAIN_NAMES,\n USDC_DOMAIN_VERSION,\n} from '@lacasoft/openrelay-protocol'\nimport { type Address, type Hex, hexToBytes, keccak256, toHex } from 'viem'\nimport { sign } from 'viem/accounts'\n\n// ERC-3009 / USDC EIP-712 constants are the single source of truth in\n// @lacasoft/openrelay-protocol. Re-exported here so SDK consumers keep importing\n// `USDC_ADDRESSES` / `SupportedChain` from `@lacasoft/openrelay-sdk` unchanged.\nexport { USDC_ADDRESSES }\nexport type { SupportedChain }\n\n/// Default authorization validity window (30 minutes from sign time).\nconst DEFAULT_VALIDITY_WINDOW_SECONDS = 30 * 60\n\nexport interface BuildAuthorizationParams {\n /** Payer address — the `from` of the USDC transfer. Must match the signer. */\n payer: Address\n /** Amount in USDC base units (6 decimals). Must match the intent's amount. */\n amount: bigint\n /** SettlementHub contract address — the `to` of the authorization. */\n settlementHub: Address\n /** Which chain — picks USDC contract address + chainId. */\n chain: SupportedChain\n /** Optional 32-byte random nonce. SDK generates one if omitted. */\n nonce?: Hex\n /** Unix seconds; signature invalid before this. Default: 0 (always valid from start). */\n validAfter?: bigint\n /** Unix seconds; signature invalid after this. Default: now + 30 minutes. */\n validBefore?: bigint\n}\n\nexport interface AuthorizationMessage {\n from: Address\n to: Address\n value: bigint\n validAfter: bigint\n validBefore: bigint\n nonce: Hex\n}\n\nexport interface AuthorizationTypedData {\n domain: {\n name: string\n version: string\n chainId: number\n verifyingContract: Address\n }\n types: {\n EIP712Domain: { name: string; type: string }[]\n ReceiveWithAuthorization: { name: string; type: string }[]\n }\n primaryType: 'ReceiveWithAuthorization'\n message: AuthorizationMessage\n}\n\n/// EIP-712 `ReceiveWithAuthorization` typed data builder. Hand the result to\n/// viem's `walletClient.signTypedData` (browser/wallet) or to\n/// `signReceiveAuthorization` (server-side with a private key).\nexport function buildReceiveAuthorizationTypedData(\n params: BuildAuthorizationParams,\n): AuthorizationTypedData {\n const nowSeconds = BigInt(Math.floor(Date.now() / 1000))\n const validAfter = params.validAfter ?? 0n\n const validBefore = params.validBefore ?? nowSeconds + BigInt(DEFAULT_VALIDITY_WINDOW_SECONDS)\n const nonce = params.nonce ?? generateNonce()\n\n return {\n domain: {\n name: USDC_DOMAIN_NAMES[params.chain],\n version: USDC_DOMAIN_VERSION,\n chainId: CHAIN_IDS[params.chain],\n verifyingContract: USDC_ADDRESSES[params.chain],\n },\n types: {\n EIP712Domain: [\n { name: 'name', type: 'string' },\n { name: 'version', type: 'string' },\n { name: 'chainId', type: 'uint256' },\n { name: 'verifyingContract', type: 'address' },\n ],\n ReceiveWithAuthorization: RECEIVE_WITH_AUTHORIZATION_TYPES.ReceiveWithAuthorization,\n },\n primaryType: 'ReceiveWithAuthorization',\n message: {\n from: params.payer,\n to: params.settlementHub,\n value: params.amount,\n validAfter,\n validBefore,\n nonce,\n },\n }\n}\n\nexport interface SignedAuthorization {\n /** Payer address (the `from` of the USDC transfer). */\n payer: Address\n /** Authorization validity window. */\n validAfter: bigint\n validBefore: bigint\n /** 32-byte random nonce, hex-encoded. */\n nonce: Hex\n /** ECDSA signature components. */\n v: number\n r: Hex\n s: Hex\n}\n\n/// Server-side signing convenience. For browser/wallet flows, use\n/// `buildReceiveAuthorizationTypedData` + your wallet's `signTypedData`.\nexport async function signReceiveAuthorization(\n params: BuildAuthorizationParams,\n privateKey: Hex,\n): Promise<SignedAuthorization> {\n const typed = buildReceiveAuthorizationTypedData(params)\n const digest = hashTypedData(typed)\n const sig = await sign({ hash: digest, privateKey })\n return {\n payer: typed.message.from,\n validAfter: typed.message.validAfter,\n validBefore: typed.message.validBefore,\n nonce: typed.message.nonce,\n v: Number(sig.v),\n r: sig.r,\n s: sig.s,\n }\n}\n\n/// Parse a 65-byte hex signature (as returned by viem's `signTypedData`)\n/// into {v, r, s}. Use this after a wallet-side sign to package the result\n/// for `submitAuthorization`.\nexport function splitSignature(signature: Hex): { v: number; r: Hex; s: Hex } {\n const bytes = hexToBytes(signature)\n if (bytes.length !== 65) {\n throw new Error(`Invalid signature length: ${bytes.length} bytes (expected 65)`)\n }\n const r = toHex(bytes.slice(0, 32))\n const s = toHex(bytes.slice(32, 64))\n // Length-checked above, so index 64 is guaranteed.\n let v = bytes[64] as number\n // EIP-155 / EIP-1559 wallets may return v as 0 or 1; normalize to 27/28.\n if (v < 27) v += 27\n return { v, r, s }\n}\n\n/// Generate a cryptographically random 32-byte nonce as 0x-prefixed hex.\nexport function generateNonce(): Hex {\n return toHex(randomBytes(32))\n}\n\n// ── Internal: EIP-712 hashing + signing ───────────────────────\n\nconst EIP712DOMAIN_TYPEHASH = keccak256(\n toHex('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),\n)\n\nconst RECEIVE_WITH_AUTHORIZATION_TYPEHASH = keccak256(\n toHex(\n 'ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)',\n ),\n)\n\nfunction hashTypedData(typed: AuthorizationTypedData): Hex {\n const domainSeparator = keccak256(\n encodeAbi(\n ['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'],\n [\n EIP712DOMAIN_TYPEHASH,\n keccak256(toHex(typed.domain.name)),\n keccak256(toHex(typed.domain.version)),\n BigInt(typed.domain.chainId),\n typed.domain.verifyingContract,\n ],\n ),\n )\n\n const m = typed.message\n const structHash = keccak256(\n encodeAbi(\n ['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256', 'bytes32'],\n [\n RECEIVE_WITH_AUTHORIZATION_TYPEHASH,\n m.from,\n m.to,\n m.value,\n m.validAfter,\n m.validBefore,\n m.nonce,\n ],\n ),\n )\n\n // EIP-712 final digest: keccak256(\"\\x19\\x01\" || domainSeparator || structHash)\n return keccak256(`0x1901${domainSeparator.slice(2)}${structHash.slice(2)}` as Hex)\n}\n\n// Minimal ABI encoder for the fixed-shape encodings we need (no dynamic\n// types — only addresses, uints, bytes32). Avoids pulling viem's full\n// `encodeAbiParameters` for ~10 lines of work.\nfunction encodeAbi(\n types: ('bytes32' | 'uint256' | 'address')[],\n values: (Hex | bigint | Address)[],\n): Hex {\n const parts: string[] = []\n for (let i = 0; i < types.length; i++) {\n const t = types[i]\n const v = values[i]\n if (t === 'bytes32') {\n parts.push((v as Hex).slice(2).padStart(64, '0'))\n } else if (t === 'uint256') {\n parts.push((v as bigint).toString(16).padStart(64, '0'))\n } else if (t === 'address') {\n parts.push((v as Address).slice(2).toLowerCase().padStart(64, '0'))\n }\n }\n return `0x${parts.join('')}` as Hex\n}\n","import { randomUUID } from 'node:crypto'\nimport { NetworkError, classifyError } from '@lacasoft/openrelay-protocol'\n\nexport interface LogEntry {\n request_id: string\n method: string\n path: string\n status: number\n latency_ms: number\n /** Nodeit wallet selected for routing, if returned by the API. */\n node_route: string | null\n}\n\nexport interface OpenRelayConfig {\n apiKey: string\n baseUrl?: string\n timeout?: number\n merchantWallet?: string\n /** Optional structured-log hook called after every SDK request. */\n logger?: (entry: LogEntry) => void\n}\n\nexport interface RequestOptions {\n method: 'GET' | 'POST' | 'DELETE'\n path: string\n body?: unknown\n /** Forwarded as Idempotency-Key header when present. */\n idempotencyKey?: string\n}\n\nexport async function request<T>(config: OpenRelayConfig, opts: RequestOptions): Promise<T> {\n const url = `${config.baseUrl}/v1${opts.path}`\n const requestId = randomUUID()\n const start = Date.now()\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), config.timeout ?? 30_000)\n\n let res: Response\n try {\n res = await fetch(url, {\n method: opts.method,\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n // Only declare a JSON content-type when we actually send a body.\n // A bodyless POST (e.g. /cancel) that still claims application/json\n // makes Fastify reject it with \"Body cannot be empty when\n // content-type is set to 'application/json'\".\n ...(opts.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n 'OpenRelay-Version': '0.1',\n 'X-Request-Id': requestId,\n ...(opts.idempotencyKey ? { 'Idempotency-Key': opts.idempotencyKey } : {}),\n },\n ...(opts.body !== undefined && { body: JSON.stringify(opts.body) }),\n signal: controller.signal,\n })\n } catch (err) {\n clearTimeout(timer)\n const isAbort = err instanceof Error && err.name === 'AbortError'\n throw new NetworkError(isAbort ? `Request timed out: ${url}` : `Network error: ${url}`, err)\n } finally {\n clearTimeout(timer)\n }\n\n const data = (await res.json()) as { error?: unknown; node_operator?: string } & Record<\n string,\n unknown\n >\n\n config.logger?.({\n request_id: requestId,\n method: opts.method,\n path: opts.path,\n status: res.status,\n latency_ms: Date.now() - start,\n node_route: typeof data.node_operator === 'string' ? data.node_operator : null,\n })\n\n if (!res.ok) {\n throw classifyError(data.error as Parameters<typeof classifyError>[0])\n }\n\n return data as T\n}\n\nexport { NetworkError }\n","import { createHmac, timingSafeEqual } from 'node:crypto'\nimport type { WebhookEvent } from '@lacasoft/openrelay-protocol'\nimport type { OpenRelayConfig } from '../lib/types'\nimport { request } from '../lib/types'\n\n/**\n * Default tolerance for the `t=` timestamp in the signature header.\n * 5 minutes — Stripe-equivalent. Tighter than that risks rejecting webhooks\n * delivered after a brief retry; looser opens a wider replay window.\n */\nconst DEFAULT_TOLERANCE_SECONDS = 300\n\nexport class Webhooks {\n constructor(private config: OpenRelayConfig) {}\n\n async register(\n url: string,\n events: string[],\n ): Promise<{ id: string; url: string; secret: string }> {\n return request(this.config, {\n method: 'POST',\n path: '/webhooks',\n body: { url, events },\n })\n }\n\n /**\n * Verify a webhook payload signature.\n * Call this in your webhook handler to ensure the request is from OpenRelay.\n *\n * Validates: (1) signature format, (2) timestamp freshness against\n * `toleranceSeconds` to mitigate replay, (3) HMAC equality with\n * timing-safe compare.\n *\n * @example\n * const event = relay.webhooks.verify(rawBody, req.headers['openrelay-signature'], secret)\n */\n verify(\n payload: string,\n signature: string,\n secret: string,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n ): WebhookEvent {\n const parts = signature.split(',')\n const ts = parts.find((p) => p.startsWith('t='))?.slice(2)\n const sig = parts.find((p) => p.startsWith('v1='))?.slice(3)\n\n if (!ts || !sig) throw new Error('Invalid signature format')\n\n // Anti-replay: reject signatures whose `t=` is outside the tolerance\n // window. Mirrors what packages/sdk-python and packages/sdk-php do.\n const timestamp = Number(ts)\n if (!Number.isFinite(timestamp) || timestamp <= 0) {\n throw new Error('Invalid signature format')\n }\n const nowSeconds = Math.floor(Date.now() / 1000)\n if (Math.abs(nowSeconds - timestamp) > toleranceSeconds) {\n throw new Error('Signature timestamp outside tolerance window')\n }\n\n const expected = createHmac('sha256', secret).update(`${ts}.${payload}`).digest('hex')\n\n // Timing-safe equality: prevents byte-by-byte timing attacks against the\n // HMAC comparison. timingSafeEqual requires equal-length buffers, which\n // for hex-encoded sha256 is guaranteed (64 chars) — but we still guard.\n if (sig.length !== expected.length) {\n throw new Error('Signature verification failed')\n }\n if (!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {\n throw new Error('Signature verification failed')\n }\n\n return JSON.parse(payload) as WebhookEvent\n }\n}\n","import type { X402MiddlewareOptions, X402PaymentRequired } from '@lacasoft/openrelay-protocol'\nimport { USDC_BASE_ADDRESS } from '@lacasoft/openrelay-protocol'\nimport type { OpenRelayConfig } from '../lib/types'\nimport { request } from '../lib/types'\n\nexport class X402 {\n constructor(private config: OpenRelayConfig) {}\n\n /**\n * Returns a Fastify preHandler hook that requires x402 payment.\n *\n * @example\n * app.addHook('preHandler', relay.x402.middleware({\n * price: 1000, // $0.001 USDC\n * currency: 'usdc',\n * chain: 'base',\n * }))\n */\n middleware(\n opts: X402MiddlewareOptions,\n ): (req: Request, reply: Response) => Promise<Response | undefined> {\n return async (req: Request, _reply: Response): Promise<Response | undefined> => {\n const challenge = await this.gate(req, opts)\n // challenge === null means payment was verified — let the request continue\n return challenge ?? undefined\n }\n }\n\n /**\n * Returns a Next.js App Router compatible handler that wraps a route with x402.\n *\n * @example\n * export const GET = relay.x402.handler({\n * price: 1000,\n * handler: async (req) => Response.json({ data: 'protected' })\n * })\n */\n handler(opts: X402MiddlewareOptions & { handler: (req: Request) => Promise<Response> }) {\n return async (req: Request): Promise<Response> => {\n const challenge = await this.gate(req, opts)\n if (challenge) return challenge\n return opts.handler(req)\n }\n }\n\n /**\n * Shared gate: extract X-PAYMENT, verify against the API, and either\n * return a 402 Response (challenge or rejection) or null when payment\n * is valid and the caller should proceed.\n */\n private async gate(req: Request, opts: X402MiddlewareOptions): Promise<Response | null> {\n const paymentHeader =\n req.headers instanceof Headers\n ? req.headers.get('x-payment')\n : (req.headers as unknown as Record<string, string>)['x-payment']\n\n if (!paymentHeader) {\n const body = this.buildPaymentRequired(opts, req.url)\n return new Response(JSON.stringify(body), {\n status: 402,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n const valid = await this.verify(paymentHeader, opts)\n if (!valid) {\n return new Response(JSON.stringify({ error: 'Payment verification failed' }), {\n status: 402,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n return null\n }\n\n private buildPaymentRequired(opts: X402MiddlewareOptions, resource: string): X402PaymentRequired {\n return {\n x402Version: 1,\n accepts: [\n {\n scheme: 'exact',\n network: opts.chain === 'base' ? 'base' : opts.chain,\n maxAmountRequired: String(opts.price),\n resource,\n description: opts.description ?? 'API access',\n mimeType: 'application/json',\n payTo: this.config.merchantWallet ?? '',\n maxTimeoutSeconds: 300,\n asset: USDC_BASE_ADDRESS,\n extra: { name: 'USDC', version: '2' },\n },\n ],\n }\n }\n\n private async verify(paymentHeader: string, opts: X402MiddlewareOptions): Promise<boolean> {\n try {\n await request(this.config, {\n method: 'POST',\n path: '/x402/verify',\n body: { payment: paymentHeader, amount: opts.price, chain: opts.chain },\n })\n return true\n } catch {\n // Verification request failed (network error or non-2xx response) — treat as unverified\n return false\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,6BAA+B;;;ACA/B,yBAA4B;AAc5B,gCAOO;AACP,kBAAqE;AACrE,sBAAqB;AASrB,IAAM,kCAAkC,KAAK;AA8CtC,SAAS,mCACd,QACwB;AACxB,QAAM,aAAa,OAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC;AACvD,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,cAAc,OAAO,eAAe,aAAa,OAAO,+BAA+B;AAC7F,QAAM,QAAQ,OAAO,SAAS,cAAc;AAE5C,SAAO;AAAA,IACL,QAAQ;AAAA,MACN,MAAM,4CAAkB,OAAO,KAAK;AAAA,MACpC,SAAS;AAAA,MACT,SAAS,oCAAU,OAAO,KAAK;AAAA,MAC/B,mBAAmB,yCAAe,OAAO,KAAK;AAAA,IAChD;AAAA,IACA,OAAO;AAAA,MACL,cAAc;AAAA,QACZ,EAAE,MAAM,QAAQ,MAAM,SAAS;AAAA,QAC/B,EAAE,MAAM,WAAW,MAAM,SAAS;AAAA,QAClC,EAAE,MAAM,WAAW,MAAM,UAAU;AAAA,QACnC,EAAE,MAAM,qBAAqB,MAAM,UAAU;AAAA,MAC/C;AAAA,MACA,0BAA0B,2DAAiC;AAAA,IAC7D;AAAA,IACA,aAAa;AAAA,IACb,SAAS;AAAA,MACP,MAAM,OAAO;AAAA,MACb,IAAI,OAAO;AAAA,MACX,OAAO,OAAO;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAkBA,eAAsB,yBACpB,QACA,YAC8B;AAC9B,QAAM,QAAQ,mCAAmC,MAAM;AACvD,QAAM,SAAS,cAAc,KAAK;AAClC,QAAM,MAAM,UAAM,sBAAK,EAAE,MAAM,QAAQ,WAAW,CAAC;AACnD,SAAO;AAAA,IACL,OAAO,MAAM,QAAQ;AAAA,IACrB,YAAY,MAAM,QAAQ;AAAA,IAC1B,aAAa,MAAM,QAAQ;AAAA,IAC3B,OAAO,MAAM,QAAQ;AAAA,IACrB,GAAG,OAAO,IAAI,CAAC;AAAA,IACf,GAAG,IAAI;AAAA,IACP,GAAG,IAAI;AAAA,EACT;AACF;AAKO,SAAS,eAAe,WAA+C;AAC5E,QAAM,YAAQ,wBAAW,SAAS;AAClC,MAAI,MAAM,WAAW,IAAI;AACvB,UAAM,IAAI,MAAM,6BAA6B,MAAM,MAAM,sBAAsB;AAAA,EACjF;AACA,QAAM,QAAI,mBAAM,MAAM,MAAM,GAAG,EAAE,CAAC;AAClC,QAAM,QAAI,mBAAM,MAAM,MAAM,IAAI,EAAE,CAAC;AAEnC,MAAI,IAAI,MAAM,EAAE;AAEhB,MAAI,IAAI,GAAI,MAAK;AACjB,SAAO,EAAE,GAAG,GAAG,EAAE;AACnB;AAGO,SAAS,gBAAqB;AACnC,aAAO,uBAAM,gCAAY,EAAE,CAAC;AAC9B;AAIA,IAAM,4BAAwB;AAAA,MAC5B,mBAAM,oFAAoF;AAC5F;AAEA,IAAM,0CAAsC;AAAA,MAC1C;AAAA,IACE;AAAA,EACF;AACF;AAEA,SAAS,cAAc,OAAoC;AACzD,QAAM,sBAAkB;AAAA,IACtB;AAAA,MACE,CAAC,WAAW,WAAW,WAAW,WAAW,SAAS;AAAA,MACtD;AAAA,QACE;AAAA,YACA,2BAAU,mBAAM,MAAM,OAAO,IAAI,CAAC;AAAA,YAClC,2BAAU,mBAAM,MAAM,OAAO,OAAO,CAAC;AAAA,QACrC,OAAO,MAAM,OAAO,OAAO;AAAA,QAC3B,MAAM,OAAO;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI,MAAM;AAChB,QAAM,iBAAa;AAAA,IACjB;AAAA,MACE,CAAC,WAAW,WAAW,WAAW,WAAW,WAAW,WAAW,SAAS;AAAA,MAC5E;AAAA,QACE;AAAA,QACA,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAGA,aAAO,uBAAU,SAAS,gBAAgB,MAAM,CAAC,CAAC,GAAG,WAAW,MAAM,CAAC,CAAC,EAAS;AACnF;AAKA,SAAS,UACP,OACA,QACK;AACL,QAAM,QAAkB,CAAC;AACzB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,IAAI,MAAM,CAAC;AACjB,UAAM,IAAI,OAAO,CAAC;AAClB,QAAI,MAAM,WAAW;AACnB,YAAM,KAAM,EAAU,MAAM,CAAC,EAAE,SAAS,IAAI,GAAG,CAAC;AAAA,IAClD,WAAW,MAAM,WAAW;AAC1B,YAAM,KAAM,EAAa,SAAS,EAAE,EAAE,SAAS,IAAI,GAAG,CAAC;AAAA,IACzD,WAAW,MAAM,WAAW;AAC1B,YAAM,KAAM,EAAc,MAAM,CAAC,EAAE,YAAY,EAAE,SAAS,IAAI,GAAG,CAAC;AAAA,IACpE;AAAA,EACF;AACA,SAAO,KAAK,MAAM,KAAK,EAAE,CAAC;AAC5B;;;AC5OA,IAAAC,sBAA2B;AAC3B,IAAAC,6BAA4C;AA6B5C,eAAsB,QAAW,QAAyB,MAAkC;AAC1F,QAAM,MAAM,GAAG,OAAO,OAAO,MAAM,KAAK,IAAI;AAC5C,QAAM,gBAAY,gCAAW;AAC7B,QAAM,QAAQ,KAAK,IAAI;AAEvB,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO,WAAW,GAAM;AAE3E,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,MACb,SAAS;AAAA,QACP,eAAe,UAAU,OAAO,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,QAKtC,GAAI,KAAK,SAAS,SAAY,EAAE,gBAAgB,mBAAmB,IAAI,CAAC;AAAA,QACxE,qBAAqB;AAAA,QACrB,gBAAgB;AAAA,QAChB,GAAI,KAAK,iBAAiB,EAAE,mBAAmB,KAAK,eAAe,IAAI,CAAC;AAAA,MAC1E;AAAA,MACA,GAAI,KAAK,SAAS,UAAa,EAAE,MAAM,KAAK,UAAU,KAAK,IAAI,EAAE;AAAA,MACjE,QAAQ,WAAW;AAAA,IACrB,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,iBAAa,KAAK;AAClB,UAAM,UAAU,eAAe,SAAS,IAAI,SAAS;AACrD,UAAM,IAAI,wCAAa,UAAU,sBAAsB,GAAG,KAAK,kBAAkB,GAAG,IAAI,GAAG;AAAA,EAC7F,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AAEA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAK7B,SAAO,SAAS;AAAA,IACd,YAAY;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,IACX,QAAQ,IAAI;AAAA,IACZ,YAAY,KAAK,IAAI,IAAI;AAAA,IACzB,YAAY,OAAO,KAAK,kBAAkB,WAAW,KAAK,gBAAgB;AAAA,EAC5E,CAAC;AAED,MAAI,CAAC,IAAI,IAAI;AACX,cAAM,0CAAc,KAAK,KAA4C;AAAA,EACvE;AAEA,SAAO;AACT;;;AFrDO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YAAoB,QAAyB;AAAzB;AAAA,EAA0B;AAAA,EAA1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAapB,MAAM,OAAO,QAA2D;AACtE,UAAM,EAAE,iBAAiB,GAAG,KAAK,IAAI;AACrC,WAAO,QAAuB,KAAK,QAAQ;AAAA,MACzC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,MACA,GAAI,kBAAkB,EAAE,gBAAgB,gBAAgB,IAAI,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,IAAoC;AACjD,WAAO,QAAuB,KAAK,QAAQ;AAAA,MACzC,QAAQ;AAAA,MACR,MAAM,oBAAoB,EAAE;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,IAAoC;AAC/C,WAAO,QAAuB,KAAK,QAAQ;AAAA,MACzC,QAAQ;AAAA,MACR,MAAM,oBAAoB,EAAE;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,QAGR;AACD,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,QAAQ,MAAO,IAAG,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACvD,QAAI,QAAQ,eAAgB,IAAG,IAAI,kBAAkB,OAAO,cAAc;AAE1E,WAAO,QAAQ,KAAK,QAAQ;AAAA,MAC1B,QAAQ;AAAA,MACR,MAAM,oBAAoB,GAAG,SAAS,CAAC;AAAA,IACzC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6BA,4BAA4B,QAA0D;AACpF,WAAO,mCAAmC,MAAM;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,kBACJ,QACA,YAC8B;AAC9B,WAAO,yBAAyB,QAAQ,UAAU;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,oBACJ,UACA,MACsC;AACtC,WAAO,QAAqC,KAAK,QAAQ;AAAA,MACvD,QAAQ;AAAA,MACR,MAAM,oBAAoB,QAAQ;AAAA,MAClC,MAAM,uBAAuB,IAAI;AAAA,IACnC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,yBACJ,OAC2C;AAC3C,QAAI,MAAM,WAAW,GAAG;AACtB,aAAO,EAAE,SAAS,CAAC,GAAG,QAAQ,GAAG,UAAU,EAAE;AAAA,IAC/C;AACA,QAAI,MAAM,SAAS,2CAAgB;AACjC,YAAM,IAAI;AAAA,QACR,oBAAoB,MAAM,MAAM,wBAAwB,yCAAc;AAAA,MACxE;AAAA,IACF;AACA,WAAO,QAA0C,KAAK,QAAQ;AAAA,MAC5D,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,OAAO,MAAM,IAAI,CAAC,QAAQ;AAAA,UACxB,WAAW,GAAG;AAAA,UACd,eAAe,uBAAuB,GAAG,aAAa;AAAA,QACxD,EAAE;AAAA,MACJ;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe,WAA+C;AAC5D,WAAO,eAAgB,SAAS;AAAA,EAClC;AACF;AAKA,SAAS,uBAAuB,MAQ9B;AACA,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,aAAa,KAAK,WAAW,SAAS;AAAA,IACtC,cAAc,KAAK,YAAY,SAAS;AAAA,IACxC,OAAO,KAAK;AAAA,IACZ,GAAG,KAAK;AAAA,IACR,GAAG,KAAK;AAAA,IACR,GAAG,KAAK;AAAA,EACV;AACF;;;AG7NA,IAAAC,sBAA4C;AAU5C,IAAM,4BAA4B;AAE3B,IAAM,WAAN,MAAe;AAAA,EACpB,YAAoB,QAAyB;AAAzB;AAAA,EAA0B;AAAA,EAA1B;AAAA,EAEpB,MAAM,SACJ,KACA,QACsD;AACtD,WAAO,QAAQ,KAAK,QAAQ;AAAA,MAC1B,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM,EAAE,KAAK,OAAO;AAAA,IACtB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,OACE,SACA,WACA,QACA,mBAAmB,2BACL;AACd,UAAM,QAAQ,UAAU,MAAM,GAAG;AACjC,UAAM,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,IAAI,CAAC,GAAG,MAAM,CAAC;AACzD,UAAM,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC,GAAG,MAAM,CAAC;AAE3D,QAAI,CAAC,MAAM,CAAC,IAAK,OAAM,IAAI,MAAM,0BAA0B;AAI3D,UAAM,YAAY,OAAO,EAAE;AAC3B,QAAI,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,GAAG;AACjD,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,UAAM,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC/C,QAAI,KAAK,IAAI,aAAa,SAAS,IAAI,kBAAkB;AACvD,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,UAAM,eAAW,gCAAW,UAAU,MAAM,EAAE,OAAO,GAAG,EAAE,IAAI,OAAO,EAAE,EAAE,OAAO,KAAK;AAKrF,QAAI,IAAI,WAAW,SAAS,QAAQ;AAClC,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AACA,QAAI,KAAC,qCAAgB,OAAO,KAAK,GAAG,GAAG,OAAO,KAAK,QAAQ,CAAC,GAAG;AAC7D,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAEA,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B;AACF;;;ACzEA,IAAAC,6BAAkC;AAI3B,IAAM,OAAN,MAAW;AAAA,EAChB,YAAoB,QAAyB;AAAzB;AAAA,EAA0B;AAAA,EAA1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYpB,WACE,MACkE;AAClE,WAAO,OAAO,KAAc,WAAoD;AAC9E,YAAM,YAAY,MAAM,KAAK,KAAK,KAAK,IAAI;AAE3C,aAAO,aAAa;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,QAAQ,MAAgF;AACtF,WAAO,OAAO,QAAoC;AAChD,YAAM,YAAY,MAAM,KAAK,KAAK,KAAK,IAAI;AAC3C,UAAI,UAAW,QAAO;AACtB,aAAO,KAAK,QAAQ,GAAG;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,KAAK,KAAc,MAAuD;AACtF,UAAM,gBACJ,IAAI,mBAAmB,UACnB,IAAI,QAAQ,IAAI,WAAW,IAC1B,IAAI,QAA8C,WAAW;AAEpE,QAAI,CAAC,eAAe;AAClB,YAAM,OAAO,KAAK,qBAAqB,MAAM,IAAI,GAAG;AACpD,aAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,QACxC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH;AAEA,UAAM,QAAQ,MAAM,KAAK,OAAO,eAAe,IAAI;AACnD,QAAI,CAAC,OAAO;AACV,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,8BAA8B,CAAC,GAAG;AAAA,QAC5E,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,qBAAqB,MAA6B,UAAuC;AAC/F,WAAO;AAAA,MACL,aAAa;AAAA,MACb,SAAS;AAAA,QACP;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,KAAK,UAAU,SAAS,SAAS,KAAK;AAAA,UAC/C,mBAAmB,OAAO,KAAK,KAAK;AAAA,UACpC;AAAA,UACA,aAAa,KAAK,eAAe;AAAA,UACjC,UAAU;AAAA,UACV,OAAO,KAAK,OAAO,kBAAkB;AAAA,UACrC,mBAAmB;AAAA,UACnB,OAAO;AAAA,UACP,OAAO,EAAE,MAAM,QAAQ,SAAS,IAAI;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,OAAO,eAAuB,MAA+C;AACzF,QAAI;AACF,YAAM,QAAQ,KAAK,QAAQ;AAAA,QACzB,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,MAAM,EAAE,SAAS,eAAe,QAAQ,KAAK,OAAO,OAAO,KAAK,MAAM;AAAA,MACxE,CAAC;AACD,aAAO;AAAA,IACT,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AL/EA,IAAAC,6BAOO;AA/BA,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EAEC;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAAyB;AACnC,QAAI,CAAC,OAAO,OAAQ,OAAM,IAAI,MAAM,+BAA+B;AAEnE,SAAK,SAAS;AAAA,MACZ,SAAS,OAAO,WAAW;AAAA,MAC3B,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO,WAAW;AAAA,MAC3B,GAAI,OAAO,mBAAmB,UAAa,EAAE,gBAAgB,OAAO,eAAe;AAAA,MACnF,GAAI,OAAO,WAAW,UAAa,EAAE,QAAQ,OAAO,OAAO;AAAA,IAC7D;AAEA,SAAK,iBAAiB,IAAI,eAAe,KAAK,MAAM;AACpD,SAAK,WAAW,IAAI,SAAS,KAAK,MAAM;AACxC,SAAK,OAAO,IAAI,KAAK,KAAK,MAAM;AAAA,EAClC;AACF;","names":["import_openrelay_protocol","import_node_crypto","import_openrelay_protocol","import_node_crypto","import_openrelay_protocol","import_openrelay_protocol"]}
|