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