@haven_ai/sdk 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21,13 +21,30 @@ var HavenError = class extends Error {
21
21
  paymentId;
22
22
  };
23
23
  var HavenApiError = class extends HavenError {
24
- constructor(message, statusCode, body) {
25
- super(message, "API_ERROR", statusCode);
24
+ constructor(message, statusCode, body, paymentId) {
25
+ super(message, "API_ERROR", statusCode, paymentId);
26
26
  this.body = body;
27
27
  this.name = "HavenApiError";
28
28
  }
29
29
  body;
30
30
  };
31
+ var HavenPaymentStateError = class extends HavenApiError {
32
+ constructor(message, statusCode, state, body) {
33
+ super(message, statusCode, body, state.paymentId);
34
+ this.state = state;
35
+ this.name = "HavenPaymentStateError";
36
+ }
37
+ state;
38
+ get status() {
39
+ return this.state.status;
40
+ }
41
+ get phase() {
42
+ return this.state.phase;
43
+ }
44
+ get nextAction() {
45
+ return this.state.nextAction;
46
+ }
47
+ };
31
48
  var HavenSigningError = class extends HavenError {
32
49
  constructor(message) {
33
50
  super(message, "SIGNING_ERROR");
@@ -256,6 +273,80 @@ function encodePaymentProof(receipt) {
256
273
  };
257
274
  return btoa(JSON.stringify(payload));
258
275
  }
276
+ function decodeBase64Json2(value, label) {
277
+ try {
278
+ return JSON.parse(atob(value));
279
+ } catch {
280
+ throw new Error(`Failed to decode ${label}`);
281
+ }
282
+ }
283
+ function normalizeChallenge(value) {
284
+ const candidate = value;
285
+ if (!candidate || typeof candidate !== "object" || candidate.rail !== "mpp_demo" || typeof candidate.version !== "string" || typeof candidate.challengeId !== "string" || typeof candidate.resource !== "string" || typeof candidate.description !== "string" || // TODO: relax these checks when non-demo machine payment rails are added.
286
+ candidate.network?.chainId !== 8453 || candidate.network?.name !== "base" || candidate.asset?.symbol !== "USDC" || typeof candidate.asset?.address !== "string" || candidate.asset.decimals !== 6 || typeof candidate.amount?.display !== "string" || typeof candidate.amount?.atomic !== "string" || typeof candidate.recipient !== "string" || typeof candidate.expiresAt !== "string") {
287
+ return null;
288
+ }
289
+ return {
290
+ rail: candidate.rail,
291
+ version: candidate.version,
292
+ challengeId: candidate.challengeId,
293
+ resource: candidate.resource,
294
+ description: candidate.description,
295
+ network: candidate.network,
296
+ asset: candidate.asset,
297
+ amount: candidate.amount,
298
+ recipient: candidate.recipient,
299
+ expiresAt: candidate.expiresAt,
300
+ metadata: candidate.metadata
301
+ };
302
+ }
303
+ function parseMachinePaymentChallenge(response) {
304
+ const header = response.headers.get("MACHINE-PAYMENT-CHALLENGE");
305
+ if (!header) {
306
+ throw new Error("No MACHINE-PAYMENT-CHALLENGE header found in 402 response.");
307
+ }
308
+ const parsed = normalizeChallenge(
309
+ decodeBase64Json2(header, "MACHINE-PAYMENT-CHALLENGE header")
310
+ );
311
+ if (!parsed) throw new Error("Invalid machine payment challenge");
312
+ return parsed;
313
+ }
314
+ async function parseMachinePaymentChallengeResponse(response) {
315
+ try {
316
+ return parseMachinePaymentChallenge(response);
317
+ } catch (headerErr) {
318
+ try {
319
+ const body = await response.clone().json();
320
+ const parsed = normalizeChallenge(body.challenge);
321
+ if (parsed) return parsed;
322
+ } catch {
323
+ }
324
+ throw headerErr;
325
+ }
326
+ }
327
+ function buildMachinePaymentIdempotencyKey(challenge) {
328
+ const material = [
329
+ challenge.rail,
330
+ challenge.challengeId,
331
+ challenge.resource,
332
+ challenge.recipient.toLowerCase(),
333
+ challenge.asset.address.toLowerCase(),
334
+ challenge.amount.atomic,
335
+ challenge.network.chainId
336
+ ].join("|");
337
+ return `${challenge.rail}:${crypto.createHash("sha256").update(material).digest("hex").slice(0, 16)}`;
338
+ }
339
+ function encodeMachinePaymentProof(receipt) {
340
+ return btoa(JSON.stringify({
341
+ rail: receipt.rail,
342
+ challengeId: receipt.challengeId,
343
+ paymentId: receipt.paymentId,
344
+ txHash: receipt.txHash,
345
+ settledVia: "haven",
346
+ payer: receipt.payer,
347
+ chainId: receipt.chainId
348
+ }));
349
+ }
259
350
 
260
351
  // src/client.ts
261
352
  var DEFAULT_BASE_URL = "http://localhost:3001";
@@ -267,15 +358,93 @@ function buildExplorerUrl(chainId, txHash) {
267
358
  const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
268
359
  return `${base}/${txHash}`;
269
360
  }
361
+ function explorerUrlOrEmpty(chainId, txHash) {
362
+ return txHash ? buildExplorerUrl(chainId, txHash) : "";
363
+ }
270
364
  var DEFAULT_REQUEST_TIMEOUT = 3e4;
271
365
  var DEFAULT_CONFIRMATION_TIMEOUT = 9e4;
272
366
  var DEFAULT_POLLING_INTERVAL = 3e3;
367
+ var PAYMENT_STATE_STATUS_CODES = {
368
+ pending: 202,
369
+ pending_approval: 202,
370
+ approved: 202,
371
+ proposed: 202,
372
+ executed: 200,
373
+ pending_signature: 409,
374
+ submitted: 409,
375
+ expired: 410,
376
+ failed: 502,
377
+ rejected: 409
378
+ };
273
379
  function chainIdFromNetwork(network) {
274
380
  if (network === "base") return 8453;
275
381
  if (!network?.startsWith("eip155:")) return void 0;
276
382
  const chainId = Number(network.slice("eip155:".length));
277
383
  return Number.isFinite(chainId) ? chainId : void 0;
278
384
  }
385
+ function phaseForStatus(status) {
386
+ if (status === "pending_signature") return "agent_signature_required";
387
+ if (status === "submitted") return "payment_submitted";
388
+ if (status === "confirmed") return "payment_confirmed";
389
+ if (status === "pending" || status === "pending_approval") return "user_approval_required";
390
+ if (status === "approved") return "user_execution_required";
391
+ if (status === "proposed") return "waiting_for_additional_approvals";
392
+ if (status === "executed") return "funding_sent";
393
+ if (status === "rejected") return "rejected";
394
+ if (status === "expired") return "expired";
395
+ if (status === "failed") return "failed";
396
+ return null;
397
+ }
398
+ function nextActionForStatus(status) {
399
+ if (status === "pending_signature") return "sign_and_submit_payment";
400
+ if (status === "submitted") return "check_status_later";
401
+ if (status === "confirmed") return "none";
402
+ if (status === "pending" || status === "pending_approval") return "wait_for_user_approval";
403
+ if (status === "approved") return "wait_for_user_to_complete_payment";
404
+ if (status === "proposed") return "wait_for_user_approval";
405
+ if (status === "executed") return "retry_original_x402_request";
406
+ if (status === "rejected") return "stop_and_tell_user";
407
+ if (status === "expired") return "request_again_if_user_still_wants_it";
408
+ if (status === "failed") return "stop_and_tell_user";
409
+ return null;
410
+ }
411
+ function messageForState(label, status, paymentId, nextAction) {
412
+ if (status === "pending" || status === "pending_approval") {
413
+ return `${label} is above the remaining agent budget and is waiting for user approval in Haven (payment_id: ${paymentId}).`;
414
+ }
415
+ if (status === "executed") {
416
+ return "The user completed the funding payment. Retry the original x402 request.";
417
+ }
418
+ if (status === "rejected") {
419
+ return `The user rejected this payment request (payment_id: ${paymentId}).`;
420
+ }
421
+ if (status === "expired") {
422
+ return `This payment request expired (payment_id: ${paymentId}).`;
423
+ }
424
+ return `${label} is ${status}; next_action=${nextAction} (payment_id: ${paymentId}).`;
425
+ }
426
+ function sameAddress(a, b) {
427
+ return Boolean(a && b && a.toLowerCase() === b.toLowerCase());
428
+ }
429
+ function decimalFromUsdcAtomic(value) {
430
+ const amount = BigInt(value);
431
+ const whole = amount / 1000000n;
432
+ const fraction = (amount % 1000000n).toString().padStart(6, "0").replace(/0+$/, "");
433
+ return fraction ? `${whole}.${fraction}` : whole.toString();
434
+ }
435
+ function normalizeDecimal(value) {
436
+ if (!value.includes(".")) return value.replace(/^0+(?=\d)/, "") || "0";
437
+ const [whole, fraction = ""] = value.split(".");
438
+ const normalizedWhole = whole.replace(/^0+(?=\d)/, "") || "0";
439
+ const normalizedFraction = fraction.replace(/0+$/, "");
440
+ return normalizedFraction ? `${normalizedWhole}.${normalizedFraction}` : normalizedWhole;
441
+ }
442
+ function parseMerchantSettlement(header) {
443
+ if (!header) return {};
444
+ const parsed = parseProtocolReceiptHeader(header);
445
+ const tx = typeof parsed?.transaction === "string" ? parsed.transaction : typeof parsed?.txHash === "string" ? parsed.txHash : typeof parsed?.tx_hash === "string" ? parsed.tx_hash : null;
446
+ return { settlementTxHash: tx };
447
+ }
279
448
  var HavenClient = class {
280
449
  apiKey;
281
450
  delegateKey;
@@ -286,6 +455,7 @@ var HavenClient = class {
286
455
  pollingInterval;
287
456
  inFlightX402 = /* @__PURE__ */ new Map();
288
457
  x402ReceiptCache = /* @__PURE__ */ new Map();
458
+ inFlightMachinePayments = /* @__PURE__ */ new Map();
289
459
  /** Delegate address derived from the private key (if provided) */
290
460
  delegateAddress;
291
461
  constructor(config) {
@@ -333,11 +503,7 @@ var HavenClient = class {
333
503
  to: request.to
334
504
  });
335
505
  if (raw.status === "pending_approval") {
336
- throw new HavenApiError(
337
- `Payment exceeds the on-chain allowance and was queued for owner approval (payment_id: ${raw.payment_id}).`,
338
- 202,
339
- raw
340
- );
506
+ this.throwPaymentStateError("Payment", raw);
341
507
  }
342
508
  return {
343
509
  paymentId: raw.payment_id,
@@ -388,6 +554,16 @@ var HavenClient = class {
388
554
  const raw = await this.get(`/payments/${paymentId}`);
389
555
  return this.mapPaymentResult(raw);
390
556
  }
557
+ /**
558
+ * Get agent-actionable status for a payment intent or approval request.
559
+ *
560
+ * Use this for IDs returned by agent tools and machine-payment/x402 flows.
561
+ * `getPayment()` remains available for payment-intent-only integrations.
562
+ */
563
+ async getPaymentStatus(paymentId) {
564
+ const raw = await this.get(`/machine-payments/${paymentId}/status`);
565
+ return this.mapPaymentStatusResult(raw);
566
+ }
391
567
  /**
392
568
  * Poll until a payment reaches a terminal status (confirmed, failed, expired).
393
569
  */
@@ -412,7 +588,7 @@ var HavenClient = class {
412
588
  *
413
589
  * Requires `delegateKey` to be set in the client config.
414
590
  */
415
- async authorizeX402(paymentRequired) {
591
+ async authorizeX402(paymentRequired, options = {}) {
416
592
  if (!this.delegateKey) {
417
593
  throw new HavenSigningError(
418
594
  "delegateKey is required for x402 payments. Pass it in the HavenClient config."
@@ -428,7 +604,7 @@ var HavenClient = class {
428
604
  400
429
605
  );
430
606
  }
431
- const idempotencyKey = buildX402IdempotencyKey(paymentRequired, option);
607
+ const idempotencyKey = options.idempotencyKey ?? buildX402IdempotencyKey(paymentRequired, option);
432
608
  const cached = this.x402ReceiptCache.get(idempotencyKey);
433
609
  if (cached && cached.expiresAt > Date.now()) return cached.receipt;
434
610
  const inFlight = this.inFlightX402.get(idempotencyKey);
@@ -454,24 +630,17 @@ var HavenClient = class {
454
630
  idempotencyKey
455
631
  });
456
632
  if (raw.success && raw.tx_hash) {
457
- const receipt2 = {
458
- success: true,
459
- paymentId: raw.payment_id,
460
- txHash: raw.tx_hash,
461
- token: raw.token ?? "",
462
- amount: raw.amount ?? "",
463
- to: raw.to ?? "",
464
- resourceUrl: paymentRequired.resource.url,
465
- explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : ""),
466
- accepted: option,
467
- paymentHeader,
468
- merchantTo: raw.merchant_to ?? option.payTo,
469
- payer: raw.payer ?? raw.safe_address,
470
- chainId: raw.chain_id ?? chainIdFromNetwork(option.network)
471
- };
633
+ const receipt2 = this.mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw);
472
634
  this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
473
635
  return receipt2;
474
636
  }
637
+ const state = this.paymentStateFromRaw("x402 payment", raw);
638
+ if (state?.nextAction === "retry_original_x402_request") {
639
+ const receipt2 = this.mapX402ReceiptFromStatus(paymentRequired, option, paymentHeader, state);
640
+ this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
641
+ return receipt2;
642
+ }
643
+ this.throwIfNonSignableAuthorizationState("x402 payment", raw);
475
644
  if (!raw.sign_data?.hash) {
476
645
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
477
646
  }
@@ -481,30 +650,55 @@ var HavenClient = class {
481
650
  { signature: sig }
482
651
  );
483
652
  if (execResult.status !== "confirmed") {
653
+ this.throwPaymentStateError("x402 payment", execResult);
654
+ }
655
+ const receipt = this.mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult);
656
+ this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt);
657
+ return receipt;
658
+ }
659
+ async resumeAuthorizedX402(input) {
660
+ if (!this.delegateKey) {
661
+ throw new HavenSigningError(
662
+ "delegateKey is required for x402 payments. Pass it in the HavenClient config."
663
+ );
664
+ }
665
+ if (!this.delegateAddress) {
666
+ throw new HavenSigningError("delegateAddress could not be derived from delegateKey.");
667
+ }
668
+ const option = selectStandardPaymentOption(input.paymentRequired.accepts);
669
+ if (!option) {
484
670
  throw new HavenApiError(
485
- execResult.error ?? `x402 payment ${execResult.status}`,
486
- 502,
487
- execResult
671
+ "No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC.",
672
+ 400
488
673
  );
489
674
  }
490
- const receipt = {
491
- success: true,
492
- paymentId: raw.payment_id,
493
- txHash: execResult.tx_hash ?? "",
494
- token: execResult.token ?? raw.token ?? "",
495
- amount: execResult.amount ?? raw.amount ?? "",
496
- to: execResult.to ?? raw.to ?? "",
497
- resourceUrl: paymentRequired.resource.url,
498
- explorerUrl: execResult.explorer_url ?? (execResult.tx_hash ? buildExplorerUrl(execResult.chain_id, execResult.tx_hash) : ""),
499
- accepted: option,
500
- paymentHeader,
501
- merchantTo: option.payTo,
502
- payer: raw.payer ?? raw.safe_address ?? raw.sign_data?.components.safe,
503
- chainId: execResult.chain_id ?? raw.chain_id ?? chainIdFromNetwork(option.network)
504
- };
675
+ const idempotencyKey = input.idempotencyKey ?? buildX402IdempotencyKey(input.paymentRequired, option);
676
+ const cached = this.x402ReceiptCache.get(idempotencyKey);
677
+ if (cached && cached.expiresAt > Date.now()) return cached.receipt;
678
+ const status = await this.getPaymentStatus(input.paymentId);
679
+ this.assertCanResumeX402(status, input.paymentRequired, option);
680
+ const paymentHeader = await this.createStandardX402Header(input.paymentRequired, option);
681
+ const receipt = this.mapX402ReceiptFromStatus(input.paymentRequired, option, paymentHeader, status);
505
682
  this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt);
506
683
  return receipt;
507
684
  }
685
+ async resumeX402Payment(input) {
686
+ const initialInit = this.withX402Wallet(input.init, this.x402PayerAddress());
687
+ let paymentRequired = input.paymentRequired;
688
+ if (!paymentRequired) {
689
+ const response = await globalThis.fetch(input.url, initialInit);
690
+ if (response.status !== 402) {
691
+ throw new HavenApiError("Expected the original x402 request to return HTTP 402 before resuming.", 400);
692
+ }
693
+ paymentRequired = await parsePaymentRequiredResponse(response);
694
+ }
695
+ const receipt = await this.resumeAuthorizedX402({
696
+ paymentId: input.paymentId,
697
+ paymentRequired,
698
+ idempotencyKey: input.idempotencyKey
699
+ });
700
+ return this.retryX402Request(input.url, initialInit, paymentRequired, receipt);
701
+ }
508
702
  /**
509
703
  * Fetch wrapper that automatically handles HTTP 402 responses.
510
704
  *
@@ -518,17 +712,31 @@ var HavenClient = class {
518
712
  *
519
713
  * Requires `delegateKey` to be set in the client config.
520
714
  */
521
- async fetch(url, init) {
715
+ async fetch(url, init, options = {}) {
522
716
  const initialInit = this.withX402Wallet(init, this.x402PayerAddress());
523
717
  const response = await globalThis.fetch(url, initialInit);
524
718
  if (response.status !== 402) return response;
719
+ const machineChallengeHeader = response.headers.get("MACHINE-PAYMENT-CHALLENGE");
720
+ if (machineChallengeHeader) {
721
+ const challenge = await parseMachinePaymentChallengeResponse(response);
722
+ return this.fetchWithMachinePayment(url, initialInit, challenge);
723
+ }
525
724
  let paymentRequired;
526
725
  try {
527
726
  paymentRequired = await parsePaymentRequiredResponse(response);
528
727
  } catch {
529
- return response;
728
+ let challenge;
729
+ try {
730
+ challenge = await parseMachinePaymentChallengeResponse(response);
731
+ } catch {
732
+ return response;
733
+ }
734
+ return this.fetchWithMachinePayment(url, initialInit, challenge);
530
735
  }
531
- const receipt = await this.authorizeX402(paymentRequired);
736
+ const receipt = await this.authorizeX402(paymentRequired, options);
737
+ return this.retryX402Request(url, initialInit, paymentRequired, receipt);
738
+ }
739
+ async retryX402Request(url, initialInit, paymentRequired, receipt) {
532
740
  if (!receipt.accepted) {
533
741
  throw new HavenApiError("No accepted x402 option was recorded for payment retry", 500);
534
742
  }
@@ -542,6 +750,17 @@ var HavenClient = class {
542
750
  headers: retryHeaders
543
751
  });
544
752
  if (retryResponse.status === 402) {
753
+ await this.recordMerchantRetryRejected({
754
+ rail: "x402",
755
+ paymentId: receipt.paymentId,
756
+ txHash: receipt.txHash,
757
+ resourceUrl: receipt.resourceUrl,
758
+ retryResponse,
759
+ details: {
760
+ merchant_to: receipt.merchantTo,
761
+ delegate_to: receipt.to
762
+ }
763
+ });
545
764
  throw new HavenApiError(
546
765
  "x402 retry was rejected after Haven funded the delegate wallet; reconciliation may be required.",
547
766
  402,
@@ -555,8 +774,260 @@ var HavenClient = class {
555
774
  }
556
775
  );
557
776
  }
777
+ const merchantSettlement = parseMerchantSettlement(retryResponse.headers.get("PAYMENT-RESPONSE"));
778
+ if (receipt.merchant && merchantSettlement.settlementTxHash) {
779
+ receipt.merchant.settlementTxHash = merchantSettlement.settlementTxHash;
780
+ receipt.merchant.settlementExplorerUrl = buildExplorerUrl(
781
+ receipt.chainId,
782
+ merchantSettlement.settlementTxHash
783
+ );
784
+ }
785
+ await this.reportMachinePaymentEvidence({
786
+ paymentId: receipt.paymentId,
787
+ rail: "x402",
788
+ txHash: receipt.txHash,
789
+ resourceUrl: receipt.resourceUrl,
790
+ merchantStatus: retryResponse.status,
791
+ challengePayload: paymentRequired,
792
+ selectedPayment: receipt.accepted,
793
+ paymentProofHeaderName: "X-PAYMENT",
794
+ paymentProofHeader: receipt.paymentHeader,
795
+ protocolReceiptHeaderName: "PAYMENT-RESPONSE",
796
+ protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
797
+ });
798
+ return retryResponse;
799
+ }
800
+ async authorizeMachinePayment(challenge) {
801
+ if (!this.delegateKey) {
802
+ throw new HavenSigningError(
803
+ "delegateKey is required for machine payments. Pass it in the HavenClient config."
804
+ );
805
+ }
806
+ if (challenge.rail !== "mpp_demo") {
807
+ throw new HavenApiError(`Unsupported machine payment rail: ${challenge.rail}`, 400);
808
+ }
809
+ const idempotencyKey = buildMachinePaymentIdempotencyKey(challenge);
810
+ const inFlight = this.inFlightMachinePayments.get(idempotencyKey);
811
+ if (inFlight) return inFlight;
812
+ const promise = this.authorizeMppDemoPayment(challenge, idempotencyKey);
813
+ this.inFlightMachinePayments.set(idempotencyKey, promise);
814
+ try {
815
+ return await promise;
816
+ } finally {
817
+ this.inFlightMachinePayments.delete(idempotencyKey);
818
+ }
819
+ }
820
+ async authorizeMppDemoPayment(challenge, idempotencyKey) {
821
+ const raw = await this.post(
822
+ "/machine-payments/authorize",
823
+ { challenge, idempotencyKey }
824
+ );
825
+ if (raw.success && raw.tx_hash) {
826
+ return this.mapMachinePaymentReceipt(challenge, raw, raw.tx_hash);
827
+ }
828
+ this.throwIfNonSignableAuthorizationState("Machine payment", raw);
829
+ if (!raw.sign_data?.hash) {
830
+ throw new HavenApiError("No sign_hash returned from machine payment authorization", 500, raw);
831
+ }
832
+ const sig = signHash(this.delegateKey, raw.sign_data.hash);
833
+ const execResult = await this.post(
834
+ `/payments/${raw.payment_id}/sign`,
835
+ { signature: sig }
836
+ );
837
+ if (execResult.status !== "confirmed" || !execResult.tx_hash) {
838
+ this.throwPaymentStateError("Machine payment", execResult);
839
+ }
840
+ return this.mapMachinePaymentReceipt(challenge, raw, execResult.tx_hash, execResult);
841
+ }
842
+ async fetchWithMachinePayment(url, initialInit, challenge) {
843
+ const receipt = await this.authorizeMachinePayment(challenge);
844
+ const retryHeaders = new Headers(initialInit?.headers);
845
+ retryHeaders.set("MACHINE-PAYMENT-PROOF", receipt.proofHeader);
846
+ const retryResponse = await globalThis.fetch(url, {
847
+ ...initialInit,
848
+ headers: retryHeaders
849
+ });
850
+ if (retryResponse.status === 402) {
851
+ await this.recordMerchantRetryRejected({
852
+ rail: receipt.rail,
853
+ paymentId: receipt.paymentId,
854
+ txHash: receipt.txHash,
855
+ resourceUrl: receipt.resourceUrl,
856
+ retryResponse,
857
+ details: {
858
+ challenge_id: receipt.challengeId
859
+ }
860
+ });
861
+ throw new HavenApiError(
862
+ "Machine payment retry was rejected after Haven sent the payment.",
863
+ 402,
864
+ {
865
+ marker: "machine_payment_retry_rejected_after_payment",
866
+ payment_id: receipt.paymentId,
867
+ tx_hash: receipt.txHash,
868
+ resource_url: receipt.resourceUrl,
869
+ rail: receipt.rail
870
+ }
871
+ );
872
+ }
873
+ await this.reportMachinePaymentEvidence({
874
+ paymentId: receipt.paymentId,
875
+ rail: receipt.rail,
876
+ txHash: receipt.txHash,
877
+ resourceUrl: receipt.resourceUrl,
878
+ merchantStatus: retryResponse.status,
879
+ challengePayload: challenge,
880
+ paymentProofHeaderName: "MACHINE-PAYMENT-PROOF",
881
+ paymentProofHeader: receipt.proofHeader,
882
+ protocolReceiptHeaderName: retryResponse.headers.has("Payment-Receipt") ? "Payment-Receipt" : retryResponse.headers.has("MACHINE-PAYMENT-RESPONSE") ? "MACHINE-PAYMENT-RESPONSE" : void 0,
883
+ protocolReceiptHeader: retryResponse.headers.get("Payment-Receipt") ?? retryResponse.headers.get("MACHINE-PAYMENT-RESPONSE") ?? void 0
884
+ });
558
885
  return retryResponse;
559
886
  }
887
+ assertCanResumeX402(status, paymentRequired, option) {
888
+ if (status.rail !== "x402") {
889
+ throw new HavenPaymentStateError(
890
+ `Payment ${status.paymentId} is ${status.rail}, not x402.`,
891
+ 409,
892
+ status
893
+ );
894
+ }
895
+ if (status.nextAction !== "retry_original_x402_request") {
896
+ throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
897
+ }
898
+ if (!status.txHash) {
899
+ throw new HavenApiError(
900
+ `x402 payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
901
+ 502,
902
+ status,
903
+ status.paymentId
904
+ );
905
+ }
906
+ if (status.resourceUrl && status.resourceUrl !== paymentRequired.resource.url) {
907
+ throw new HavenApiError(
908
+ "x402 resume request does not match the approved resource URL.",
909
+ 409,
910
+ { status, paymentRequired },
911
+ status.paymentId
912
+ );
913
+ }
914
+ if (status.merchantAddress && !sameAddress(status.merchantAddress, option.payTo)) {
915
+ throw new HavenApiError(
916
+ "x402 resume request does not match the approved merchant.",
917
+ 409,
918
+ { status, selectedPayment: option },
919
+ status.paymentId
920
+ );
921
+ }
922
+ const optionChainId = chainIdFromNetwork(option.network);
923
+ if (status.chainId && optionChainId && status.chainId !== optionChainId) {
924
+ throw new HavenApiError(
925
+ "x402 resume request does not match the approved network.",
926
+ 409,
927
+ { status, selectedPayment: option },
928
+ status.paymentId
929
+ );
930
+ }
931
+ if (status.token && status.token !== "USDC") {
932
+ throw new HavenApiError(
933
+ "x402 resume request does not match the approved token.",
934
+ 409,
935
+ { status, selectedPayment: option },
936
+ status.paymentId
937
+ );
938
+ }
939
+ const approvedAmount = status.amount ? normalizeDecimal(status.amount) : "";
940
+ const requestedAmount = normalizeDecimal(decimalFromUsdcAtomic(option.amount));
941
+ if (approvedAmount && approvedAmount !== requestedAmount) {
942
+ throw new HavenApiError(
943
+ "x402 resume request does not match the approved amount.",
944
+ 409,
945
+ { status, selectedPayment: option },
946
+ status.paymentId
947
+ );
948
+ }
949
+ }
950
+ mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult) {
951
+ const txHash = execResult?.tx_hash ?? raw.tx_hash ?? "";
952
+ const chainId = execResult?.chain_id ?? raw.chain_id ?? chainIdFromNetwork(option.network);
953
+ const token = execResult?.token ?? raw.token ?? "USDC";
954
+ const amount = execResult?.amount ?? raw.amount ?? decimalFromUsdcAtomic(option.amount);
955
+ const to = execResult?.to ?? raw.to ?? this.delegateAddress ?? "";
956
+ const explorerUrl = execResult?.explorer_url ?? raw.explorer_url ?? explorerUrlOrEmpty(chainId, txHash);
957
+ const merchantTo = execResult?.merchant_to ?? raw.merchant_to ?? option.payTo;
958
+ const payer = raw.payer ?? raw.safe_address ?? raw.sign_data?.components.safe;
959
+ return this.buildX402Receipt({
960
+ paymentId: raw.payment_id,
961
+ txHash,
962
+ token,
963
+ amount,
964
+ to,
965
+ resourceUrl: paymentRequired.resource.url,
966
+ explorerUrl,
967
+ accepted: option,
968
+ paymentHeader,
969
+ merchantTo,
970
+ payer,
971
+ chainId
972
+ });
973
+ }
974
+ mapX402ReceiptFromStatus(paymentRequired, option, paymentHeader, status) {
975
+ if (!status.txHash) {
976
+ throw new HavenApiError(
977
+ `x402 payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
978
+ 502,
979
+ status,
980
+ status.paymentId
981
+ );
982
+ }
983
+ return this.buildX402Receipt({
984
+ paymentId: status.paymentId,
985
+ txHash: status.txHash,
986
+ token: status.token || "USDC",
987
+ amount: status.amount || decimalFromUsdcAtomic(option.amount),
988
+ to: this.delegateAddress ?? "",
989
+ resourceUrl: paymentRequired.resource.url,
990
+ explorerUrl: explorerUrlOrEmpty(status.chainId, status.txHash),
991
+ accepted: option,
992
+ paymentHeader,
993
+ merchantTo: status.merchantAddress ?? option.payTo,
994
+ payer: this.x402Wallet,
995
+ chainId: status.chainId || chainIdFromNetwork(option.network)
996
+ });
997
+ }
998
+ buildX402Receipt(input) {
999
+ const fundingExplorerUrl = input.explorerUrl || explorerUrlOrEmpty(input.chainId, input.txHash);
1000
+ return {
1001
+ success: true,
1002
+ paymentId: input.paymentId,
1003
+ txHash: input.txHash,
1004
+ token: input.token,
1005
+ amount: input.amount,
1006
+ to: input.to,
1007
+ resourceUrl: input.resourceUrl,
1008
+ explorerUrl: input.explorerUrl,
1009
+ accepted: input.accepted,
1010
+ paymentHeader: input.paymentHeader,
1011
+ merchantTo: input.merchantTo ?? input.accepted.payTo,
1012
+ payer: input.payer,
1013
+ chainId: input.chainId,
1014
+ haven: {
1015
+ paymentId: input.paymentId,
1016
+ fundingTxHash: input.txHash,
1017
+ fundingExplorerUrl
1018
+ },
1019
+ merchant: {
1020
+ payTo: input.merchantTo ?? input.accepted.payTo
1021
+ },
1022
+ x402: {
1023
+ amount: input.accepted.amount,
1024
+ token: input.token,
1025
+ network: input.accepted.network,
1026
+ asset: input.accepted.asset,
1027
+ resource: input.accepted.resource ?? input.resourceUrl
1028
+ }
1029
+ };
1030
+ }
560
1031
  async createStandardX402Header(paymentRequired, option) {
561
1032
  if (!this.delegateKey) {
562
1033
  throw new HavenSigningError("delegateKey is required to sign x402 payment headers.");
@@ -569,7 +1040,7 @@ var HavenClient = class {
569
1040
  requirements
570
1041
  );
571
1042
  if (paymentRequired.x402Version < 2) return header;
572
- const payment = decodeBase64Json2(header);
1043
+ const payment = decodeBase64Json3(header);
573
1044
  return btoa(JSON.stringify({
574
1045
  x402Version: paymentRequired.x402Version,
575
1046
  accepted: option,
@@ -582,6 +1053,116 @@ var HavenClient = class {
582
1053
  this.x402ReceiptCache.set(idempotencyKey, { expiresAt, receipt });
583
1054
  }
584
1055
  }
1056
+ mapMachinePaymentReceipt(challenge, raw, txHash, execResult) {
1057
+ const receiptWithoutHeader = {
1058
+ success: true,
1059
+ rail: challenge.rail,
1060
+ paymentId: raw.payment_id,
1061
+ challengeId: challenge.challengeId,
1062
+ txHash,
1063
+ token: execResult?.token ?? raw.token ?? challenge.asset.symbol,
1064
+ amount: execResult?.amount ?? raw.amount ?? challenge.amount.display,
1065
+ to: execResult?.to ?? raw.to ?? challenge.recipient,
1066
+ resourceUrl: raw.resource_url ?? challenge.resource,
1067
+ explorerUrl: execResult?.explorer_url ?? raw.explorer_url ?? buildExplorerUrl(execResult?.chain_id ?? raw.chain_id ?? challenge.network.chainId, txHash),
1068
+ payer: raw.payer ?? raw.safe_address,
1069
+ chainId: execResult?.chain_id ?? raw.chain_id ?? challenge.network.chainId
1070
+ };
1071
+ return {
1072
+ ...receiptWithoutHeader,
1073
+ proofHeader: encodeMachinePaymentProof(receiptWithoutHeader)
1074
+ };
1075
+ }
1076
+ async recordMerchantRetryRejected(input) {
1077
+ try {
1078
+ await this.post("/machine-payments/reconciliation-events", {
1079
+ paymentId: input.paymentId,
1080
+ rail: input.rail,
1081
+ eventType: "merchant_retry_rejected_after_payment",
1082
+ txHash: input.txHash,
1083
+ reason: `Merchant returned HTTP ${input.retryResponse.status} after Haven payment confirmation`,
1084
+ details: {
1085
+ resource_url: input.resourceUrl,
1086
+ retry_status: input.retryResponse.status,
1087
+ retry_body: await responseSnippet(input.retryResponse),
1088
+ ...input.details
1089
+ }
1090
+ });
1091
+ } catch {
1092
+ }
1093
+ }
1094
+ async reportMachinePaymentEvidence(input) {
1095
+ try {
1096
+ await this.post("/machine-payments/evidence", {
1097
+ paymentId: input.paymentId,
1098
+ rail: input.rail,
1099
+ txHash: input.txHash,
1100
+ resourceUrl: input.resourceUrl,
1101
+ merchantStatus: input.merchantStatus,
1102
+ challengePayload: input.challengePayload,
1103
+ selectedPayment: input.selectedPayment,
1104
+ paymentProofHeaderName: input.paymentProofHeaderName,
1105
+ paymentProofHeader: input.paymentProofHeader,
1106
+ protocolReceiptHeaderName: input.protocolReceiptHeaderName,
1107
+ protocolReceiptHeader: input.protocolReceiptHeader,
1108
+ protocolReceiptPayload: input.protocolReceiptHeader ? parseProtocolReceiptHeader(input.protocolReceiptHeader) : void 0
1109
+ });
1110
+ } catch {
1111
+ }
1112
+ }
1113
+ throwIfNonSignableAuthorizationState(label, raw) {
1114
+ if (raw.status === "pending_signature") return;
1115
+ this.throwPaymentStateError(label, raw);
1116
+ }
1117
+ throwPaymentStateError(label, raw) {
1118
+ const statusCode = PAYMENT_STATE_STATUS_CODES[raw.status] ?? 502;
1119
+ const state = this.paymentStateFromRaw(label, raw);
1120
+ if (state) {
1121
+ throw new HavenPaymentStateError(state.message, statusCode, state, raw);
1122
+ }
1123
+ if (raw.status === "pending_approval") {
1124
+ throw new HavenApiError(
1125
+ `${label} exceeds the on-chain allowance and was queued for owner approval (payment_id: ${raw.payment_id}).`,
1126
+ statusCode,
1127
+ raw
1128
+ );
1129
+ }
1130
+ if (raw.status === "expired") {
1131
+ throw new HavenApiError(
1132
+ `${label} expired before it could be completed (payment_id: ${raw.payment_id}).`,
1133
+ statusCode,
1134
+ raw
1135
+ );
1136
+ }
1137
+ const paymentId = raw.payment_id ? ` (payment_id: ${raw.payment_id})` : "";
1138
+ const message = raw.error ?? `${label} ${raw.status}${paymentId}`;
1139
+ throw new HavenApiError(message, statusCode, raw);
1140
+ }
1141
+ paymentStateFromRaw(label, raw) {
1142
+ if (!raw.payment_id || !raw.status) return null;
1143
+ const phase = raw.phase ?? phaseForStatus(raw.status);
1144
+ const nextAction = raw.next_action ?? nextActionForStatus(raw.status);
1145
+ if (!phase || !nextAction) return null;
1146
+ const amount = raw.amount ?? raw.requested ?? "";
1147
+ const token = raw.token ?? "";
1148
+ const message = raw.message ?? raw.error ?? messageForState(label, raw.status, raw.payment_id, nextAction);
1149
+ return {
1150
+ paymentId: raw.payment_id,
1151
+ kind: raw.kind === "payment_intent" ? "payment_intent" : "approval_request",
1152
+ rail: raw.rail ?? "direct",
1153
+ status: raw.status === "pending" ? "pending_approval" : raw.status,
1154
+ phase,
1155
+ nextAction,
1156
+ amount,
1157
+ token,
1158
+ resourceUrl: raw.resource_url ?? null,
1159
+ merchantAddress: raw.merchant_to ?? null,
1160
+ txHash: raw.tx_hash ?? null,
1161
+ expiresAt: raw.expires_at ?? "",
1162
+ chainId: raw.chain_id ?? 0,
1163
+ message
1164
+ };
1165
+ }
585
1166
  x402PayerAddress() {
586
1167
  return this.delegateAddress ?? this.x402Wallet;
587
1168
  }
@@ -626,29 +1207,38 @@ var HavenClient = class {
626
1207
  error: result.errorMessage
627
1208
  };
628
1209
  } catch (err) {
629
- return {
630
- success: false,
631
- error: err instanceof Error ? err.message : String(err)
632
- };
1210
+ return this.toolError(err);
633
1211
  }
634
1212
  }
635
1213
  if (toolName === "authorize_x402_payment") {
636
- const { url, payTo, amount, asset, network, description } = input;
1214
+ const { url, payTo, amount, asset, network, description, idempotencyKey } = input;
1215
+ try {
1216
+ const receipt = await this.authorizeX402(
1217
+ this.toolX402PaymentRequired({ url, payTo, amount, asset, network, description }),
1218
+ { idempotencyKey }
1219
+ );
1220
+ return this.x402ToolReceipt(receipt);
1221
+ } catch (err) {
1222
+ return this.toolError(err);
1223
+ }
1224
+ }
1225
+ if (toolName === "resume_x402_payment") {
1226
+ const { payment_id, url, payTo, amount, asset, network, description, idempotencyKey } = input;
637
1227
  try {
638
- const receipt = await this.authorizeX402({
639
- x402Version: 2,
640
- resource: { url, description },
641
- accepts: [
642
- {
643
- scheme: "exact",
644
- network,
645
- amount,
646
- asset,
647
- payTo,
648
- maxTimeoutSeconds: 30
649
- }
650
- ]
1228
+ const receipt = await this.resumeAuthorizedX402({
1229
+ paymentId: payment_id,
1230
+ paymentRequired: this.toolX402PaymentRequired({ url, payTo, amount, asset, network, description }),
1231
+ idempotencyKey
651
1232
  });
1233
+ return this.x402ToolReceipt(receipt);
1234
+ } catch (err) {
1235
+ return this.toolError(err);
1236
+ }
1237
+ }
1238
+ if (toolName === "authorize_machine_payment") {
1239
+ const { challenge } = input;
1240
+ try {
1241
+ const receipt = await this.authorizeMachinePayment(challenge);
652
1242
  return {
653
1243
  success: true,
654
1244
  payment_id: receipt.paymentId,
@@ -658,33 +1248,107 @@ var HavenClient = class {
658
1248
  to: receipt.to,
659
1249
  resource_url: receipt.resourceUrl,
660
1250
  explorer_url: receipt.explorerUrl,
661
- payment_header: receipt.paymentHeader,
662
- merchant_to: receipt.merchantTo,
1251
+ proof_header: receipt.proofHeader,
1252
+ rail: receipt.rail,
1253
+ challenge_id: receipt.challengeId,
663
1254
  payer: receipt.payer,
664
1255
  chain_id: receipt.chainId
665
1256
  };
666
1257
  } catch (err) {
667
- return {
668
- success: false,
669
- error: err instanceof Error ? err.message : String(err)
670
- };
1258
+ return this.toolError(err);
671
1259
  }
672
1260
  }
673
1261
  if (toolName === "get_payment_status") {
674
1262
  const { payment_id } = input;
675
- const result = await this.getPayment(payment_id);
1263
+ const result = await this.getPaymentStatus(payment_id);
676
1264
  return {
677
1265
  payment_id: result.paymentId,
1266
+ kind: result.kind,
1267
+ rail: result.rail,
678
1268
  status: result.status,
1269
+ phase: result.phase,
1270
+ next_action: result.nextAction,
679
1271
  tx_hash: result.txHash,
680
1272
  token: result.token,
681
1273
  amount: result.amount,
682
- to: result.to,
683
- explorer_url: result.explorerUrl
1274
+ resource_url: result.resourceUrl,
1275
+ merchant_address: result.merchantAddress,
1276
+ expires_at: result.expiresAt,
1277
+ chain_id: result.chainId,
1278
+ message: result.message
684
1279
  };
685
1280
  }
686
1281
  throw new Error(`Unknown tool: ${toolName}`);
687
1282
  }
1283
+ toolX402PaymentRequired(input) {
1284
+ return {
1285
+ x402Version: 2,
1286
+ resource: { url: input.url, description: input.description },
1287
+ accepts: [
1288
+ {
1289
+ scheme: "exact",
1290
+ network: input.network,
1291
+ amount: input.amount,
1292
+ asset: input.asset,
1293
+ payTo: input.payTo,
1294
+ maxTimeoutSeconds: 30
1295
+ }
1296
+ ]
1297
+ };
1298
+ }
1299
+ x402ToolReceipt(receipt) {
1300
+ return {
1301
+ success: true,
1302
+ payment_id: receipt.paymentId,
1303
+ tx_hash: receipt.txHash,
1304
+ token: receipt.token,
1305
+ amount: receipt.amount,
1306
+ to: receipt.to,
1307
+ resource_url: receipt.resourceUrl,
1308
+ explorer_url: receipt.explorerUrl,
1309
+ payment_header: receipt.paymentHeader,
1310
+ merchant_to: receipt.merchantTo,
1311
+ payer: receipt.payer,
1312
+ chain_id: receipt.chainId,
1313
+ haven: receipt.haven,
1314
+ merchant: receipt.merchant,
1315
+ x402: receipt.x402
1316
+ };
1317
+ }
1318
+ toolError(err) {
1319
+ if (err instanceof HavenPaymentStateError) {
1320
+ return {
1321
+ success: false,
1322
+ payment_id: err.state.paymentId,
1323
+ kind: err.state.kind,
1324
+ rail: err.state.rail,
1325
+ status: err.state.status,
1326
+ phase: err.state.phase,
1327
+ next_action: err.state.nextAction,
1328
+ tx_hash: err.state.txHash,
1329
+ token: err.state.token,
1330
+ amount: err.state.amount,
1331
+ resource_url: err.state.resourceUrl,
1332
+ merchant_address: err.state.merchantAddress,
1333
+ expires_at: err.state.expiresAt,
1334
+ chain_id: err.state.chainId,
1335
+ message: err.state.message,
1336
+ error: err.message
1337
+ };
1338
+ }
1339
+ if (err instanceof HavenApiError) {
1340
+ return {
1341
+ success: false,
1342
+ status_code: err.statusCode,
1343
+ error: err.message,
1344
+ body: err.body
1345
+ };
1346
+ }
1347
+ return {
1348
+ success: false,
1349
+ error: err instanceof Error ? err.message : String(err)
1350
+ };
1351
+ }
688
1352
  // ── HTTP Helpers ─────────────────────────────────────────────────
689
1353
  async post(path, body) {
690
1354
  return this.request("POST", path, body);
@@ -743,13 +1407,31 @@ var HavenClient = class {
743
1407
  expiresAt: raw.expires_at
744
1408
  };
745
1409
  }
1410
+ mapPaymentStatusResult(raw) {
1411
+ return {
1412
+ paymentId: raw.payment_id,
1413
+ kind: raw.kind,
1414
+ rail: raw.rail,
1415
+ status: raw.status,
1416
+ phase: raw.phase,
1417
+ nextAction: raw.next_action,
1418
+ amount: raw.amount,
1419
+ token: raw.token,
1420
+ resourceUrl: raw.resource_url,
1421
+ merchantAddress: raw.merchant_address,
1422
+ txHash: raw.tx_hash,
1423
+ expiresAt: raw.expires_at,
1424
+ chainId: raw.chain_id,
1425
+ message: raw.message
1426
+ };
1427
+ }
746
1428
  };
747
1429
  function sleep(ms) {
748
1430
  return new Promise((resolve) => setTimeout(resolve, ms));
749
1431
  }
750
1432
  function getPaymentHeaderValidBefore(paymentHeader) {
751
1433
  try {
752
- const payment = decodeBase64Json2(
1434
+ const payment = decodeBase64Json3(
753
1435
  paymentHeader
754
1436
  );
755
1437
  const payload = payment.payload;
@@ -759,9 +1441,28 @@ function getPaymentHeaderValidBefore(paymentHeader) {
759
1441
  }
760
1442
  return 0;
761
1443
  }
762
- function decodeBase64Json2(value) {
1444
+ function decodeBase64Json3(value) {
763
1445
  return JSON.parse(atob(value));
764
1446
  }
1447
+ function parseProtocolReceiptHeader(value) {
1448
+ try {
1449
+ return JSON.parse(atob(value));
1450
+ } catch {
1451
+ try {
1452
+ return JSON.parse(value);
1453
+ } catch {
1454
+ return void 0;
1455
+ }
1456
+ }
1457
+ }
1458
+ async function responseSnippet(response) {
1459
+ try {
1460
+ const text = await response.clone().text();
1461
+ return text.slice(0, 1e3) || null;
1462
+ } catch {
1463
+ return null;
1464
+ }
1465
+ }
765
1466
 
766
1467
  // src/tools.ts
767
1468
  var makePaymentSchema = {
@@ -822,13 +1523,67 @@ var authorizeX402Schema = {
822
1523
  description: {
823
1524
  type: "string",
824
1525
  description: "Description of the resource being paid for"
1526
+ },
1527
+ idempotencyKey: {
1528
+ type: "string",
1529
+ description: "Stable caller-supplied key for this user intent. Reuse it when resuming after user approval."
825
1530
  }
826
1531
  },
827
1532
  required: ["url", "payTo", "amount", "asset", "network"]
828
1533
  };
829
- 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.";
830
- var GET_STATUS_DESCRIPTION = "Check the status of a previously initiated payment. Returns the current status, transaction hash (if confirmed), and payment details.";
831
- 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.";
1534
+ var resumeX402Schema = {
1535
+ type: "object",
1536
+ properties: {
1537
+ payment_id: {
1538
+ type: "string",
1539
+ description: "The payment or approval request ID returned by authorize_x402_payment."
1540
+ },
1541
+ url: {
1542
+ type: "string",
1543
+ description: "The original URL that returned HTTP 402."
1544
+ },
1545
+ payTo: {
1546
+ type: "string",
1547
+ description: "Payment recipient address from the original 402 response."
1548
+ },
1549
+ amount: {
1550
+ type: "string",
1551
+ description: "Payment amount in atomic units from the original 402 response."
1552
+ },
1553
+ asset: {
1554
+ type: "string",
1555
+ description: "Token contract address from the original 402 response."
1556
+ },
1557
+ network: {
1558
+ type: "string",
1559
+ description: "CAIP-2 chain ID or x402 network from the original 402 response."
1560
+ },
1561
+ description: {
1562
+ type: "string",
1563
+ description: "Description of the resource being paid for."
1564
+ },
1565
+ idempotencyKey: {
1566
+ type: "string",
1567
+ description: "Stable caller-supplied key used for the original authorization."
1568
+ }
1569
+ },
1570
+ required: ["payment_id", "url", "payTo", "amount", "asset", "network"]
1571
+ };
1572
+ var authorizeMachinePaymentSchema = {
1573
+ type: "object",
1574
+ properties: {
1575
+ challenge: {
1576
+ type: "object",
1577
+ description: "Machine payment challenge returned by a Haven demo endpoint"
1578
+ }
1579
+ },
1580
+ required: ["challenge"]
1581
+ };
1582
+ var MAKE_PAYMENT_DESCRIPTION = "Request and sign a payment from the user-controlled Safe within approved on-chain limits. Haven authenticates the agent, validates the signed intent, and relays the Safe AllowanceModule transaction; it does not hold keys or control funds. Gnosis Chain tokens: EURe, USDC.e, xDAI. Base tokens: USDC, ETH.";
1583
+ var GET_STATUS_DESCRIPTION = "Check the status of a previously initiated payment. Accepts payment intent IDs and approval request IDs. Returns the current status, phase, next_action, transaction hash if available, and payment details.";
1584
+ var AUTHORIZE_X402_DESCRIPTION = "Authorize payment for an HTTP 402 (Payment Required) response. When a paid API returns x402 payment requirements, use this tool to sign with the agent-owned delegate key and request a policy-limited Safe AllowanceModule top-up when needed. Haven relays signed transactions only; the agent key authorizes payment and on-chain limits enforce spend. If this returns pending_approval, tell the user it is waiting in Haven, call get_payment_status later, and use resume_x402_payment only when next_action is retry_original_x402_request. Do not loop retries while approval is pending. Use the returned payment_header as the X-PAYMENT header on the retry request when doing a manual HTTP retry.";
1585
+ var RESUME_X402_DESCRIPTION = "Resume an x402 payment after the user approved it in Haven. Use this only after get_payment_status returns next_action=retry_original_x402_request. It checks the approved payment, validates the original x402 details, and returns a merchant X-PAYMENT header without creating a new approval request.";
1586
+ var AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION = "Authorize a Haven machine-payment challenge, currently for the internal MPP demo rail. The agent signs the payment, Haven relays it within the on-chain allowance, and the tool returns a proof header for the retry request.";
832
1587
  function claudeTools() {
833
1588
  return [
834
1589
  {
@@ -845,6 +1600,16 @@ function claudeTools() {
845
1600
  name: "authorize_x402_payment",
846
1601
  description: AUTHORIZE_X402_DESCRIPTION,
847
1602
  input_schema: authorizeX402Schema
1603
+ },
1604
+ {
1605
+ name: "resume_x402_payment",
1606
+ description: RESUME_X402_DESCRIPTION,
1607
+ input_schema: resumeX402Schema
1608
+ },
1609
+ {
1610
+ name: "authorize_machine_payment",
1611
+ description: AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION,
1612
+ input_schema: authorizeMachinePaymentSchema
848
1613
  }
849
1614
  ];
850
1615
  }
@@ -873,6 +1638,22 @@ function openaiTools() {
873
1638
  description: AUTHORIZE_X402_DESCRIPTION,
874
1639
  parameters: authorizeX402Schema
875
1640
  }
1641
+ },
1642
+ {
1643
+ type: "function",
1644
+ function: {
1645
+ name: "resume_x402_payment",
1646
+ description: RESUME_X402_DESCRIPTION,
1647
+ parameters: resumeX402Schema
1648
+ }
1649
+ },
1650
+ {
1651
+ type: "function",
1652
+ function: {
1653
+ name: "authorize_machine_payment",
1654
+ description: AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION,
1655
+ parameters: authorizeMachinePaymentSchema
1656
+ }
876
1657
  }
877
1658
  ];
878
1659
  }
@@ -886,11 +1667,16 @@ var havenTools = {
886
1667
  exports.HavenApiError = HavenApiError;
887
1668
  exports.HavenClient = HavenClient;
888
1669
  exports.HavenError = HavenError;
1670
+ exports.HavenPaymentStateError = HavenPaymentStateError;
889
1671
  exports.HavenSigningError = HavenSigningError;
890
1672
  exports.HavenTimeoutError = HavenTimeoutError;
891
1673
  exports.addressFromKey = addressFromKey;
1674
+ exports.buildMachinePaymentIdempotencyKey = buildMachinePaymentIdempotencyKey;
1675
+ exports.encodeMachinePaymentProof = encodeMachinePaymentProof;
892
1676
  exports.encodePaymentProof = encodePaymentProof;
893
1677
  exports.havenTools = havenTools;
1678
+ exports.parseMachinePaymentChallenge = parseMachinePaymentChallenge;
1679
+ exports.parseMachinePaymentChallengeResponse = parseMachinePaymentChallengeResponse;
894
1680
  exports.parsePaymentRequired = parsePaymentRequired;
895
1681
  exports.parsePaymentRequiredResponse = parsePaymentRequiredResponse;
896
1682
  exports.selectPaymentOption = selectPaymentOption;