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