@haven_ai/sdk 0.1.0 → 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.
- package/README.md +61 -7
- package/dist/index.cjs +286 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -3
- package/dist/index.d.ts +37 -3
- package/dist/index.js +286 -36
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { exact } from 'x402/schemes';
|
|
2
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
1
3
|
import { ethers } from 'ethers';
|
|
4
|
+
import { createHash } from 'crypto';
|
|
2
5
|
|
|
3
|
-
// src/
|
|
6
|
+
// src/client.ts
|
|
4
7
|
|
|
5
8
|
// src/types.ts
|
|
6
9
|
var HavenError = class extends Error {
|
|
@@ -70,11 +73,66 @@ function verifySignature(hash, signature, expectedAddress) {
|
|
|
70
73
|
return false;
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
77
|
+
var X402_IDEMPOTENCY_BUCKET_MS = 3e5;
|
|
78
|
+
function decodeBase64Json(value, label) {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(atob(value));
|
|
81
|
+
} catch {
|
|
82
|
+
throw new Error(`Failed to decode ${label}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function normalizePaymentOption(value) {
|
|
86
|
+
const candidate = value;
|
|
87
|
+
if (!candidate || typeof candidate !== "object" || typeof candidate.scheme !== "string" || typeof candidate.network !== "string" || typeof candidate.asset !== "string" || typeof candidate.payTo !== "string") {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const amount = typeof candidate.amount === "string" ? candidate.amount : typeof candidate.maxAmountRequired === "string" ? candidate.maxAmountRequired : null;
|
|
91
|
+
if (!amount) return null;
|
|
92
|
+
return {
|
|
93
|
+
scheme: candidate.scheme,
|
|
94
|
+
network: candidate.network,
|
|
95
|
+
amount,
|
|
96
|
+
maxAmountRequired: candidate.maxAmountRequired,
|
|
97
|
+
resource: candidate.resource,
|
|
98
|
+
description: candidate.description,
|
|
99
|
+
mimeType: candidate.mimeType,
|
|
100
|
+
asset: candidate.asset,
|
|
101
|
+
payTo: candidate.payTo,
|
|
102
|
+
maxTimeoutSeconds: candidate.maxTimeoutSeconds ?? 30,
|
|
103
|
+
extra: candidate.extra
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function normalizePaymentRequired(value) {
|
|
107
|
+
const candidate = value;
|
|
108
|
+
if (!candidate || typeof candidate !== "object" || typeof candidate.x402Version !== "number" || !Array.isArray(candidate.accepts)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const accepts = candidate.accepts.map((option) => normalizePaymentOption(option)).filter((option) => !!option);
|
|
112
|
+
if (accepts.length === 0) return null;
|
|
113
|
+
const first = accepts[0];
|
|
114
|
+
const resourceUrl = candidate.resource?.url ?? first.resource;
|
|
115
|
+
if (!resourceUrl) return null;
|
|
116
|
+
const resource = {
|
|
117
|
+
url: resourceUrl,
|
|
118
|
+
description: candidate.resource?.description ?? first.description,
|
|
119
|
+
mimeType: candidate.resource?.mimeType ?? first.mimeType
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
x402Version: candidate.x402Version,
|
|
123
|
+
resource,
|
|
124
|
+
accepts,
|
|
125
|
+
error: candidate.error
|
|
126
|
+
};
|
|
127
|
+
}
|
|
75
128
|
var SUPPORTED_X402_NETWORKS = {
|
|
76
129
|
"eip155:100": "Gnosis Chain",
|
|
77
|
-
"eip155:8453": "Base"
|
|
130
|
+
"eip155:8453": "Base",
|
|
131
|
+
"base": "Base"
|
|
132
|
+
};
|
|
133
|
+
var STANDARD_X402_NETWORKS = {
|
|
134
|
+
"eip155:8453": "base",
|
|
135
|
+
"base": "base"
|
|
78
136
|
};
|
|
79
137
|
var GNOSIS_TOKENS = {
|
|
80
138
|
"0x0000000000000000000000000000000000000000": { symbol: "xDAI", decimals: 18 },
|
|
@@ -87,29 +145,41 @@ var BASE_TOKENS = {
|
|
|
87
145
|
};
|
|
88
146
|
var NETWORK_TOKENS = {
|
|
89
147
|
"eip155:100": GNOSIS_TOKENS,
|
|
90
|
-
"eip155:8453": BASE_TOKENS
|
|
148
|
+
"eip155:8453": BASE_TOKENS,
|
|
149
|
+
"base": BASE_TOKENS
|
|
91
150
|
};
|
|
92
151
|
function parsePaymentRequired(response) {
|
|
93
152
|
const v2Header = response.headers.get("PAYMENT-REQUIRED");
|
|
94
153
|
if (v2Header) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
154
|
+
const parsed = normalizePaymentRequired(
|
|
155
|
+
decodeBase64Json(v2Header, "PAYMENT-REQUIRED header")
|
|
156
|
+
);
|
|
157
|
+
if (parsed) return parsed;
|
|
100
158
|
}
|
|
101
159
|
const v1Header = response.headers.get("X-PAYMENT");
|
|
102
160
|
if (v1Header) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
161
|
+
const parsed = normalizePaymentRequired(
|
|
162
|
+
decodeBase64Json(v1Header, "X-PAYMENT header")
|
|
163
|
+
);
|
|
164
|
+
if (parsed) return parsed;
|
|
108
165
|
}
|
|
109
166
|
throw new Error(
|
|
110
167
|
"No x402 payment headers found in 402 response. Expected PAYMENT-REQUIRED (v2) or X-PAYMENT (v1) header."
|
|
111
168
|
);
|
|
112
169
|
}
|
|
170
|
+
async function parsePaymentRequiredResponse(response) {
|
|
171
|
+
try {
|
|
172
|
+
return parsePaymentRequired(response);
|
|
173
|
+
} catch (headerErr) {
|
|
174
|
+
try {
|
|
175
|
+
const body = await response.clone().json();
|
|
176
|
+
const parsed = normalizePaymentRequired(body);
|
|
177
|
+
if (parsed) return parsed;
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
throw headerErr;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
113
183
|
function selectPaymentOption(accepts) {
|
|
114
184
|
if (!accepts || accepts.length === 0) return null;
|
|
115
185
|
for (const opt of accepts) {
|
|
@@ -125,13 +195,61 @@ function selectPaymentOption(accepts) {
|
|
|
125
195
|
}
|
|
126
196
|
return null;
|
|
127
197
|
}
|
|
198
|
+
function selectStandardPaymentOption(accepts) {
|
|
199
|
+
if (!accepts || accepts.length === 0) return null;
|
|
200
|
+
for (const opt of accepts) {
|
|
201
|
+
if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && opt.asset.toLowerCase() === BASE_USDC_ADDRESS) {
|
|
202
|
+
return opt;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
function toStandardPaymentRequirements(paymentRequired, option) {
|
|
208
|
+
const network = STANDARD_X402_NETWORKS[option.network];
|
|
209
|
+
if (!network) {
|
|
210
|
+
throw new Error(`x402 exact payments are not supported on ${option.network}`);
|
|
211
|
+
}
|
|
212
|
+
if (option.scheme !== "exact") {
|
|
213
|
+
throw new Error(`Unsupported x402 scheme: ${option.scheme}`);
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
scheme: "exact",
|
|
217
|
+
network,
|
|
218
|
+
maxAmountRequired: option.maxAmountRequired ?? option.amount,
|
|
219
|
+
resource: option.resource ?? paymentRequired.resource.url,
|
|
220
|
+
description: option.description ?? paymentRequired.resource.description ?? "Haven x402 payment",
|
|
221
|
+
mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
|
|
222
|
+
payTo: option.payTo,
|
|
223
|
+
asset: option.asset,
|
|
224
|
+
maxTimeoutSeconds: option.maxTimeoutSeconds,
|
|
225
|
+
extra: option.extra
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function buildX402IdempotencyKey(paymentRequired, option, now = Date.now()) {
|
|
229
|
+
const bucket = Math.floor(now / X402_IDEMPOTENCY_BUCKET_MS);
|
|
230
|
+
const material = [
|
|
231
|
+
paymentRequired.resource.url,
|
|
232
|
+
paymentRequired.resource.description ?? "",
|
|
233
|
+
option.payTo.toLowerCase(),
|
|
234
|
+
option.asset.toLowerCase(),
|
|
235
|
+
option.amount,
|
|
236
|
+
option.network,
|
|
237
|
+
bucket
|
|
238
|
+
].join("|");
|
|
239
|
+
return `x402:${createHash("sha256").update(material).digest("hex").slice(0, 16)}`;
|
|
240
|
+
}
|
|
128
241
|
function encodePaymentProof(receipt) {
|
|
129
242
|
const payload = {
|
|
130
243
|
x402Version: 2,
|
|
244
|
+
resource: receipt.resourceUrl ? { url: receipt.resourceUrl } : void 0,
|
|
245
|
+
accepted: receipt.accepted,
|
|
131
246
|
payload: {
|
|
247
|
+
type: "haven_tx_hash",
|
|
132
248
|
txHash: receipt.txHash,
|
|
133
249
|
paymentId: receipt.paymentId,
|
|
134
|
-
settledVia: "haven"
|
|
250
|
+
settledVia: "haven",
|
|
251
|
+
payer: receipt.payer,
|
|
252
|
+
chainId: receipt.chainId
|
|
135
253
|
}
|
|
136
254
|
};
|
|
137
255
|
return btoa(JSON.stringify(payload));
|
|
@@ -144,25 +262,35 @@ var CHAIN_EXPLORER_TX = {
|
|
|
144
262
|
8453: "https://basescan.org/tx"
|
|
145
263
|
};
|
|
146
264
|
function buildExplorerUrl(chainId, txHash) {
|
|
147
|
-
const base = CHAIN_EXPLORER_TX[chainId ??
|
|
265
|
+
const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
|
|
148
266
|
return `${base}/${txHash}`;
|
|
149
267
|
}
|
|
150
268
|
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
151
269
|
var DEFAULT_CONFIRMATION_TIMEOUT = 9e4;
|
|
152
270
|
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
271
|
+
function chainIdFromNetwork(network) {
|
|
272
|
+
if (network === "base") return 8453;
|
|
273
|
+
if (!network?.startsWith("eip155:")) return void 0;
|
|
274
|
+
const chainId = Number(network.slice("eip155:".length));
|
|
275
|
+
return Number.isFinite(chainId) ? chainId : void 0;
|
|
276
|
+
}
|
|
153
277
|
var HavenClient = class {
|
|
154
278
|
apiKey;
|
|
155
279
|
delegateKey;
|
|
156
280
|
baseUrl;
|
|
281
|
+
x402Wallet;
|
|
157
282
|
requestTimeout;
|
|
158
283
|
confirmationTimeout;
|
|
159
284
|
pollingInterval;
|
|
285
|
+
inFlightX402 = /* @__PURE__ */ new Map();
|
|
286
|
+
x402ReceiptCache = /* @__PURE__ */ new Map();
|
|
160
287
|
/** Delegate address derived from the private key (if provided) */
|
|
161
288
|
delegateAddress;
|
|
162
289
|
constructor(config) {
|
|
163
290
|
this.apiKey = config.apiKey;
|
|
164
291
|
this.delegateKey = config.delegateKey;
|
|
165
292
|
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
293
|
+
this.x402Wallet = config.x402Wallet;
|
|
166
294
|
this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
167
295
|
this.confirmationTimeout = config.confirmationTimeout ?? DEFAULT_CONFIRMATION_TIMEOUT;
|
|
168
296
|
this.pollingInterval = config.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
|
|
@@ -202,6 +330,13 @@ var HavenClient = class {
|
|
|
202
330
|
amount: request.amount,
|
|
203
331
|
to: request.to
|
|
204
332
|
});
|
|
333
|
+
if (raw.status === "pending_approval") {
|
|
334
|
+
throw new HavenApiError(
|
|
335
|
+
`Payment exceeds the on-chain allowance and was queued for owner approval (payment_id: ${raw.payment_id}).`,
|
|
336
|
+
202,
|
|
337
|
+
raw
|
|
338
|
+
);
|
|
339
|
+
}
|
|
205
340
|
return {
|
|
206
341
|
paymentId: raw.payment_id,
|
|
207
342
|
status: "pending_signature",
|
|
@@ -269,8 +404,9 @@ var HavenClient = class {
|
|
|
269
404
|
/**
|
|
270
405
|
* Authorize an x402 payment.
|
|
271
406
|
*
|
|
272
|
-
* Takes the parsed PaymentRequired from a 402 response, selects a
|
|
273
|
-
*
|
|
407
|
+
* Takes the parsed PaymentRequired from a 402 response, selects a compatible
|
|
408
|
+
* option, funds the delegate wallet through Haven, and returns the standard
|
|
409
|
+
* x402 header that the merchant can verify and settle.
|
|
274
410
|
*
|
|
275
411
|
* Requires `delegateKey` to be set in the client config.
|
|
276
412
|
*/
|
|
@@ -280,23 +416,43 @@ var HavenClient = class {
|
|
|
280
416
|
"delegateKey is required for x402 payments. Pass it in the HavenClient config."
|
|
281
417
|
);
|
|
282
418
|
}
|
|
283
|
-
|
|
419
|
+
if (!this.delegateAddress) {
|
|
420
|
+
throw new HavenSigningError("delegateAddress could not be derived from delegateKey.");
|
|
421
|
+
}
|
|
422
|
+
const option = selectStandardPaymentOption(paymentRequired.accepts);
|
|
284
423
|
if (!option) {
|
|
285
424
|
throw new HavenApiError(
|
|
286
|
-
"No compatible payment option found in x402 requirements. Haven supports
|
|
425
|
+
"No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC.",
|
|
287
426
|
400
|
|
288
427
|
);
|
|
289
428
|
}
|
|
429
|
+
const idempotencyKey = buildX402IdempotencyKey(paymentRequired, option);
|
|
430
|
+
const cached = this.x402ReceiptCache.get(idempotencyKey);
|
|
431
|
+
if (cached && cached.expiresAt > Date.now()) return cached.receipt;
|
|
432
|
+
const inFlight = this.inFlightX402.get(idempotencyKey);
|
|
433
|
+
if (inFlight) return inFlight;
|
|
434
|
+
const promise = this.authorizeStandardX402(paymentRequired, option, idempotencyKey);
|
|
435
|
+
this.inFlightX402.set(idempotencyKey, promise);
|
|
436
|
+
try {
|
|
437
|
+
return await promise;
|
|
438
|
+
} finally {
|
|
439
|
+
this.inFlightX402.delete(idempotencyKey);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async authorizeStandardX402(paymentRequired, option, idempotencyKey) {
|
|
443
|
+
const paymentHeader = await this.createStandardX402Header(paymentRequired, option);
|
|
290
444
|
const raw = await this.post("/x402", {
|
|
291
445
|
url: paymentRequired.resource.url,
|
|
292
|
-
payTo:
|
|
446
|
+
payTo: this.delegateAddress,
|
|
447
|
+
merchantPayTo: option.payTo,
|
|
293
448
|
amount: option.amount,
|
|
294
449
|
asset: option.asset,
|
|
295
450
|
network: option.network,
|
|
296
|
-
description: paymentRequired.resource.description
|
|
451
|
+
description: paymentRequired.resource.description,
|
|
452
|
+
idempotencyKey
|
|
297
453
|
});
|
|
298
454
|
if (raw.success && raw.tx_hash) {
|
|
299
|
-
|
|
455
|
+
const receipt2 = {
|
|
300
456
|
success: true,
|
|
301
457
|
paymentId: raw.payment_id,
|
|
302
458
|
txHash: raw.tx_hash,
|
|
@@ -304,8 +460,15 @@ var HavenClient = class {
|
|
|
304
460
|
amount: raw.amount ?? "",
|
|
305
461
|
to: raw.to ?? "",
|
|
306
462
|
resourceUrl: paymentRequired.resource.url,
|
|
307
|
-
explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : "")
|
|
463
|
+
explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : ""),
|
|
464
|
+
accepted: option,
|
|
465
|
+
paymentHeader,
|
|
466
|
+
merchantTo: raw.merchant_to ?? option.payTo,
|
|
467
|
+
payer: raw.payer ?? raw.safe_address,
|
|
468
|
+
chainId: raw.chain_id ?? chainIdFromNetwork(option.network)
|
|
308
469
|
};
|
|
470
|
+
this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
|
|
471
|
+
return receipt2;
|
|
309
472
|
}
|
|
310
473
|
if (!raw.sign_data?.hash) {
|
|
311
474
|
throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
|
|
@@ -322,7 +485,7 @@ var HavenClient = class {
|
|
|
322
485
|
execResult
|
|
323
486
|
);
|
|
324
487
|
}
|
|
325
|
-
|
|
488
|
+
const receipt = {
|
|
326
489
|
success: true,
|
|
327
490
|
paymentId: raw.payment_id,
|
|
328
491
|
txHash: execResult.tx_hash ?? "",
|
|
@@ -330,8 +493,15 @@ var HavenClient = class {
|
|
|
330
493
|
amount: execResult.amount ?? raw.amount ?? "",
|
|
331
494
|
to: execResult.to ?? raw.to ?? "",
|
|
332
495
|
resourceUrl: paymentRequired.resource.url,
|
|
333
|
-
explorerUrl: execResult.explorer_url ?? (execResult.tx_hash ? buildExplorerUrl(execResult.chain_id, execResult.tx_hash) : "")
|
|
496
|
+
explorerUrl: execResult.explorer_url ?? (execResult.tx_hash ? buildExplorerUrl(execResult.chain_id, execResult.tx_hash) : ""),
|
|
497
|
+
accepted: option,
|
|
498
|
+
paymentHeader,
|
|
499
|
+
merchantTo: option.payTo,
|
|
500
|
+
payer: raw.payer ?? raw.safe_address ?? raw.sign_data?.components.safe,
|
|
501
|
+
chainId: execResult.chain_id ?? raw.chain_id ?? chainIdFromNetwork(option.network)
|
|
334
502
|
};
|
|
503
|
+
this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt);
|
|
504
|
+
return receipt;
|
|
335
505
|
}
|
|
336
506
|
/**
|
|
337
507
|
* Fetch wrapper that automatically handles HTTP 402 responses.
|
|
@@ -347,21 +517,82 @@ var HavenClient = class {
|
|
|
347
517
|
* Requires `delegateKey` to be set in the client config.
|
|
348
518
|
*/
|
|
349
519
|
async fetch(url, init) {
|
|
350
|
-
const
|
|
520
|
+
const initialInit = this.withX402Wallet(init, this.x402PayerAddress());
|
|
521
|
+
const response = await globalThis.fetch(url, initialInit);
|
|
351
522
|
if (response.status !== 402) return response;
|
|
352
523
|
let paymentRequired;
|
|
353
524
|
try {
|
|
354
|
-
paymentRequired =
|
|
525
|
+
paymentRequired = await parsePaymentRequiredResponse(response);
|
|
355
526
|
} catch {
|
|
356
527
|
return response;
|
|
357
528
|
}
|
|
358
529
|
const receipt = await this.authorizeX402(paymentRequired);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
530
|
+
if (!receipt.accepted) {
|
|
531
|
+
throw new HavenApiError("No accepted x402 option was recorded for payment retry", 500);
|
|
532
|
+
}
|
|
533
|
+
if (!receipt.paymentHeader) {
|
|
534
|
+
throw new HavenApiError("No x402 payment header was returned for payment retry", 500);
|
|
535
|
+
}
|
|
536
|
+
const retryHeaders = new Headers(initialInit?.headers);
|
|
537
|
+
retryHeaders.set("X-PAYMENT", receipt.paymentHeader);
|
|
538
|
+
const retryResponse = await globalThis.fetch(url, {
|
|
539
|
+
...initialInit,
|
|
363
540
|
headers: retryHeaders
|
|
364
541
|
});
|
|
542
|
+
if (retryResponse.status === 402) {
|
|
543
|
+
throw new HavenApiError(
|
|
544
|
+
"x402 retry was rejected after Haven funded the delegate wallet; reconciliation may be required.",
|
|
545
|
+
402,
|
|
546
|
+
{
|
|
547
|
+
marker: "x402_retry_rejected_after_funding",
|
|
548
|
+
payment_id: receipt.paymentId,
|
|
549
|
+
tx_hash: receipt.txHash,
|
|
550
|
+
resource_url: receipt.resourceUrl,
|
|
551
|
+
merchant_to: receipt.merchantTo,
|
|
552
|
+
delegate_to: receipt.to
|
|
553
|
+
}
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return retryResponse;
|
|
557
|
+
}
|
|
558
|
+
async createStandardX402Header(paymentRequired, option) {
|
|
559
|
+
if (!this.delegateKey) {
|
|
560
|
+
throw new HavenSigningError("delegateKey is required to sign x402 payment headers.");
|
|
561
|
+
}
|
|
562
|
+
const account = privateKeyToAccount(this.delegateKey);
|
|
563
|
+
const requirements = toStandardPaymentRequirements(paymentRequired, option);
|
|
564
|
+
const header = await exact.evm.createPaymentHeader(
|
|
565
|
+
account,
|
|
566
|
+
paymentRequired.x402Version,
|
|
567
|
+
requirements
|
|
568
|
+
);
|
|
569
|
+
if (paymentRequired.x402Version < 2) return header;
|
|
570
|
+
const payment = decodeBase64Json2(header);
|
|
571
|
+
return btoa(JSON.stringify({
|
|
572
|
+
x402Version: paymentRequired.x402Version,
|
|
573
|
+
accepted: option,
|
|
574
|
+
payload: payment.payload
|
|
575
|
+
}));
|
|
576
|
+
}
|
|
577
|
+
cacheX402Receipt(idempotencyKey, paymentHeader, receipt) {
|
|
578
|
+
const expiresAt = getPaymentHeaderValidBefore(paymentHeader);
|
|
579
|
+
if (expiresAt > Date.now()) {
|
|
580
|
+
this.x402ReceiptCache.set(idempotencyKey, { expiresAt, receipt });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
x402PayerAddress() {
|
|
584
|
+
return this.delegateAddress ?? this.x402Wallet;
|
|
585
|
+
}
|
|
586
|
+
withX402Wallet(init, wallet = this.x402PayerAddress()) {
|
|
587
|
+
if (!wallet) return init;
|
|
588
|
+
const headers = new Headers(init?.headers);
|
|
589
|
+
if (!headers.has("x402-wallet")) {
|
|
590
|
+
headers.set("x402-wallet", wallet);
|
|
591
|
+
}
|
|
592
|
+
return {
|
|
593
|
+
...init,
|
|
594
|
+
headers
|
|
595
|
+
};
|
|
365
596
|
}
|
|
366
597
|
// ── Tool Execution (for agent frameworks) ────────────────────────
|
|
367
598
|
/**
|
|
@@ -424,7 +655,11 @@ var HavenClient = class {
|
|
|
424
655
|
amount: receipt.amount,
|
|
425
656
|
to: receipt.to,
|
|
426
657
|
resource_url: receipt.resourceUrl,
|
|
427
|
-
explorer_url: receipt.explorerUrl
|
|
658
|
+
explorer_url: receipt.explorerUrl,
|
|
659
|
+
payment_header: receipt.paymentHeader,
|
|
660
|
+
merchant_to: receipt.merchantTo,
|
|
661
|
+
payer: receipt.payer,
|
|
662
|
+
chain_id: receipt.chainId
|
|
428
663
|
};
|
|
429
664
|
} catch (err) {
|
|
430
665
|
return {
|
|
@@ -510,6 +745,21 @@ var HavenClient = class {
|
|
|
510
745
|
function sleep(ms) {
|
|
511
746
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
512
747
|
}
|
|
748
|
+
function getPaymentHeaderValidBefore(paymentHeader) {
|
|
749
|
+
try {
|
|
750
|
+
const payment = decodeBase64Json2(
|
|
751
|
+
paymentHeader
|
|
752
|
+
);
|
|
753
|
+
const payload = payment.payload;
|
|
754
|
+
const validBeforeSeconds = Number(payload.authorization?.validBefore);
|
|
755
|
+
if (Number.isFinite(validBeforeSeconds)) return validBeforeSeconds * 1e3;
|
|
756
|
+
} catch {
|
|
757
|
+
}
|
|
758
|
+
return 0;
|
|
759
|
+
}
|
|
760
|
+
function decodeBase64Json2(value) {
|
|
761
|
+
return JSON.parse(atob(value));
|
|
762
|
+
}
|
|
513
763
|
|
|
514
764
|
// src/tools.ts
|
|
515
765
|
var makePaymentSchema = {
|
|
@@ -576,7 +826,7 @@ var authorizeX402Schema = {
|
|
|
576
826
|
};
|
|
577
827
|
var MAKE_PAYMENT_DESCRIPTION = "Send a payment from the Haven-managed Safe wallet. The payment will be validated against the agent's on-chain spending policy. Gnosis Chain tokens: EURe, USDC.e, xDAI. Base tokens: USDC, ETH.";
|
|
578
828
|
var GET_STATUS_DESCRIPTION = "Check the status of a previously initiated payment. Returns the current status, transaction hash (if confirmed), and payment details.";
|
|
579
|
-
var AUTHORIZE_X402_DESCRIPTION = "Authorize payment for an HTTP 402 (Payment Required) response. When a paid API returns 402 with x402 payment requirements, use this tool to
|
|
829
|
+
var AUTHORIZE_X402_DESCRIPTION = "Authorize payment for an HTTP 402 (Payment Required) response. When a paid API returns 402 with x402 payment requirements, use this tool to fund the agent wallet and get a merchant payment header. Haven evaluates the payment against policy before moving funds from the Haven wallet. Use the returned payment_header as the X-PAYMENT header on the retry request.";
|
|
580
830
|
function claudeTools() {
|
|
581
831
|
return [
|
|
582
832
|
{
|
|
@@ -631,6 +881,6 @@ var havenTools = {
|
|
|
631
881
|
openai: openaiTools
|
|
632
882
|
};
|
|
633
883
|
|
|
634
|
-
export { HavenApiError, HavenClient, HavenError, HavenSigningError, HavenTimeoutError, addressFromKey, encodePaymentProof, havenTools, parsePaymentRequired, selectPaymentOption, signHash, verifySignature };
|
|
884
|
+
export { HavenApiError, HavenClient, HavenError, HavenSigningError, HavenTimeoutError, addressFromKey, encodePaymentProof, havenTools, parsePaymentRequired, parsePaymentRequiredResponse, selectPaymentOption, signHash, verifySignature };
|
|
635
885
|
//# sourceMappingURL=index.js.map
|
|
636
886
|
//# sourceMappingURL=index.js.map
|