@haven_ai/sdk 0.1.1 → 0.1.2

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";
@@ -270,12 +361,65 @@ function buildExplorerUrl(chainId, txHash) {
270
361
  var DEFAULT_REQUEST_TIMEOUT = 3e4;
271
362
  var DEFAULT_CONFIRMATION_TIMEOUT = 9e4;
272
363
  var DEFAULT_POLLING_INTERVAL = 3e3;
364
+ var PAYMENT_STATE_STATUS_CODES = {
365
+ pending: 202,
366
+ pending_approval: 202,
367
+ approved: 202,
368
+ proposed: 202,
369
+ executed: 200,
370
+ pending_signature: 409,
371
+ submitted: 409,
372
+ expired: 410,
373
+ failed: 502,
374
+ rejected: 409
375
+ };
273
376
  function chainIdFromNetwork(network) {
274
377
  if (network === "base") return 8453;
275
378
  if (!network?.startsWith("eip155:")) return void 0;
276
379
  const chainId = Number(network.slice("eip155:".length));
277
380
  return Number.isFinite(chainId) ? chainId : void 0;
278
381
  }
382
+ function phaseForStatus(status) {
383
+ if (status === "pending_signature") return "agent_signature_required";
384
+ if (status === "submitted") return "payment_submitted";
385
+ if (status === "confirmed") return "payment_confirmed";
386
+ if (status === "pending" || status === "pending_approval") return "user_approval_required";
387
+ if (status === "approved") return "user_execution_required";
388
+ if (status === "proposed") return "waiting_for_additional_approvals";
389
+ if (status === "executed") return "funding_sent";
390
+ if (status === "rejected") return "rejected";
391
+ if (status === "expired") return "expired";
392
+ if (status === "failed") return "failed";
393
+ return null;
394
+ }
395
+ function nextActionForStatus(status) {
396
+ if (status === "pending_signature") return "sign_and_submit_payment";
397
+ if (status === "submitted") return "check_status_later";
398
+ if (status === "confirmed") return "none";
399
+ if (status === "pending" || status === "pending_approval") return "wait_for_user_approval";
400
+ if (status === "approved") return "wait_for_user_to_complete_payment";
401
+ if (status === "proposed") return "wait_for_user_approval";
402
+ if (status === "executed") return "retry_original_x402_request";
403
+ if (status === "rejected") return "stop_and_tell_user";
404
+ if (status === "expired") return "request_again_if_user_still_wants_it";
405
+ if (status === "failed") return "stop_and_tell_user";
406
+ return null;
407
+ }
408
+ function messageForState(label, status, paymentId, nextAction) {
409
+ if (status === "pending" || status === "pending_approval") {
410
+ return `${label} is above the remaining agent budget and is waiting for user approval in Haven (payment_id: ${paymentId}).`;
411
+ }
412
+ if (status === "executed") {
413
+ return "The user completed the funding payment. Retry the original x402 request.";
414
+ }
415
+ if (status === "rejected") {
416
+ return `The user rejected this payment request (payment_id: ${paymentId}).`;
417
+ }
418
+ if (status === "expired") {
419
+ return `This payment request expired (payment_id: ${paymentId}).`;
420
+ }
421
+ return `${label} is ${status}; next_action=${nextAction} (payment_id: ${paymentId}).`;
422
+ }
279
423
  var HavenClient = class {
280
424
  apiKey;
281
425
  delegateKey;
@@ -286,6 +430,7 @@ var HavenClient = class {
286
430
  pollingInterval;
287
431
  inFlightX402 = /* @__PURE__ */ new Map();
288
432
  x402ReceiptCache = /* @__PURE__ */ new Map();
433
+ inFlightMachinePayments = /* @__PURE__ */ new Map();
289
434
  /** Delegate address derived from the private key (if provided) */
290
435
  delegateAddress;
291
436
  constructor(config) {
@@ -333,11 +478,7 @@ var HavenClient = class {
333
478
  to: request.to
334
479
  });
335
480
  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
- );
481
+ this.throwPaymentStateError("Payment", raw);
341
482
  }
342
483
  return {
343
484
  paymentId: raw.payment_id,
@@ -388,6 +529,16 @@ var HavenClient = class {
388
529
  const raw = await this.get(`/payments/${paymentId}`);
389
530
  return this.mapPaymentResult(raw);
390
531
  }
532
+ /**
533
+ * Get agent-actionable status for a payment intent or approval request.
534
+ *
535
+ * Use this for IDs returned by agent tools and machine-payment/x402 flows.
536
+ * `getPayment()` remains available for payment-intent-only integrations.
537
+ */
538
+ async getPaymentStatus(paymentId) {
539
+ const raw = await this.get(`/machine-payments/${paymentId}/status`);
540
+ return this.mapPaymentStatusResult(raw);
541
+ }
391
542
  /**
392
543
  * Poll until a payment reaches a terminal status (confirmed, failed, expired).
393
544
  */
@@ -472,6 +623,7 @@ var HavenClient = class {
472
623
  this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
473
624
  return receipt2;
474
625
  }
626
+ this.throwIfNonSignableAuthorizationState("x402 payment", raw);
475
627
  if (!raw.sign_data?.hash) {
476
628
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
477
629
  }
@@ -481,11 +633,7 @@ var HavenClient = class {
481
633
  { signature: sig }
482
634
  );
483
635
  if (execResult.status !== "confirmed") {
484
- throw new HavenApiError(
485
- execResult.error ?? `x402 payment ${execResult.status}`,
486
- 502,
487
- execResult
488
- );
636
+ this.throwPaymentStateError("x402 payment", execResult);
489
637
  }
490
638
  const receipt = {
491
639
  success: true,
@@ -522,11 +670,22 @@ var HavenClient = class {
522
670
  const initialInit = this.withX402Wallet(init, this.x402PayerAddress());
523
671
  const response = await globalThis.fetch(url, initialInit);
524
672
  if (response.status !== 402) return response;
673
+ const machineChallengeHeader = response.headers.get("MACHINE-PAYMENT-CHALLENGE");
674
+ if (machineChallengeHeader) {
675
+ const challenge = await parseMachinePaymentChallengeResponse(response);
676
+ return this.fetchWithMachinePayment(url, initialInit, challenge);
677
+ }
525
678
  let paymentRequired;
526
679
  try {
527
680
  paymentRequired = await parsePaymentRequiredResponse(response);
528
681
  } catch {
529
- return response;
682
+ let challenge;
683
+ try {
684
+ challenge = await parseMachinePaymentChallengeResponse(response);
685
+ } catch {
686
+ return response;
687
+ }
688
+ return this.fetchWithMachinePayment(url, initialInit, challenge);
530
689
  }
531
690
  const receipt = await this.authorizeX402(paymentRequired);
532
691
  if (!receipt.accepted) {
@@ -542,6 +701,17 @@ var HavenClient = class {
542
701
  headers: retryHeaders
543
702
  });
544
703
  if (retryResponse.status === 402) {
704
+ await this.recordMerchantRetryRejected({
705
+ rail: "x402",
706
+ paymentId: receipt.paymentId,
707
+ txHash: receipt.txHash,
708
+ resourceUrl: receipt.resourceUrl,
709
+ retryResponse,
710
+ details: {
711
+ merchant_to: receipt.merchantTo,
712
+ delegate_to: receipt.to
713
+ }
714
+ });
545
715
  throw new HavenApiError(
546
716
  "x402 retry was rejected after Haven funded the delegate wallet; reconciliation may be required.",
547
717
  402,
@@ -555,6 +725,106 @@ var HavenClient = class {
555
725
  }
556
726
  );
557
727
  }
728
+ await this.reportMachinePaymentEvidence({
729
+ paymentId: receipt.paymentId,
730
+ rail: "x402",
731
+ txHash: receipt.txHash,
732
+ resourceUrl: receipt.resourceUrl,
733
+ merchantStatus: retryResponse.status,
734
+ challengePayload: paymentRequired,
735
+ selectedPayment: receipt.accepted,
736
+ paymentProofHeaderName: "X-PAYMENT",
737
+ paymentProofHeader: receipt.paymentHeader,
738
+ protocolReceiptHeaderName: "PAYMENT-RESPONSE",
739
+ protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
740
+ });
741
+ return retryResponse;
742
+ }
743
+ async authorizeMachinePayment(challenge) {
744
+ if (!this.delegateKey) {
745
+ throw new HavenSigningError(
746
+ "delegateKey is required for machine payments. Pass it in the HavenClient config."
747
+ );
748
+ }
749
+ if (challenge.rail !== "mpp_demo") {
750
+ throw new HavenApiError(`Unsupported machine payment rail: ${challenge.rail}`, 400);
751
+ }
752
+ const idempotencyKey = buildMachinePaymentIdempotencyKey(challenge);
753
+ const inFlight = this.inFlightMachinePayments.get(idempotencyKey);
754
+ if (inFlight) return inFlight;
755
+ const promise = this.authorizeMppDemoPayment(challenge, idempotencyKey);
756
+ this.inFlightMachinePayments.set(idempotencyKey, promise);
757
+ try {
758
+ return await promise;
759
+ } finally {
760
+ this.inFlightMachinePayments.delete(idempotencyKey);
761
+ }
762
+ }
763
+ async authorizeMppDemoPayment(challenge, idempotencyKey) {
764
+ const raw = await this.post(
765
+ "/machine-payments/authorize",
766
+ { challenge, idempotencyKey }
767
+ );
768
+ if (raw.success && raw.tx_hash) {
769
+ return this.mapMachinePaymentReceipt(challenge, raw, raw.tx_hash);
770
+ }
771
+ this.throwIfNonSignableAuthorizationState("Machine payment", raw);
772
+ if (!raw.sign_data?.hash) {
773
+ throw new HavenApiError("No sign_hash returned from machine payment authorization", 500, raw);
774
+ }
775
+ const sig = signHash(this.delegateKey, raw.sign_data.hash);
776
+ const execResult = await this.post(
777
+ `/payments/${raw.payment_id}/sign`,
778
+ { signature: sig }
779
+ );
780
+ if (execResult.status !== "confirmed" || !execResult.tx_hash) {
781
+ this.throwPaymentStateError("Machine payment", execResult);
782
+ }
783
+ return this.mapMachinePaymentReceipt(challenge, raw, execResult.tx_hash, execResult);
784
+ }
785
+ async fetchWithMachinePayment(url, initialInit, challenge) {
786
+ const receipt = await this.authorizeMachinePayment(challenge);
787
+ const retryHeaders = new Headers(initialInit?.headers);
788
+ retryHeaders.set("MACHINE-PAYMENT-PROOF", receipt.proofHeader);
789
+ const retryResponse = await globalThis.fetch(url, {
790
+ ...initialInit,
791
+ headers: retryHeaders
792
+ });
793
+ if (retryResponse.status === 402) {
794
+ await this.recordMerchantRetryRejected({
795
+ rail: receipt.rail,
796
+ paymentId: receipt.paymentId,
797
+ txHash: receipt.txHash,
798
+ resourceUrl: receipt.resourceUrl,
799
+ retryResponse,
800
+ details: {
801
+ challenge_id: receipt.challengeId
802
+ }
803
+ });
804
+ throw new HavenApiError(
805
+ "Machine payment retry was rejected after Haven sent the payment.",
806
+ 402,
807
+ {
808
+ marker: "machine_payment_retry_rejected_after_payment",
809
+ payment_id: receipt.paymentId,
810
+ tx_hash: receipt.txHash,
811
+ resource_url: receipt.resourceUrl,
812
+ rail: receipt.rail
813
+ }
814
+ );
815
+ }
816
+ await this.reportMachinePaymentEvidence({
817
+ paymentId: receipt.paymentId,
818
+ rail: receipt.rail,
819
+ txHash: receipt.txHash,
820
+ resourceUrl: receipt.resourceUrl,
821
+ merchantStatus: retryResponse.status,
822
+ challengePayload: challenge,
823
+ paymentProofHeaderName: "MACHINE-PAYMENT-PROOF",
824
+ paymentProofHeader: receipt.proofHeader,
825
+ protocolReceiptHeaderName: retryResponse.headers.has("Payment-Receipt") ? "Payment-Receipt" : retryResponse.headers.has("MACHINE-PAYMENT-RESPONSE") ? "MACHINE-PAYMENT-RESPONSE" : void 0,
826
+ protocolReceiptHeader: retryResponse.headers.get("Payment-Receipt") ?? retryResponse.headers.get("MACHINE-PAYMENT-RESPONSE") ?? void 0
827
+ });
558
828
  return retryResponse;
559
829
  }
560
830
  async createStandardX402Header(paymentRequired, option) {
@@ -569,7 +839,7 @@ var HavenClient = class {
569
839
  requirements
570
840
  );
571
841
  if (paymentRequired.x402Version < 2) return header;
572
- const payment = decodeBase64Json2(header);
842
+ const payment = decodeBase64Json3(header);
573
843
  return btoa(JSON.stringify({
574
844
  x402Version: paymentRequired.x402Version,
575
845
  accepted: option,
@@ -582,6 +852,116 @@ var HavenClient = class {
582
852
  this.x402ReceiptCache.set(idempotencyKey, { expiresAt, receipt });
583
853
  }
584
854
  }
855
+ mapMachinePaymentReceipt(challenge, raw, txHash, execResult) {
856
+ const receiptWithoutHeader = {
857
+ success: true,
858
+ rail: challenge.rail,
859
+ paymentId: raw.payment_id,
860
+ challengeId: challenge.challengeId,
861
+ txHash,
862
+ token: execResult?.token ?? raw.token ?? challenge.asset.symbol,
863
+ amount: execResult?.amount ?? raw.amount ?? challenge.amount.display,
864
+ to: execResult?.to ?? raw.to ?? challenge.recipient,
865
+ resourceUrl: raw.resource_url ?? challenge.resource,
866
+ explorerUrl: execResult?.explorer_url ?? raw.explorer_url ?? buildExplorerUrl(execResult?.chain_id ?? raw.chain_id ?? challenge.network.chainId, txHash),
867
+ payer: raw.payer ?? raw.safe_address,
868
+ chainId: execResult?.chain_id ?? raw.chain_id ?? challenge.network.chainId
869
+ };
870
+ return {
871
+ ...receiptWithoutHeader,
872
+ proofHeader: encodeMachinePaymentProof(receiptWithoutHeader)
873
+ };
874
+ }
875
+ async recordMerchantRetryRejected(input) {
876
+ try {
877
+ await this.post("/machine-payments/reconciliation-events", {
878
+ paymentId: input.paymentId,
879
+ rail: input.rail,
880
+ eventType: "merchant_retry_rejected_after_payment",
881
+ txHash: input.txHash,
882
+ reason: `Merchant returned HTTP ${input.retryResponse.status} after Haven payment confirmation`,
883
+ details: {
884
+ resource_url: input.resourceUrl,
885
+ retry_status: input.retryResponse.status,
886
+ retry_body: await responseSnippet(input.retryResponse),
887
+ ...input.details
888
+ }
889
+ });
890
+ } catch {
891
+ }
892
+ }
893
+ async reportMachinePaymentEvidence(input) {
894
+ try {
895
+ await this.post("/machine-payments/evidence", {
896
+ paymentId: input.paymentId,
897
+ rail: input.rail,
898
+ txHash: input.txHash,
899
+ resourceUrl: input.resourceUrl,
900
+ merchantStatus: input.merchantStatus,
901
+ challengePayload: input.challengePayload,
902
+ selectedPayment: input.selectedPayment,
903
+ paymentProofHeaderName: input.paymentProofHeaderName,
904
+ paymentProofHeader: input.paymentProofHeader,
905
+ protocolReceiptHeaderName: input.protocolReceiptHeaderName,
906
+ protocolReceiptHeader: input.protocolReceiptHeader,
907
+ protocolReceiptPayload: input.protocolReceiptHeader ? parseProtocolReceiptHeader(input.protocolReceiptHeader) : void 0
908
+ });
909
+ } catch {
910
+ }
911
+ }
912
+ throwIfNonSignableAuthorizationState(label, raw) {
913
+ if (raw.status === "pending_signature") return;
914
+ this.throwPaymentStateError(label, raw);
915
+ }
916
+ throwPaymentStateError(label, raw) {
917
+ const statusCode = PAYMENT_STATE_STATUS_CODES[raw.status] ?? 502;
918
+ const state = this.paymentStateFromRaw(label, raw);
919
+ if (state) {
920
+ throw new HavenPaymentStateError(state.message, statusCode, state, raw);
921
+ }
922
+ if (raw.status === "pending_approval") {
923
+ throw new HavenApiError(
924
+ `${label} exceeds the on-chain allowance and was queued for owner approval (payment_id: ${raw.payment_id}).`,
925
+ statusCode,
926
+ raw
927
+ );
928
+ }
929
+ if (raw.status === "expired") {
930
+ throw new HavenApiError(
931
+ `${label} expired before it could be completed (payment_id: ${raw.payment_id}).`,
932
+ statusCode,
933
+ raw
934
+ );
935
+ }
936
+ const paymentId = raw.payment_id ? ` (payment_id: ${raw.payment_id})` : "";
937
+ const message = raw.error ?? `${label} ${raw.status}${paymentId}`;
938
+ throw new HavenApiError(message, statusCode, raw);
939
+ }
940
+ paymentStateFromRaw(label, raw) {
941
+ if (!raw.payment_id || !raw.status) return null;
942
+ const phase = raw.phase ?? phaseForStatus(raw.status);
943
+ const nextAction = raw.next_action ?? nextActionForStatus(raw.status);
944
+ if (!phase || !nextAction) return null;
945
+ const amount = raw.amount ?? raw.requested ?? "";
946
+ const token = raw.token ?? "";
947
+ const message = raw.message ?? raw.error ?? messageForState(label, raw.status, raw.payment_id, nextAction);
948
+ return {
949
+ paymentId: raw.payment_id,
950
+ kind: raw.kind === "payment_intent" ? "payment_intent" : "approval_request",
951
+ rail: raw.rail ?? "direct",
952
+ status: raw.status === "pending" ? "pending_approval" : raw.status,
953
+ phase,
954
+ nextAction,
955
+ amount,
956
+ token,
957
+ resourceUrl: raw.resource_url ?? null,
958
+ merchantAddress: raw.merchant_to ?? null,
959
+ txHash: raw.tx_hash ?? null,
960
+ expiresAt: raw.expires_at ?? "",
961
+ chainId: raw.chain_id ?? 0,
962
+ message
963
+ };
964
+ }
585
965
  x402PayerAddress() {
586
966
  return this.delegateAddress ?? this.x402Wallet;
587
967
  }
@@ -626,10 +1006,7 @@ var HavenClient = class {
626
1006
  error: result.errorMessage
627
1007
  };
628
1008
  } catch (err) {
629
- return {
630
- success: false,
631
- error: err instanceof Error ? err.message : String(err)
632
- };
1009
+ return this.toolError(err);
633
1010
  }
634
1011
  }
635
1012
  if (toolName === "authorize_x402_payment") {
@@ -664,27 +1041,88 @@ var HavenClient = class {
664
1041
  chain_id: receipt.chainId
665
1042
  };
666
1043
  } catch (err) {
1044
+ return this.toolError(err);
1045
+ }
1046
+ }
1047
+ if (toolName === "authorize_machine_payment") {
1048
+ const { challenge } = input;
1049
+ try {
1050
+ const receipt = await this.authorizeMachinePayment(challenge);
667
1051
  return {
668
- success: false,
669
- error: err instanceof Error ? err.message : String(err)
1052
+ success: true,
1053
+ payment_id: receipt.paymentId,
1054
+ tx_hash: receipt.txHash,
1055
+ token: receipt.token,
1056
+ amount: receipt.amount,
1057
+ to: receipt.to,
1058
+ resource_url: receipt.resourceUrl,
1059
+ explorer_url: receipt.explorerUrl,
1060
+ proof_header: receipt.proofHeader,
1061
+ rail: receipt.rail,
1062
+ challenge_id: receipt.challengeId,
1063
+ payer: receipt.payer,
1064
+ chain_id: receipt.chainId
670
1065
  };
1066
+ } catch (err) {
1067
+ return this.toolError(err);
671
1068
  }
672
1069
  }
673
1070
  if (toolName === "get_payment_status") {
674
1071
  const { payment_id } = input;
675
- const result = await this.getPayment(payment_id);
1072
+ const result = await this.getPaymentStatus(payment_id);
676
1073
  return {
677
1074
  payment_id: result.paymentId,
1075
+ kind: result.kind,
1076
+ rail: result.rail,
678
1077
  status: result.status,
1078
+ phase: result.phase,
1079
+ next_action: result.nextAction,
679
1080
  tx_hash: result.txHash,
680
1081
  token: result.token,
681
1082
  amount: result.amount,
682
- to: result.to,
683
- explorer_url: result.explorerUrl
1083
+ resource_url: result.resourceUrl,
1084
+ merchant_address: result.merchantAddress,
1085
+ expires_at: result.expiresAt,
1086
+ chain_id: result.chainId,
1087
+ message: result.message
684
1088
  };
685
1089
  }
686
1090
  throw new Error(`Unknown tool: ${toolName}`);
687
1091
  }
1092
+ toolError(err) {
1093
+ if (err instanceof HavenPaymentStateError) {
1094
+ return {
1095
+ success: false,
1096
+ payment_id: err.state.paymentId,
1097
+ kind: err.state.kind,
1098
+ rail: err.state.rail,
1099
+ status: err.state.status,
1100
+ phase: err.state.phase,
1101
+ next_action: err.state.nextAction,
1102
+ tx_hash: err.state.txHash,
1103
+ token: err.state.token,
1104
+ amount: err.state.amount,
1105
+ resource_url: err.state.resourceUrl,
1106
+ merchant_address: err.state.merchantAddress,
1107
+ expires_at: err.state.expiresAt,
1108
+ chain_id: err.state.chainId,
1109
+ message: err.state.message,
1110
+ error: err.message
1111
+ };
1112
+ }
1113
+ if (err instanceof HavenApiError) {
1114
+ return {
1115
+ success: false,
1116
+ status_code: err.statusCode,
1117
+ error: err.message,
1118
+ body: err.body
1119
+ };
1120
+ }
1121
+ return {
1122
+ success: false,
1123
+ error: err instanceof Error ? err.message : String(err)
1124
+ };
1125
+ }
688
1126
  // ── HTTP Helpers ─────────────────────────────────────────────────
689
1127
  async post(path, body) {
690
1128
  return this.request("POST", path, body);
@@ -743,13 +1181,31 @@ var HavenClient = class {
743
1181
  expiresAt: raw.expires_at
744
1182
  };
745
1183
  }
1184
+ mapPaymentStatusResult(raw) {
1185
+ return {
1186
+ paymentId: raw.payment_id,
1187
+ kind: raw.kind,
1188
+ rail: raw.rail,
1189
+ status: raw.status,
1190
+ phase: raw.phase,
1191
+ nextAction: raw.next_action,
1192
+ amount: raw.amount,
1193
+ token: raw.token,
1194
+ resourceUrl: raw.resource_url,
1195
+ merchantAddress: raw.merchant_address,
1196
+ txHash: raw.tx_hash,
1197
+ expiresAt: raw.expires_at,
1198
+ chainId: raw.chain_id,
1199
+ message: raw.message
1200
+ };
1201
+ }
746
1202
  };
747
1203
  function sleep(ms) {
748
1204
  return new Promise((resolve) => setTimeout(resolve, ms));
749
1205
  }
750
1206
  function getPaymentHeaderValidBefore(paymentHeader) {
751
1207
  try {
752
- const payment = decodeBase64Json2(
1208
+ const payment = decodeBase64Json3(
753
1209
  paymentHeader
754
1210
  );
755
1211
  const payload = payment.payload;
@@ -759,9 +1215,28 @@ function getPaymentHeaderValidBefore(paymentHeader) {
759
1215
  }
760
1216
  return 0;
761
1217
  }
762
- function decodeBase64Json2(value) {
1218
+ function decodeBase64Json3(value) {
763
1219
  return JSON.parse(atob(value));
764
1220
  }
1221
+ function parseProtocolReceiptHeader(value) {
1222
+ try {
1223
+ return JSON.parse(atob(value));
1224
+ } catch {
1225
+ try {
1226
+ return JSON.parse(value);
1227
+ } catch {
1228
+ return void 0;
1229
+ }
1230
+ }
1231
+ }
1232
+ async function responseSnippet(response) {
1233
+ try {
1234
+ const text = await response.clone().text();
1235
+ return text.slice(0, 1e3) || null;
1236
+ } catch {
1237
+ return null;
1238
+ }
1239
+ }
765
1240
 
766
1241
  // src/tools.ts
767
1242
  var makePaymentSchema = {
@@ -826,9 +1301,20 @@ var authorizeX402Schema = {
826
1301
  },
827
1302
  required: ["url", "payTo", "amount", "asset", "network"]
828
1303
  };
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.";
1304
+ var authorizeMachinePaymentSchema = {
1305
+ type: "object",
1306
+ properties: {
1307
+ challenge: {
1308
+ type: "object",
1309
+ description: "Machine payment challenge returned by a Haven demo endpoint"
1310
+ }
1311
+ },
1312
+ required: ["challenge"]
1313
+ };
1314
+ 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.";
1315
+ 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.";
1316
+ 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 retry the original x402 request only when next_action is retry_original_x402_request. Do not rewrite the SDK or loop retries while approval is pending. Use the returned payment_header as the X-PAYMENT header on the retry request.";
1317
+ 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
1318
  function claudeTools() {
833
1319
  return [
834
1320
  {
@@ -845,6 +1331,11 @@ function claudeTools() {
845
1331
  name: "authorize_x402_payment",
846
1332
  description: AUTHORIZE_X402_DESCRIPTION,
847
1333
  input_schema: authorizeX402Schema
1334
+ },
1335
+ {
1336
+ name: "authorize_machine_payment",
1337
+ description: AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION,
1338
+ input_schema: authorizeMachinePaymentSchema
848
1339
  }
849
1340
  ];
850
1341
  }
@@ -873,6 +1364,14 @@ function openaiTools() {
873
1364
  description: AUTHORIZE_X402_DESCRIPTION,
874
1365
  parameters: authorizeX402Schema
875
1366
  }
1367
+ },
1368
+ {
1369
+ type: "function",
1370
+ function: {
1371
+ name: "authorize_machine_payment",
1372
+ description: AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION,
1373
+ parameters: authorizeMachinePaymentSchema
1374
+ }
876
1375
  }
877
1376
  ];
878
1377
  }
@@ -886,11 +1385,16 @@ var havenTools = {
886
1385
  exports.HavenApiError = HavenApiError;
887
1386
  exports.HavenClient = HavenClient;
888
1387
  exports.HavenError = HavenError;
1388
+ exports.HavenPaymentStateError = HavenPaymentStateError;
889
1389
  exports.HavenSigningError = HavenSigningError;
890
1390
  exports.HavenTimeoutError = HavenTimeoutError;
891
1391
  exports.addressFromKey = addressFromKey;
1392
+ exports.buildMachinePaymentIdempotencyKey = buildMachinePaymentIdempotencyKey;
1393
+ exports.encodeMachinePaymentProof = encodeMachinePaymentProof;
892
1394
  exports.encodePaymentProof = encodePaymentProof;
893
1395
  exports.havenTools = havenTools;
1396
+ exports.parseMachinePaymentChallenge = parseMachinePaymentChallenge;
1397
+ exports.parseMachinePaymentChallengeResponse = parseMachinePaymentChallengeResponse;
894
1398
  exports.parsePaymentRequired = parsePaymentRequired;
895
1399
  exports.parsePaymentRequiredResponse = parsePaymentRequiredResponse;
896
1400
  exports.selectPaymentOption = selectPaymentOption;