@haven_ai/sdk 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -11
- package/dist/index.cjs +774 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +358 -7
- package/dist/index.d.ts +358 -7
- package/dist/index.js +763 -38
- package/dist/index.js.map +1 -1
- package/examples/mcp-x402-sse.ts +149 -0
- package/examples/x402_openapi_python.py +114 -0
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var async_hooks = require('async_hooks');
|
|
3
4
|
var schemes = require('x402/schemes');
|
|
4
5
|
var accounts = require('viem/accounts');
|
|
5
6
|
var ethers = require('ethers');
|
|
@@ -8,6 +9,102 @@ var crypto = require('crypto');
|
|
|
8
9
|
// src/client.ts
|
|
9
10
|
|
|
10
11
|
// src/types.ts
|
|
12
|
+
var AgentPaymentPhase = {
|
|
13
|
+
/** The agent must sign and submit the prepared payment before Haven can relay it. */
|
|
14
|
+
AgentSignatureRequired: "agent_signature_required",
|
|
15
|
+
/** Haven has received the signed payment and the agent should poll for confirmation. */
|
|
16
|
+
PaymentSubmitted: "payment_submitted",
|
|
17
|
+
/** The direct payment is confirmed; the agent does not need to do more for this payment id. */
|
|
18
|
+
PaymentConfirmed: "payment_confirmed",
|
|
19
|
+
/** The payment needs wallet owner approval in Haven before it can continue. */
|
|
20
|
+
UserApprovalRequired: "user_approval_required",
|
|
21
|
+
/** The wallet owner approved the request and still needs to complete the funding payment. */
|
|
22
|
+
UserExecutionRequired: "user_execution_required",
|
|
23
|
+
/** The funding payment was proposed and is waiting for the remaining account approvals. */
|
|
24
|
+
WaitingForAdditionalApprovals: "waiting_for_additional_approvals",
|
|
25
|
+
/** The Haven funding leg was sent; the agent can continue the merchant/protocol leg. */
|
|
26
|
+
FundingSent: "funding_sent",
|
|
27
|
+
/** The wallet owner rejected the request; the agent should stop and tell the user. */
|
|
28
|
+
Rejected: "rejected",
|
|
29
|
+
/** The payment or approval request expired before completion. */
|
|
30
|
+
Expired: "expired",
|
|
31
|
+
/** Haven could not complete the payment; the agent should stop and surface the failure. */
|
|
32
|
+
Failed: "failed"
|
|
33
|
+
};
|
|
34
|
+
var AgentPaymentNextAction = {
|
|
35
|
+
/** Sign with the delegate key and submit the payment to Haven. */
|
|
36
|
+
SignAndSubmitPayment: "sign_and_submit_payment",
|
|
37
|
+
/** Poll getPaymentStatus later using this payment id. */
|
|
38
|
+
CheckStatusLater: "check_status_later",
|
|
39
|
+
/** No further agent action is required for this payment id. */
|
|
40
|
+
None: "none",
|
|
41
|
+
/** Wait for the wallet owner to approve or reject the request in Haven. */
|
|
42
|
+
WaitForUserApproval: "wait_for_user_approval",
|
|
43
|
+
/** Wait for the wallet owner to finish sending the approved funding payment. */
|
|
44
|
+
WaitForUserToCompletePayment: "wait_for_user_to_complete_payment",
|
|
45
|
+
/** Resume this payment id and retry the original x402 request with the merchant payment header. */
|
|
46
|
+
RetryOriginalX402Request: "retry_original_x402_request",
|
|
47
|
+
/** Stop retrying this payment and tell the user what happened. */
|
|
48
|
+
StopAndTellUser: "stop_and_tell_user",
|
|
49
|
+
/** Ask again only if the user still wants the payment after expiry. */
|
|
50
|
+
RequestAgainIfUserStillWantsIt: "request_again_if_user_still_wants_it"
|
|
51
|
+
};
|
|
52
|
+
var AgentPaymentRail = {
|
|
53
|
+
/** Standard Haven payment from the user's Safe through an approved delegate allowance. */
|
|
54
|
+
Direct: "direct",
|
|
55
|
+
/** x402 HTTP 402 payment flow with a Haven funding leg and merchant retry leg. */
|
|
56
|
+
X402: "x402",
|
|
57
|
+
/** Machine Payment Protocol flow. */
|
|
58
|
+
Mpp: "mpp"
|
|
59
|
+
};
|
|
60
|
+
var AGENT_PAYMENT_PHASE_VALUES = Object.values(AgentPaymentPhase);
|
|
61
|
+
var AGENT_PAYMENT_NEXT_ACTION_VALUES = Object.values(AgentPaymentNextAction);
|
|
62
|
+
var AGENT_PAYMENT_RAIL_VALUES = Object.values(AgentPaymentRail);
|
|
63
|
+
var AgentPaymentPhaseDescriptions = {
|
|
64
|
+
[AgentPaymentPhase.AgentSignatureRequired]: "The agent must sign and submit the prepared payment before Haven can relay it.",
|
|
65
|
+
[AgentPaymentPhase.PaymentSubmitted]: "Haven has received the signed payment and the agent should poll for confirmation.",
|
|
66
|
+
[AgentPaymentPhase.PaymentConfirmed]: "The direct payment is confirmed; the agent does not need to do more for this payment id.",
|
|
67
|
+
[AgentPaymentPhase.UserApprovalRequired]: "The payment needs wallet owner approval in Haven before it can continue.",
|
|
68
|
+
[AgentPaymentPhase.UserExecutionRequired]: "The wallet owner approved the request and still needs to complete the funding payment.",
|
|
69
|
+
[AgentPaymentPhase.WaitingForAdditionalApprovals]: "The funding payment was proposed and is waiting for the remaining account approvals.",
|
|
70
|
+
[AgentPaymentPhase.FundingSent]: "The Haven funding leg was sent; the agent can continue the merchant/protocol leg.",
|
|
71
|
+
[AgentPaymentPhase.Rejected]: "The wallet owner rejected the request; the agent should stop and tell the user.",
|
|
72
|
+
[AgentPaymentPhase.Expired]: "The payment or approval request expired before completion.",
|
|
73
|
+
[AgentPaymentPhase.Failed]: "Haven could not complete the payment; the agent should stop and surface the failure."
|
|
74
|
+
};
|
|
75
|
+
var AgentPaymentNextActionDescriptions = {
|
|
76
|
+
[AgentPaymentNextAction.SignAndSubmitPayment]: "Sign with the delegate key and submit the payment to Haven.",
|
|
77
|
+
[AgentPaymentNextAction.CheckStatusLater]: "Poll getPaymentStatus later using this payment id.",
|
|
78
|
+
[AgentPaymentNextAction.None]: "No further agent action is required for this payment id.",
|
|
79
|
+
[AgentPaymentNextAction.WaitForUserApproval]: "Wait for the wallet owner to approve or reject the request in Haven.",
|
|
80
|
+
[AgentPaymentNextAction.WaitForUserToCompletePayment]: "Wait for the wallet owner to finish sending the approved funding payment.",
|
|
81
|
+
[AgentPaymentNextAction.RetryOriginalX402Request]: "Resume this payment id and retry the original x402 request with the merchant payment header.",
|
|
82
|
+
[AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
|
|
83
|
+
[AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry."
|
|
84
|
+
};
|
|
85
|
+
var AgentPaymentRailDescriptions = {
|
|
86
|
+
[AgentPaymentRail.Direct]: "Standard Haven payment from the user-controlled Safe through an approved delegate allowance.",
|
|
87
|
+
[AgentPaymentRail.X402]: "x402 HTTP 402 payment flow with a Haven funding leg and merchant retry leg.",
|
|
88
|
+
[AgentPaymentRail.Mpp]: "Machine Payment Protocol flow."
|
|
89
|
+
};
|
|
90
|
+
var AgentPaymentPhaseSchema = {
|
|
91
|
+
type: "string",
|
|
92
|
+
enum: AGENT_PAYMENT_PHASE_VALUES,
|
|
93
|
+
description: "Stable Haven agent payment state phase.",
|
|
94
|
+
"x-enumDescriptions": AgentPaymentPhaseDescriptions
|
|
95
|
+
};
|
|
96
|
+
var AgentPaymentNextActionSchema = {
|
|
97
|
+
type: "string",
|
|
98
|
+
enum: AGENT_PAYMENT_NEXT_ACTION_VALUES,
|
|
99
|
+
description: "Stable next action an agent should take for a Haven payment state.",
|
|
100
|
+
"x-enumDescriptions": AgentPaymentNextActionDescriptions
|
|
101
|
+
};
|
|
102
|
+
var AgentPaymentRailSchema = {
|
|
103
|
+
type: "string",
|
|
104
|
+
enum: AGENT_PAYMENT_RAIL_VALUES,
|
|
105
|
+
description: "Stable rail identifier for Haven agent payment states.",
|
|
106
|
+
"x-enumDescriptions": AgentPaymentRailDescriptions
|
|
107
|
+
};
|
|
11
108
|
var HavenError = class extends Error {
|
|
12
109
|
constructor(message, code, statusCode, paymentId) {
|
|
13
110
|
super(message);
|
|
@@ -35,6 +132,7 @@ var HavenPaymentStateError = class extends HavenApiError {
|
|
|
35
132
|
this.name = "HavenPaymentStateError";
|
|
36
133
|
}
|
|
37
134
|
state;
|
|
135
|
+
resumeState;
|
|
38
136
|
get status() {
|
|
39
137
|
return this.state.status;
|
|
40
138
|
}
|
|
@@ -162,6 +260,10 @@ var BASE_TOKENS = {
|
|
|
162
260
|
"0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
|
|
163
261
|
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { symbol: "USDC", decimals: 6 }
|
|
164
262
|
};
|
|
263
|
+
var ALL_TOKENS = {
|
|
264
|
+
...GNOSIS_TOKENS,
|
|
265
|
+
...BASE_TOKENS
|
|
266
|
+
};
|
|
165
267
|
var NETWORK_TOKENS = {
|
|
166
268
|
"eip155:100": GNOSIS_TOKENS,
|
|
167
269
|
"eip155:8453": BASE_TOKENS,
|
|
@@ -273,6 +375,13 @@ function encodePaymentProof(receipt) {
|
|
|
273
375
|
};
|
|
274
376
|
return btoa(JSON.stringify(payload));
|
|
275
377
|
}
|
|
378
|
+
function resolveTokenFromAddress(address, network) {
|
|
379
|
+
const lower = address.toLowerCase();
|
|
380
|
+
if (network && network in NETWORK_TOKENS) {
|
|
381
|
+
return NETWORK_TOKENS[network][lower] ?? null;
|
|
382
|
+
}
|
|
383
|
+
return ALL_TOKENS[lower] ?? null;
|
|
384
|
+
}
|
|
276
385
|
function decodeBase64Json2(value, label) {
|
|
277
386
|
try {
|
|
278
387
|
return JSON.parse(atob(value));
|
|
@@ -382,30 +491,33 @@ function chainIdFromNetwork(network) {
|
|
|
382
491
|
const chainId = Number(network.slice("eip155:".length));
|
|
383
492
|
return Number.isFinite(chainId) ? chainId : void 0;
|
|
384
493
|
}
|
|
494
|
+
function chainIdOrNull(network) {
|
|
495
|
+
return chainIdFromNetwork(network) ?? null;
|
|
496
|
+
}
|
|
385
497
|
function phaseForStatus(status) {
|
|
386
|
-
if (status === "pending_signature") return
|
|
387
|
-
if (status === "submitted") return
|
|
388
|
-
if (status === "confirmed") return
|
|
389
|
-
if (status === "pending" || status === "pending_approval") return
|
|
390
|
-
if (status === "approved") return
|
|
391
|
-
if (status === "proposed") return
|
|
392
|
-
if (status === "executed") return
|
|
393
|
-
if (status === "rejected") return
|
|
394
|
-
if (status === "expired") return
|
|
395
|
-
if (status === "failed") return
|
|
498
|
+
if (status === "pending_signature") return AgentPaymentPhase.AgentSignatureRequired;
|
|
499
|
+
if (status === "submitted") return AgentPaymentPhase.PaymentSubmitted;
|
|
500
|
+
if (status === "confirmed") return AgentPaymentPhase.PaymentConfirmed;
|
|
501
|
+
if (status === "pending" || status === "pending_approval") return AgentPaymentPhase.UserApprovalRequired;
|
|
502
|
+
if (status === "approved") return AgentPaymentPhase.UserExecutionRequired;
|
|
503
|
+
if (status === "proposed") return AgentPaymentPhase.WaitingForAdditionalApprovals;
|
|
504
|
+
if (status === "executed") return AgentPaymentPhase.FundingSent;
|
|
505
|
+
if (status === "rejected") return AgentPaymentPhase.Rejected;
|
|
506
|
+
if (status === "expired") return AgentPaymentPhase.Expired;
|
|
507
|
+
if (status === "failed") return AgentPaymentPhase.Failed;
|
|
396
508
|
return null;
|
|
397
509
|
}
|
|
398
510
|
function nextActionForStatus(status) {
|
|
399
|
-
if (status === "pending_signature") return
|
|
400
|
-
if (status === "submitted") return
|
|
401
|
-
if (status === "confirmed") return
|
|
402
|
-
if (status === "pending" || status === "pending_approval") return
|
|
403
|
-
if (status === "approved") return
|
|
404
|
-
if (status === "proposed") return
|
|
405
|
-
if (status === "executed") return
|
|
406
|
-
if (status === "rejected") return
|
|
407
|
-
if (status === "expired") return
|
|
408
|
-
if (status === "failed") return
|
|
511
|
+
if (status === "pending_signature") return AgentPaymentNextAction.SignAndSubmitPayment;
|
|
512
|
+
if (status === "submitted") return AgentPaymentNextAction.CheckStatusLater;
|
|
513
|
+
if (status === "confirmed") return AgentPaymentNextAction.None;
|
|
514
|
+
if (status === "pending" || status === "pending_approval") return AgentPaymentNextAction.WaitForUserApproval;
|
|
515
|
+
if (status === "approved") return AgentPaymentNextAction.WaitForUserToCompletePayment;
|
|
516
|
+
if (status === "proposed") return AgentPaymentNextAction.WaitForUserApproval;
|
|
517
|
+
if (status === "executed") return AgentPaymentNextAction.RetryOriginalX402Request;
|
|
518
|
+
if (status === "rejected") return AgentPaymentNextAction.StopAndTellUser;
|
|
519
|
+
if (status === "expired") return AgentPaymentNextAction.RequestAgainIfUserStillWantsIt;
|
|
520
|
+
if (status === "failed") return AgentPaymentNextAction.StopAndTellUser;
|
|
409
521
|
return null;
|
|
410
522
|
}
|
|
411
523
|
function messageForState(label, status, paymentId, nextAction) {
|
|
@@ -426,6 +538,9 @@ function messageForState(label, status, paymentId, nextAction) {
|
|
|
426
538
|
function sameAddress(a, b) {
|
|
427
539
|
return Boolean(a && b && a.toLowerCase() === b.toLowerCase());
|
|
428
540
|
}
|
|
541
|
+
function isMppRail(rail) {
|
|
542
|
+
return rail === "mpp" || Boolean(rail?.startsWith("mpp_"));
|
|
543
|
+
}
|
|
429
544
|
function decimalFromUsdcAtomic(value) {
|
|
430
545
|
const amount = BigInt(value);
|
|
431
546
|
const whole = amount / 1000000n;
|
|
@@ -456,6 +571,19 @@ var HavenClient = class {
|
|
|
456
571
|
inFlightX402 = /* @__PURE__ */ new Map();
|
|
457
572
|
x402ReceiptCache = /* @__PURE__ */ new Map();
|
|
458
573
|
inFlightMachinePayments = /* @__PURE__ */ new Map();
|
|
574
|
+
/**
|
|
575
|
+
* Setup-time headers configured via `HavenClientConfig.defaultHeaders`.
|
|
576
|
+
* Read-only after construction — use `withRequestContext` for per-call
|
|
577
|
+
* scoping so concurrent requests don't race on shared mutable state.
|
|
578
|
+
*/
|
|
579
|
+
defaultHeaders;
|
|
580
|
+
/**
|
|
581
|
+
* Async-local store for per-request context (currently: extra headers).
|
|
582
|
+
* Each `withRequestContext` invocation produces an isolated store, so
|
|
583
|
+
* overlapping async work — like two MCP tool dispatches in flight at
|
|
584
|
+
* the same time — see their own headers without stepping on each other.
|
|
585
|
+
*/
|
|
586
|
+
requestContext = new async_hooks.AsyncLocalStorage();
|
|
459
587
|
/** Delegate address derived from the private key (if provided) */
|
|
460
588
|
delegateAddress;
|
|
461
589
|
constructor(config) {
|
|
@@ -466,10 +594,29 @@ var HavenClient = class {
|
|
|
466
594
|
this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
467
595
|
this.confirmationTimeout = config.confirmationTimeout ?? DEFAULT_CONFIRMATION_TIMEOUT;
|
|
468
596
|
this.pollingInterval = config.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
|
|
597
|
+
this.defaultHeaders = { ...config.defaultHeaders ?? {} };
|
|
469
598
|
if (this.delegateKey) {
|
|
470
599
|
this.delegateAddress = addressFromKey(this.delegateKey);
|
|
471
600
|
}
|
|
472
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* Run `fn` with extra Haven-API headers scoped to the async work it
|
|
604
|
+
* performs. Used by the MCP server to tag every Haven API request that
|
|
605
|
+
* a single tool dispatch makes with `X-Haven-MCP-Tool: <name>` so the
|
|
606
|
+
* backend can write an audit-log row attributing the call.
|
|
607
|
+
*
|
|
608
|
+
* The headers are held in an `AsyncLocalStorage` so overlapping
|
|
609
|
+
* dispatches do not leak headers into each other's requests. The store
|
|
610
|
+
* inherits across `await` boundaries, so any Haven API call made while
|
|
611
|
+
* `fn` is awaiting will pick up the right headers.
|
|
612
|
+
*
|
|
613
|
+
* Has no effect on outbound merchant requests (x402 / MPP) — those
|
|
614
|
+
* never go through the internal `request<T>` path that reads the
|
|
615
|
+
* context.
|
|
616
|
+
*/
|
|
617
|
+
withRequestContext(headers, fn) {
|
|
618
|
+
return this.requestContext.run({ headers: { ...headers } }, fn);
|
|
619
|
+
}
|
|
473
620
|
// ── High-Level API ───────────────────────────────────────────────
|
|
474
621
|
/**
|
|
475
622
|
* Send a payment in one call.
|
|
@@ -564,6 +711,66 @@ var HavenClient = class {
|
|
|
564
711
|
const raw = await this.get(`/machine-payments/${paymentId}/status`);
|
|
565
712
|
return this.mapPaymentStatusResult(raw);
|
|
566
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* Get the agent identity tied to this API key.
|
|
716
|
+
*/
|
|
717
|
+
async getAgent() {
|
|
718
|
+
const raw = await this.get("/machine-payments/agent");
|
|
719
|
+
return {
|
|
720
|
+
id: raw.id,
|
|
721
|
+
name: raw.name,
|
|
722
|
+
status: raw.status,
|
|
723
|
+
safeAddress: raw.safe_address,
|
|
724
|
+
delegateAddress: raw.delegate_address,
|
|
725
|
+
chainId: raw.chain_id
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Get configured and on-chain allowances for the authenticated agent.
|
|
730
|
+
*/
|
|
731
|
+
async getAllowances() {
|
|
732
|
+
const raw = await this.get("/machine-payments/allowances");
|
|
733
|
+
return {
|
|
734
|
+
agentId: raw.agent_id,
|
|
735
|
+
safeAddress: raw.safe_address,
|
|
736
|
+
delegateAddress: raw.delegate_address,
|
|
737
|
+
chainId: raw.chain_id,
|
|
738
|
+
allowances: raw.allowances.map((allowance) => ({
|
|
739
|
+
id: allowance.id,
|
|
740
|
+
tokenAddress: allowance.token_address,
|
|
741
|
+
tokenSymbol: allowance.token_symbol,
|
|
742
|
+
configuredAmount: allowance.configured_amount,
|
|
743
|
+
resetPeriodMin: allowance.reset_period_min,
|
|
744
|
+
onchain: {
|
|
745
|
+
amount: allowance.onchain.amount,
|
|
746
|
+
spent: allowance.onchain.spent,
|
|
747
|
+
remaining: allowance.onchain.remaining,
|
|
748
|
+
effectiveSpent: allowance.onchain.effective_spent,
|
|
749
|
+
resetTimeMin: allowance.onchain.reset_time_min,
|
|
750
|
+
lastResetMin: allowance.onchain.last_reset_min,
|
|
751
|
+
nonce: allowance.onchain.nonce,
|
|
752
|
+
isResetPending: allowance.onchain.is_reset_pending
|
|
753
|
+
}
|
|
754
|
+
}))
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* List recent machine-payment receipts/evidence for bookkeeping.
|
|
759
|
+
*/
|
|
760
|
+
async listReceipts(options = {}) {
|
|
761
|
+
const query = options.limit ? `?limit=${encodeURIComponent(String(options.limit))}` : "";
|
|
762
|
+
const raw = await this.get(`/machine-payments/receipts${query}`);
|
|
763
|
+
return raw.receipts.map((receipt) => this.mapPaymentReceipt(receipt));
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Rehydrate the x402/MPP resume-state bundle for a payment id.
|
|
767
|
+
*
|
|
768
|
+
* The server returns stored protocol context only. The client still signs the
|
|
769
|
+
* merchant proof locally when resumeX402Payment() or resumeMppPayment() runs.
|
|
770
|
+
*/
|
|
771
|
+
async getResumeState(paymentId) {
|
|
772
|
+
return this.get(`/payments/${paymentId}/resume_state`);
|
|
773
|
+
}
|
|
567
774
|
/**
|
|
568
775
|
* Poll until a payment reaches a terminal status (confirmed, failed, expired).
|
|
569
776
|
*/
|
|
@@ -613,10 +820,62 @@ var HavenClient = class {
|
|
|
613
820
|
this.inFlightX402.set(idempotencyKey, promise);
|
|
614
821
|
try {
|
|
615
822
|
return await promise;
|
|
823
|
+
} catch (err) {
|
|
824
|
+
this.attachResumeState(err, {
|
|
825
|
+
rail: "x402",
|
|
826
|
+
paymentRequired,
|
|
827
|
+
accepted: option,
|
|
828
|
+
idempotencyKey
|
|
829
|
+
});
|
|
830
|
+
throw err;
|
|
616
831
|
} finally {
|
|
617
832
|
this.inFlightX402.delete(idempotencyKey);
|
|
618
833
|
}
|
|
619
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* Probe a paid endpoint and return its x402 quote without creating a Haven
|
|
837
|
+
* payment or approval request.
|
|
838
|
+
*/
|
|
839
|
+
async quoteX402(url, init, options = {}) {
|
|
840
|
+
const initialInit = this.withX402Wallet(init, this.x402PayerAddress());
|
|
841
|
+
const request = this.snapshotX402Request(url, initialInit);
|
|
842
|
+
const response = await globalThis.fetch(url, initialInit);
|
|
843
|
+
if (response.status !== 402) {
|
|
844
|
+
throw new HavenApiError(
|
|
845
|
+
`Expected an x402 quote response with HTTP 402, got HTTP ${response.status}.`,
|
|
846
|
+
response.status || 400
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (response.headers.get("MACHINE-PAYMENT-CHALLENGE")) {
|
|
850
|
+
throw new HavenApiError("quoteX402 only supports standard x402 Payment Required responses.", 400);
|
|
851
|
+
}
|
|
852
|
+
const paymentRequired = await parsePaymentRequiredResponse(response);
|
|
853
|
+
return this.buildX402Quote(paymentRequired, request, options.idempotencyKey);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Pay a previously inspected x402 quote and retry the exact captured request.
|
|
857
|
+
*/
|
|
858
|
+
async payX402Quote(quote, options = {}) {
|
|
859
|
+
const idempotencyKey = options.idempotencyKey ?? quote.idempotencyKey;
|
|
860
|
+
try {
|
|
861
|
+
const receipt = await this.authorizeX402(quote.paymentRequired, { idempotencyKey });
|
|
862
|
+
return this.retryX402Request(
|
|
863
|
+
quote.request.url,
|
|
864
|
+
this.requestInitFromSnapshot(quote.request),
|
|
865
|
+
quote.paymentRequired,
|
|
866
|
+
receipt
|
|
867
|
+
);
|
|
868
|
+
} catch (err) {
|
|
869
|
+
this.attachResumeState(err, {
|
|
870
|
+
rail: "x402",
|
|
871
|
+
paymentRequired: quote.paymentRequired,
|
|
872
|
+
accepted: quote.accepted,
|
|
873
|
+
idempotencyKey,
|
|
874
|
+
request: quote.request
|
|
875
|
+
});
|
|
876
|
+
throw err;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
620
879
|
async authorizeStandardX402(paymentRequired, option, idempotencyKey) {
|
|
621
880
|
const paymentHeader = await this.createStandardX402Header(paymentRequired, option);
|
|
622
881
|
const raw = await this.post("/x402", {
|
|
@@ -635,7 +894,7 @@ var HavenClient = class {
|
|
|
635
894
|
return receipt2;
|
|
636
895
|
}
|
|
637
896
|
const state = this.paymentStateFromRaw("x402 payment", raw);
|
|
638
|
-
if (state?.nextAction ===
|
|
897
|
+
if (state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request) {
|
|
639
898
|
const receipt2 = this.mapX402ReceiptFromStatus(paymentRequired, option, paymentHeader, state);
|
|
640
899
|
this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
|
|
641
900
|
return receipt2;
|
|
@@ -683,10 +942,18 @@ var HavenClient = class {
|
|
|
683
942
|
return receipt;
|
|
684
943
|
}
|
|
685
944
|
async resumeX402Payment(input) {
|
|
686
|
-
const
|
|
945
|
+
const inputInit = "init" in input ? input.init : void 0;
|
|
946
|
+
const initialInit = this.withX402Wallet(
|
|
947
|
+
inputInit ?? (input.request ? this.requestInitFromSnapshot(input.request) : void 0),
|
|
948
|
+
this.x402PayerAddress()
|
|
949
|
+
);
|
|
687
950
|
let paymentRequired = input.paymentRequired;
|
|
951
|
+
const url = input.url ?? input.request?.url;
|
|
688
952
|
if (!paymentRequired) {
|
|
689
|
-
|
|
953
|
+
if (!url) {
|
|
954
|
+
throw new HavenApiError("x402 resume requires the original URL or a captured request snapshot.", 400);
|
|
955
|
+
}
|
|
956
|
+
const response = await globalThis.fetch(url, initialInit);
|
|
690
957
|
if (response.status !== 402) {
|
|
691
958
|
throw new HavenApiError("Expected the original x402 request to return HTTP 402 before resuming.", 400);
|
|
692
959
|
}
|
|
@@ -697,7 +964,7 @@ var HavenClient = class {
|
|
|
697
964
|
paymentRequired,
|
|
698
965
|
idempotencyKey: input.idempotencyKey
|
|
699
966
|
});
|
|
700
|
-
return this.retryX402Request(
|
|
967
|
+
return this.retryX402Request(url ?? paymentRequired.resource.url, initialInit, paymentRequired, receipt);
|
|
701
968
|
}
|
|
702
969
|
/**
|
|
703
970
|
* Fetch wrapper that automatically handles HTTP 402 responses.
|
|
@@ -733,9 +1000,69 @@ var HavenClient = class {
|
|
|
733
1000
|
}
|
|
734
1001
|
return this.fetchWithMachinePayment(url, initialInit, challenge);
|
|
735
1002
|
}
|
|
736
|
-
const
|
|
1003
|
+
const request = this.snapshotX402Request(url, initialInit);
|
|
1004
|
+
const option = selectStandardPaymentOption(paymentRequired.accepts);
|
|
1005
|
+
const idempotencyKey = options.idempotencyKey ?? (option ? buildX402IdempotencyKey(paymentRequired, option) : void 0);
|
|
1006
|
+
let receipt;
|
|
1007
|
+
try {
|
|
1008
|
+
receipt = await this.authorizeX402(paymentRequired, options);
|
|
1009
|
+
} catch (err) {
|
|
1010
|
+
if (option && idempotencyKey) {
|
|
1011
|
+
this.attachResumeState(err, {
|
|
1012
|
+
rail: "x402",
|
|
1013
|
+
paymentRequired,
|
|
1014
|
+
accepted: option,
|
|
1015
|
+
idempotencyKey,
|
|
1016
|
+
request
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
throw err;
|
|
1020
|
+
}
|
|
737
1021
|
return this.retryX402Request(url, initialInit, paymentRequired, receipt);
|
|
738
1022
|
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Probe a paid MPP endpoint or inspect an existing challenge without creating
|
|
1025
|
+
* a Haven payment or approval request.
|
|
1026
|
+
*/
|
|
1027
|
+
async quoteMpp(challengeOrUrl, init, options = {}) {
|
|
1028
|
+
if (typeof challengeOrUrl !== "string") {
|
|
1029
|
+
const request2 = this.snapshotX402Request(challengeOrUrl.resource, init);
|
|
1030
|
+
return this.buildMppQuote(challengeOrUrl, request2, options.idempotencyKey);
|
|
1031
|
+
}
|
|
1032
|
+
const request = this.snapshotX402Request(challengeOrUrl, init);
|
|
1033
|
+
const response = await globalThis.fetch(challengeOrUrl, init);
|
|
1034
|
+
if (response.status !== 402) {
|
|
1035
|
+
throw new HavenApiError(
|
|
1036
|
+
`Expected an MPP quote response with HTTP 402, got HTTP ${response.status}.`,
|
|
1037
|
+
response.status || 400
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
const challenge = await parseMachinePaymentChallengeResponse(response);
|
|
1041
|
+
return this.buildMppQuote(challenge, request, options.idempotencyKey);
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Pay a previously inspected MPP quote and retry the exact captured request.
|
|
1045
|
+
*/
|
|
1046
|
+
async payMppChallenge(quote, options = {}) {
|
|
1047
|
+
const idempotencyKey = options.idempotencyKey ?? quote.idempotencyKey;
|
|
1048
|
+
try {
|
|
1049
|
+
const receipt = await this.authorizeMachinePayment(quote.challenge, { idempotencyKey });
|
|
1050
|
+
return this.retryMppRequest(
|
|
1051
|
+
quote.request.url,
|
|
1052
|
+
this.requestInitFromSnapshot(quote.request),
|
|
1053
|
+
quote.challenge,
|
|
1054
|
+
receipt
|
|
1055
|
+
);
|
|
1056
|
+
} catch (err) {
|
|
1057
|
+
this.attachResumeState(err, {
|
|
1058
|
+
rail: "mpp",
|
|
1059
|
+
challenge: quote.challenge,
|
|
1060
|
+
idempotencyKey,
|
|
1061
|
+
request: quote.request
|
|
1062
|
+
});
|
|
1063
|
+
throw err;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
739
1066
|
async retryX402Request(url, initialInit, paymentRequired, receipt) {
|
|
740
1067
|
if (!receipt.accepted) {
|
|
741
1068
|
throw new HavenApiError("No accepted x402 option was recorded for payment retry", 500);
|
|
@@ -797,7 +1124,7 @@ var HavenClient = class {
|
|
|
797
1124
|
});
|
|
798
1125
|
return retryResponse;
|
|
799
1126
|
}
|
|
800
|
-
async authorizeMachinePayment(challenge) {
|
|
1127
|
+
async authorizeMachinePayment(challenge, options = {}) {
|
|
801
1128
|
if (!this.delegateKey) {
|
|
802
1129
|
throw new HavenSigningError(
|
|
803
1130
|
"delegateKey is required for machine payments. Pass it in the HavenClient config."
|
|
@@ -806,13 +1133,20 @@ var HavenClient = class {
|
|
|
806
1133
|
if (challenge.rail !== "mpp_demo") {
|
|
807
1134
|
throw new HavenApiError(`Unsupported machine payment rail: ${challenge.rail}`, 400);
|
|
808
1135
|
}
|
|
809
|
-
const idempotencyKey = buildMachinePaymentIdempotencyKey(challenge);
|
|
1136
|
+
const idempotencyKey = options.idempotencyKey ?? buildMachinePaymentIdempotencyKey(challenge);
|
|
810
1137
|
const inFlight = this.inFlightMachinePayments.get(idempotencyKey);
|
|
811
1138
|
if (inFlight) return inFlight;
|
|
812
1139
|
const promise = this.authorizeMppDemoPayment(challenge, idempotencyKey);
|
|
813
1140
|
this.inFlightMachinePayments.set(idempotencyKey, promise);
|
|
814
1141
|
try {
|
|
815
1142
|
return await promise;
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
this.attachResumeState(err, {
|
|
1145
|
+
rail: "mpp",
|
|
1146
|
+
challenge,
|
|
1147
|
+
idempotencyKey
|
|
1148
|
+
});
|
|
1149
|
+
throw err;
|
|
816
1150
|
} finally {
|
|
817
1151
|
this.inFlightMachinePayments.delete(idempotencyKey);
|
|
818
1152
|
}
|
|
@@ -839,8 +1173,56 @@ var HavenClient = class {
|
|
|
839
1173
|
}
|
|
840
1174
|
return this.mapMachinePaymentReceipt(challenge, raw, execResult.tx_hash, execResult);
|
|
841
1175
|
}
|
|
1176
|
+
async resumeAuthorizedMpp(input) {
|
|
1177
|
+
if (!this.delegateKey) {
|
|
1178
|
+
throw new HavenSigningError(
|
|
1179
|
+
"delegateKey is required for machine payments. Pass it in the HavenClient config."
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
const status = await this.getPaymentStatus(input.paymentId);
|
|
1183
|
+
this.assertCanResumeMpp(status, input.challenge);
|
|
1184
|
+
return this.mapMachinePaymentReceiptFromStatus(input.challenge, status);
|
|
1185
|
+
}
|
|
1186
|
+
async resumeMppPayment(input) {
|
|
1187
|
+
const inputInit = "init" in input ? input.init : void 0;
|
|
1188
|
+
const initialInit = inputInit ?? (input.request ? this.requestInitFromSnapshot(input.request) : void 0);
|
|
1189
|
+
let challenge = input.challenge;
|
|
1190
|
+
const url = input.url ?? input.request?.url;
|
|
1191
|
+
if (!challenge) {
|
|
1192
|
+
if (!url) {
|
|
1193
|
+
throw new HavenApiError("MPP resume requires the original URL or a captured request snapshot.", 400);
|
|
1194
|
+
}
|
|
1195
|
+
const response = await globalThis.fetch(url, initialInit);
|
|
1196
|
+
if (response.status !== 402) {
|
|
1197
|
+
throw new HavenApiError("Expected the original MPP request to return HTTP 402 before resuming.", 400);
|
|
1198
|
+
}
|
|
1199
|
+
challenge = await parseMachinePaymentChallengeResponse(response);
|
|
1200
|
+
}
|
|
1201
|
+
const receipt = await this.resumeAuthorizedMpp({
|
|
1202
|
+
paymentId: input.paymentId,
|
|
1203
|
+
challenge,
|
|
1204
|
+
idempotencyKey: input.idempotencyKey
|
|
1205
|
+
});
|
|
1206
|
+
return this.retryMppRequest(url ?? challenge.resource, initialInit, challenge, receipt);
|
|
1207
|
+
}
|
|
842
1208
|
async fetchWithMachinePayment(url, initialInit, challenge) {
|
|
843
|
-
const
|
|
1209
|
+
const request = this.snapshotX402Request(url, initialInit);
|
|
1210
|
+
const idempotencyKey = buildMachinePaymentIdempotencyKey(challenge);
|
|
1211
|
+
let receipt;
|
|
1212
|
+
try {
|
|
1213
|
+
receipt = await this.authorizeMachinePayment(challenge, { idempotencyKey });
|
|
1214
|
+
} catch (err) {
|
|
1215
|
+
this.attachResumeState(err, {
|
|
1216
|
+
rail: "mpp",
|
|
1217
|
+
challenge,
|
|
1218
|
+
idempotencyKey,
|
|
1219
|
+
request
|
|
1220
|
+
});
|
|
1221
|
+
throw err;
|
|
1222
|
+
}
|
|
1223
|
+
return this.retryMppRequest(url, initialInit, challenge, receipt);
|
|
1224
|
+
}
|
|
1225
|
+
async retryMppRequest(url, initialInit, challenge, receipt) {
|
|
844
1226
|
const retryHeaders = new Headers(initialInit?.headers);
|
|
845
1227
|
retryHeaders.set("MACHINE-PAYMENT-PROOF", receipt.proofHeader);
|
|
846
1228
|
const retryResponse = await globalThis.fetch(url, {
|
|
@@ -892,7 +1274,7 @@ var HavenClient = class {
|
|
|
892
1274
|
status
|
|
893
1275
|
);
|
|
894
1276
|
}
|
|
895
|
-
if (status.nextAction !==
|
|
1277
|
+
if (status.nextAction !== AgentPaymentNextAction.RetryOriginalX402Request) {
|
|
896
1278
|
throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
|
|
897
1279
|
}
|
|
898
1280
|
if (!status.txHash) {
|
|
@@ -947,6 +1329,68 @@ var HavenClient = class {
|
|
|
947
1329
|
);
|
|
948
1330
|
}
|
|
949
1331
|
}
|
|
1332
|
+
assertCanResumeMpp(status, challenge) {
|
|
1333
|
+
if (!isMppRail(status.rail)) {
|
|
1334
|
+
throw new HavenPaymentStateError(
|
|
1335
|
+
`Payment ${status.paymentId} is ${status.rail}, not MPP.`,
|
|
1336
|
+
409,
|
|
1337
|
+
status
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
if (status.nextAction !== AgentPaymentNextAction.RetryOriginalX402Request) {
|
|
1341
|
+
throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
|
|
1342
|
+
}
|
|
1343
|
+
if (!status.txHash) {
|
|
1344
|
+
throw new HavenApiError(
|
|
1345
|
+
`MPP payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
|
|
1346
|
+
502,
|
|
1347
|
+
status,
|
|
1348
|
+
status.paymentId
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
if (status.resourceUrl && status.resourceUrl !== challenge.resource) {
|
|
1352
|
+
throw new HavenApiError(
|
|
1353
|
+
"MPP resume request does not match the approved resource URL.",
|
|
1354
|
+
409,
|
|
1355
|
+
{ status, challenge },
|
|
1356
|
+
status.paymentId
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
if (status.merchantAddress && !sameAddress(status.merchantAddress, challenge.recipient)) {
|
|
1360
|
+
throw new HavenApiError(
|
|
1361
|
+
"MPP resume request does not match the approved merchant.",
|
|
1362
|
+
409,
|
|
1363
|
+
{ status, challenge },
|
|
1364
|
+
status.paymentId
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
if (status.chainId && status.chainId !== challenge.network.chainId) {
|
|
1368
|
+
throw new HavenApiError(
|
|
1369
|
+
"MPP resume request does not match the approved network.",
|
|
1370
|
+
409,
|
|
1371
|
+
{ status, challenge },
|
|
1372
|
+
status.paymentId
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
if (status.token && status.token !== challenge.asset.symbol) {
|
|
1376
|
+
throw new HavenApiError(
|
|
1377
|
+
"MPP resume request does not match the approved token.",
|
|
1378
|
+
409,
|
|
1379
|
+
{ status, challenge },
|
|
1380
|
+
status.paymentId
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
const approvedAmount = status.amount ? normalizeDecimal(status.amount) : "";
|
|
1384
|
+
const requestedAmount = normalizeDecimal(challenge.amount.display);
|
|
1385
|
+
if (approvedAmount && approvedAmount !== requestedAmount) {
|
|
1386
|
+
throw new HavenApiError(
|
|
1387
|
+
"MPP resume request does not match the approved amount.",
|
|
1388
|
+
409,
|
|
1389
|
+
{ status, challenge },
|
|
1390
|
+
status.paymentId
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
950
1394
|
mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult) {
|
|
951
1395
|
const txHash = execResult?.tx_hash ?? raw.tx_hash ?? "";
|
|
952
1396
|
const chainId = execResult?.chain_id ?? raw.chain_id ?? chainIdFromNetwork(option.network);
|
|
@@ -1073,6 +1517,33 @@ var HavenClient = class {
|
|
|
1073
1517
|
proofHeader: encodeMachinePaymentProof(receiptWithoutHeader)
|
|
1074
1518
|
};
|
|
1075
1519
|
}
|
|
1520
|
+
mapMachinePaymentReceiptFromStatus(challenge, status) {
|
|
1521
|
+
if (!status.txHash) {
|
|
1522
|
+
throw new HavenApiError(
|
|
1523
|
+
`MPP payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
|
|
1524
|
+
502,
|
|
1525
|
+
status,
|
|
1526
|
+
status.paymentId
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
const receiptWithoutHeader = {
|
|
1530
|
+
success: true,
|
|
1531
|
+
rail: challenge.rail,
|
|
1532
|
+
paymentId: status.paymentId,
|
|
1533
|
+
challengeId: challenge.challengeId,
|
|
1534
|
+
txHash: status.txHash,
|
|
1535
|
+
token: status.token || challenge.asset.symbol,
|
|
1536
|
+
amount: status.amount || challenge.amount.display,
|
|
1537
|
+
to: status.merchantAddress ?? challenge.recipient,
|
|
1538
|
+
resourceUrl: status.resourceUrl ?? challenge.resource,
|
|
1539
|
+
explorerUrl: explorerUrlOrEmpty(status.chainId || challenge.network.chainId, status.txHash),
|
|
1540
|
+
chainId: status.chainId || challenge.network.chainId
|
|
1541
|
+
};
|
|
1542
|
+
return {
|
|
1543
|
+
...receiptWithoutHeader,
|
|
1544
|
+
proofHeader: encodeMachinePaymentProof(receiptWithoutHeader)
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1076
1547
|
async recordMerchantRetryRejected(input) {
|
|
1077
1548
|
try {
|
|
1078
1549
|
await this.post("/machine-payments/reconciliation-events", {
|
|
@@ -1156,16 +1627,64 @@ var HavenClient = class {
|
|
|
1156
1627
|
amount,
|
|
1157
1628
|
token,
|
|
1158
1629
|
resourceUrl: raw.resource_url ?? null,
|
|
1159
|
-
merchantAddress: raw.merchant_to ?? null,
|
|
1630
|
+
merchantAddress: raw.merchant_address ?? raw.merchant_to ?? null,
|
|
1160
1631
|
txHash: raw.tx_hash ?? null,
|
|
1161
1632
|
expiresAt: raw.expires_at ?? "",
|
|
1162
1633
|
chainId: raw.chain_id ?? 0,
|
|
1163
|
-
message
|
|
1634
|
+
message,
|
|
1635
|
+
amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? raw.mpp?.amount_atomic ?? null,
|
|
1636
|
+
asset: raw.asset ?? raw.x402?.asset ?? raw.mpp?.asset ?? null,
|
|
1637
|
+
network: raw.network ?? raw.x402?.network ?? raw.mpp?.network ?? null,
|
|
1638
|
+
description: raw.description ?? raw.x402?.description ?? raw.mpp?.description ?? null,
|
|
1639
|
+
idempotencyKey: raw.idempotency_key ?? raw.x402?.idempotency_key ?? raw.mpp?.idempotency_key ?? null,
|
|
1640
|
+
x402: raw.x402 ? {
|
|
1641
|
+
amountAtomic: raw.x402.amount_atomic ?? raw.amount_atomic ?? null,
|
|
1642
|
+
asset: raw.x402.asset ?? raw.asset ?? null,
|
|
1643
|
+
network: raw.x402.network ?? raw.network ?? null,
|
|
1644
|
+
resourceUrl: raw.x402.resource_url ?? raw.resource_url ?? null,
|
|
1645
|
+
merchantAddress: raw.x402.merchant_address ?? raw.merchant_address ?? raw.merchant_to ?? null,
|
|
1646
|
+
description: raw.x402.description ?? raw.description ?? null,
|
|
1647
|
+
idempotencyKey: raw.x402.idempotency_key ?? raw.idempotency_key ?? null
|
|
1648
|
+
} : void 0,
|
|
1649
|
+
mpp: raw.mpp ? {
|
|
1650
|
+
amountAtomic: raw.mpp.amount_atomic ?? raw.amount_atomic ?? null,
|
|
1651
|
+
asset: raw.mpp.asset ?? raw.asset ?? null,
|
|
1652
|
+
network: raw.mpp.network ?? raw.network ?? null,
|
|
1653
|
+
resourceUrl: raw.mpp.resource_url ?? raw.resource_url ?? null,
|
|
1654
|
+
merchantAddress: raw.mpp.merchant_address ?? raw.merchant_address ?? raw.merchant_to ?? null,
|
|
1655
|
+
description: raw.mpp.description ?? raw.description ?? null,
|
|
1656
|
+
idempotencyKey: raw.mpp.idempotency_key ?? raw.idempotency_key ?? null,
|
|
1657
|
+
challengeId: raw.mpp.challenge_id ?? raw.challenge_id ?? null
|
|
1658
|
+
} : void 0
|
|
1164
1659
|
};
|
|
1165
1660
|
}
|
|
1166
1661
|
x402PayerAddress() {
|
|
1167
1662
|
return this.delegateAddress ?? this.x402Wallet;
|
|
1168
1663
|
}
|
|
1664
|
+
snapshotX402Request(url, init) {
|
|
1665
|
+
return {
|
|
1666
|
+
url,
|
|
1667
|
+
method: init?.method ?? "GET",
|
|
1668
|
+
headers: Array.from(new Headers(init?.headers).entries()),
|
|
1669
|
+
body: this.snapshotRequestBody(init?.body)
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
snapshotRequestBody(body) {
|
|
1673
|
+
if (body == null) return void 0;
|
|
1674
|
+
if (typeof body === "string") return body;
|
|
1675
|
+
if (body instanceof URLSearchParams) return body.toString();
|
|
1676
|
+
throw new HavenApiError(
|
|
1677
|
+
"Quote helpers can only capture resumable request bodies that are strings or URLSearchParams. For streams, blobs, or binary bodies, preserve the original request yourself and call the matching resume method with fresh init.",
|
|
1678
|
+
400
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
requestInitFromSnapshot(request) {
|
|
1682
|
+
return {
|
|
1683
|
+
method: request.method,
|
|
1684
|
+
headers: request.headers,
|
|
1685
|
+
body: request.body
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1169
1688
|
withX402Wallet(init, wallet = this.x402PayerAddress()) {
|
|
1170
1689
|
if (!wallet) return init;
|
|
1171
1690
|
const headers = new Headers(init?.headers);
|
|
@@ -1177,6 +1696,134 @@ var HavenClient = class {
|
|
|
1177
1696
|
headers
|
|
1178
1697
|
};
|
|
1179
1698
|
}
|
|
1699
|
+
buildX402Quote(paymentRequired, request, idempotencyKey) {
|
|
1700
|
+
const option = selectStandardPaymentOption(paymentRequired.accepts);
|
|
1701
|
+
if (!option) {
|
|
1702
|
+
throw new HavenApiError(
|
|
1703
|
+
"No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC.",
|
|
1704
|
+
400
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
const token = resolveTokenFromAddress(option.asset, option.network);
|
|
1708
|
+
return {
|
|
1709
|
+
rail: "x402",
|
|
1710
|
+
idempotencyKey: idempotencyKey ?? buildX402IdempotencyKey(paymentRequired, option),
|
|
1711
|
+
paymentRequired,
|
|
1712
|
+
accepted: option,
|
|
1713
|
+
request,
|
|
1714
|
+
resourceUrl: paymentRequired.resource.url,
|
|
1715
|
+
description: paymentRequired.resource.description ?? option.description ?? null,
|
|
1716
|
+
mimeType: paymentRequired.resource.mimeType ?? option.mimeType ?? null,
|
|
1717
|
+
amountAtomic: option.amount,
|
|
1718
|
+
amount: decimalFromUsdcAtomic(option.amount),
|
|
1719
|
+
token: token?.symbol ?? "USDC",
|
|
1720
|
+
asset: option.asset,
|
|
1721
|
+
network: option.network,
|
|
1722
|
+
chainId: chainIdOrNull(option.network),
|
|
1723
|
+
merchantAddress: option.payTo,
|
|
1724
|
+
maxTimeoutSeconds: option.maxTimeoutSeconds
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
buildX402ResumeState(input) {
|
|
1728
|
+
const token = resolveTokenFromAddress(input.accepted.asset, input.accepted.network);
|
|
1729
|
+
return {
|
|
1730
|
+
rail: "x402",
|
|
1731
|
+
paymentId: input.paymentId,
|
|
1732
|
+
idempotencyKey: input.idempotencyKey,
|
|
1733
|
+
paymentRequired: input.paymentRequired,
|
|
1734
|
+
accepted: input.accepted,
|
|
1735
|
+
url: input.request?.url ?? input.paymentRequired.resource.url,
|
|
1736
|
+
request: input.request,
|
|
1737
|
+
resourceUrl: input.paymentRequired.resource.url,
|
|
1738
|
+
description: input.paymentRequired.resource.description ?? input.accepted.description ?? null,
|
|
1739
|
+
amountAtomic: input.accepted.amount,
|
|
1740
|
+
amount: decimalFromUsdcAtomic(input.accepted.amount),
|
|
1741
|
+
token: token?.symbol ?? "USDC",
|
|
1742
|
+
asset: input.accepted.asset,
|
|
1743
|
+
network: input.accepted.network,
|
|
1744
|
+
chainId: chainIdOrNull(input.accepted.network),
|
|
1745
|
+
merchantAddress: input.accepted.payTo
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
buildMppQuote(challenge, request, idempotencyKey) {
|
|
1749
|
+
return {
|
|
1750
|
+
rail: "mpp",
|
|
1751
|
+
paymentRail: challenge.rail,
|
|
1752
|
+
idempotencyKey: idempotencyKey ?? buildMachinePaymentIdempotencyKey(challenge),
|
|
1753
|
+
challenge,
|
|
1754
|
+
request,
|
|
1755
|
+
resourceUrl: challenge.resource,
|
|
1756
|
+
description: challenge.description ?? null,
|
|
1757
|
+
amountAtomic: challenge.amount.atomic,
|
|
1758
|
+
amount: challenge.amount.display,
|
|
1759
|
+
token: challenge.asset.symbol,
|
|
1760
|
+
asset: challenge.asset.address,
|
|
1761
|
+
network: challenge.network.name,
|
|
1762
|
+
chainId: challenge.network.chainId,
|
|
1763
|
+
merchantAddress: challenge.recipient,
|
|
1764
|
+
expiresAt: challenge.expiresAt
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
buildMppResumeState(input) {
|
|
1768
|
+
const quote = this.buildMppQuote(
|
|
1769
|
+
input.challenge,
|
|
1770
|
+
input.request ?? this.snapshotX402Request(input.challenge.resource),
|
|
1771
|
+
input.idempotencyKey
|
|
1772
|
+
);
|
|
1773
|
+
return {
|
|
1774
|
+
rail: "mpp",
|
|
1775
|
+
paymentRail: quote.paymentRail,
|
|
1776
|
+
paymentId: input.paymentId,
|
|
1777
|
+
idempotencyKey: quote.idempotencyKey,
|
|
1778
|
+
challenge: input.challenge,
|
|
1779
|
+
url: input.request?.url ?? input.challenge.resource,
|
|
1780
|
+
request: input.request,
|
|
1781
|
+
resourceUrl: quote.resourceUrl,
|
|
1782
|
+
description: quote.description,
|
|
1783
|
+
amountAtomic: quote.amountAtomic,
|
|
1784
|
+
amount: quote.amount,
|
|
1785
|
+
token: quote.token,
|
|
1786
|
+
asset: quote.asset,
|
|
1787
|
+
network: quote.network,
|
|
1788
|
+
chainId: quote.chainId,
|
|
1789
|
+
merchantAddress: quote.merchantAddress,
|
|
1790
|
+
expiresAt: quote.expiresAt
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
attachResumeState(err, input) {
|
|
1794
|
+
if (input.rail === "x402") {
|
|
1795
|
+
this.attachX402ResumeState(
|
|
1796
|
+
err,
|
|
1797
|
+
input.paymentRequired,
|
|
1798
|
+
input.accepted,
|
|
1799
|
+
input.idempotencyKey,
|
|
1800
|
+
input.request
|
|
1801
|
+
);
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
this.attachMppResumeState(err, input.challenge, input.idempotencyKey, input.request);
|
|
1805
|
+
}
|
|
1806
|
+
attachX402ResumeState(err, paymentRequired, accepted, idempotencyKey, request) {
|
|
1807
|
+
if (!(err instanceof HavenPaymentStateError)) return;
|
|
1808
|
+
if (err.state.rail !== "x402") return;
|
|
1809
|
+
err.resumeState = this.buildX402ResumeState({
|
|
1810
|
+
paymentId: err.state.paymentId,
|
|
1811
|
+
paymentRequired,
|
|
1812
|
+
accepted,
|
|
1813
|
+
idempotencyKey,
|
|
1814
|
+
request
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
attachMppResumeState(err, challenge, idempotencyKey, request) {
|
|
1818
|
+
if (!(err instanceof HavenPaymentStateError)) return;
|
|
1819
|
+
if (!isMppRail(err.state.rail)) return;
|
|
1820
|
+
err.resumeState = this.buildMppResumeState({
|
|
1821
|
+
paymentId: err.state.paymentId,
|
|
1822
|
+
challenge,
|
|
1823
|
+
idempotencyKey,
|
|
1824
|
+
request
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1180
1827
|
// ── Tool Execution (for agent frameworks) ────────────────────────
|
|
1181
1828
|
/**
|
|
1182
1829
|
* Execute a tool call by name and input.
|
|
@@ -1236,9 +1883,9 @@ var HavenClient = class {
|
|
|
1236
1883
|
}
|
|
1237
1884
|
}
|
|
1238
1885
|
if (toolName === "authorize_machine_payment") {
|
|
1239
|
-
const { challenge } = input;
|
|
1886
|
+
const { challenge, idempotencyKey } = input;
|
|
1240
1887
|
try {
|
|
1241
|
-
const receipt = await this.authorizeMachinePayment(challenge);
|
|
1888
|
+
const receipt = await this.authorizeMachinePayment(challenge, { idempotencyKey });
|
|
1242
1889
|
return {
|
|
1243
1890
|
success: true,
|
|
1244
1891
|
payment_id: receipt.paymentId,
|
|
@@ -1273,6 +1920,13 @@ var HavenClient = class {
|
|
|
1273
1920
|
amount: result.amount,
|
|
1274
1921
|
resource_url: result.resourceUrl,
|
|
1275
1922
|
merchant_address: result.merchantAddress,
|
|
1923
|
+
amount_atomic: result.amountAtomic,
|
|
1924
|
+
asset: result.asset,
|
|
1925
|
+
network: result.network,
|
|
1926
|
+
description: result.description,
|
|
1927
|
+
idempotency_key: result.idempotencyKey,
|
|
1928
|
+
x402: result.x402,
|
|
1929
|
+
mpp: result.mpp,
|
|
1276
1930
|
expires_at: result.expiresAt,
|
|
1277
1931
|
chain_id: result.chainId,
|
|
1278
1932
|
message: result.message
|
|
@@ -1330,6 +1984,31 @@ var HavenClient = class {
|
|
|
1330
1984
|
amount: err.state.amount,
|
|
1331
1985
|
resource_url: err.state.resourceUrl,
|
|
1332
1986
|
merchant_address: err.state.merchantAddress,
|
|
1987
|
+
amount_atomic: err.state.amountAtomic,
|
|
1988
|
+
asset: err.state.asset,
|
|
1989
|
+
network: err.state.network,
|
|
1990
|
+
description: err.state.description,
|
|
1991
|
+
idempotency_key: err.state.idempotencyKey,
|
|
1992
|
+
x402: err.state.x402 ? {
|
|
1993
|
+
amount_atomic: err.state.x402.amountAtomic,
|
|
1994
|
+
asset: err.state.x402.asset,
|
|
1995
|
+
network: err.state.x402.network,
|
|
1996
|
+
resource_url: err.state.x402.resourceUrl,
|
|
1997
|
+
merchant_address: err.state.x402.merchantAddress,
|
|
1998
|
+
description: err.state.x402.description,
|
|
1999
|
+
idempotency_key: err.state.x402.idempotencyKey
|
|
2000
|
+
} : void 0,
|
|
2001
|
+
mpp: err.state.mpp ? {
|
|
2002
|
+
amount_atomic: err.state.mpp.amountAtomic,
|
|
2003
|
+
asset: err.state.mpp.asset,
|
|
2004
|
+
network: err.state.mpp.network,
|
|
2005
|
+
resource_url: err.state.mpp.resourceUrl,
|
|
2006
|
+
merchant_address: err.state.mpp.merchantAddress,
|
|
2007
|
+
description: err.state.mpp.description,
|
|
2008
|
+
idempotency_key: err.state.mpp.idempotencyKey,
|
|
2009
|
+
challenge_id: err.state.mpp.challengeId
|
|
2010
|
+
} : void 0,
|
|
2011
|
+
resume_state: err.resumeState,
|
|
1333
2012
|
expires_at: err.state.expiresAt,
|
|
1334
2013
|
chain_id: err.state.chainId,
|
|
1335
2014
|
message: err.state.message,
|
|
@@ -1361,11 +2040,14 @@ var HavenClient = class {
|
|
|
1361
2040
|
const controller = new AbortController();
|
|
1362
2041
|
const timeout = setTimeout(() => controller.abort(), this.requestTimeout);
|
|
1363
2042
|
try {
|
|
2043
|
+
const contextHeaders = this.requestContext.getStore()?.headers ?? {};
|
|
1364
2044
|
const res = await fetch(url, {
|
|
1365
2045
|
method,
|
|
1366
2046
|
headers: {
|
|
1367
2047
|
"Content-Type": "application/json",
|
|
1368
|
-
"Authorization": `Bearer ${this.apiKey}
|
|
2048
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
2049
|
+
...this.defaultHeaders,
|
|
2050
|
+
...contextHeaders
|
|
1369
2051
|
},
|
|
1370
2052
|
body: body ? JSON.stringify(body) : void 0,
|
|
1371
2053
|
signal: controller.signal
|
|
@@ -1422,7 +2104,50 @@ var HavenClient = class {
|
|
|
1422
2104
|
txHash: raw.tx_hash,
|
|
1423
2105
|
expiresAt: raw.expires_at,
|
|
1424
2106
|
chainId: raw.chain_id,
|
|
1425
|
-
message: raw.message
|
|
2107
|
+
message: raw.message,
|
|
2108
|
+
amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? null,
|
|
2109
|
+
asset: raw.asset ?? raw.x402?.asset ?? null,
|
|
2110
|
+
network: raw.network ?? raw.x402?.network ?? null,
|
|
2111
|
+
description: raw.description ?? raw.x402?.description ?? null,
|
|
2112
|
+
idempotencyKey: raw.idempotency_key ?? raw.x402?.idempotency_key ?? null,
|
|
2113
|
+
x402: raw.x402 ? {
|
|
2114
|
+
amountAtomic: raw.x402.amount_atomic ?? raw.amount_atomic ?? null,
|
|
2115
|
+
asset: raw.x402.asset ?? raw.asset ?? null,
|
|
2116
|
+
network: raw.x402.network ?? raw.network ?? null,
|
|
2117
|
+
resourceUrl: raw.x402.resource_url ?? raw.resource_url,
|
|
2118
|
+
merchantAddress: raw.x402.merchant_address ?? raw.merchant_address,
|
|
2119
|
+
description: raw.x402.description ?? raw.description ?? null,
|
|
2120
|
+
idempotencyKey: raw.x402.idempotency_key ?? raw.idempotency_key ?? null
|
|
2121
|
+
} : void 0
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
mapPaymentReceipt(raw) {
|
|
2125
|
+
return {
|
|
2126
|
+
id: raw.id,
|
|
2127
|
+
paymentId: raw.payment_id,
|
|
2128
|
+
rail: raw.rail,
|
|
2129
|
+
proofStatus: raw.proof_status,
|
|
2130
|
+
txHash: raw.tx_hash,
|
|
2131
|
+
chainId: raw.chain_id,
|
|
2132
|
+
resourceUrl: raw.resource_url,
|
|
2133
|
+
merchantAddress: raw.merchant_address,
|
|
2134
|
+
payerAddress: raw.payer_address,
|
|
2135
|
+
settlementAddress: raw.settlement_address,
|
|
2136
|
+
tokenSymbol: raw.token_symbol,
|
|
2137
|
+
tokenAddress: raw.token_address,
|
|
2138
|
+
amountRaw: raw.amount_raw,
|
|
2139
|
+
amount: raw.amount_human,
|
|
2140
|
+
challengeId: raw.challenge_id,
|
|
2141
|
+
idempotencyKey: raw.idempotency_key,
|
|
2142
|
+
challengePayload: raw.challenge_payload,
|
|
2143
|
+
selectedPayment: raw.selected_payment,
|
|
2144
|
+
paymentProofHeaderName: raw.payment_proof_header_name,
|
|
2145
|
+
protocolReceiptHeaderName: raw.protocol_receipt_header_name,
|
|
2146
|
+
protocolReceiptPayload: raw.protocol_receipt_payload,
|
|
2147
|
+
merchantStatus: raw.merchant_status,
|
|
2148
|
+
confirmedAt: raw.confirmed_at,
|
|
2149
|
+
createdAt: raw.created_at,
|
|
2150
|
+
updatedAt: raw.updated_at
|
|
1426
2151
|
};
|
|
1427
2152
|
}
|
|
1428
2153
|
};
|
|
@@ -1581,8 +2306,8 @@ var authorizeMachinePaymentSchema = {
|
|
|
1581
2306
|
};
|
|
1582
2307
|
var MAKE_PAYMENT_DESCRIPTION = "Request and sign a payment from the user-controlled Safe within approved on-chain limits. Haven authenticates the agent, validates the signed intent, and relays the Safe AllowanceModule transaction; it does not hold keys or control funds. Gnosis Chain tokens: EURe, USDC.e, xDAI. Base tokens: USDC, ETH.";
|
|
1583
2308
|
var GET_STATUS_DESCRIPTION = "Check the status of a previously initiated payment. Accepts payment intent IDs and approval request IDs. Returns the current status, phase, next_action, transaction hash if available, and payment details.";
|
|
1584
|
-
var AUTHORIZE_X402_DESCRIPTION = "Authorize payment for an HTTP 402 (Payment Required) response. When a paid API returns x402 payment requirements, use this tool to sign with the agent-owned delegate key and request a policy-limited Safe AllowanceModule top-up when needed. Haven relays signed transactions only; the agent key authorizes payment and on-chain limits enforce spend. If this returns pending_approval, tell the user it is waiting in Haven, call get_payment_status later, and use resume_x402_payment only when next_action is retry_original_x402_request. Do not loop retries while approval is pending. Use the returned payment_header as the X-PAYMENT header on the retry request when doing a manual HTTP retry.";
|
|
1585
|
-
var RESUME_X402_DESCRIPTION = "Resume an x402 payment after the user approved it in Haven. Use this only after get_payment_status returns next_action=retry_original_x402_request. It checks the approved payment, validates the original x402 details, and returns a merchant X-PAYMENT header without creating a new approval request.";
|
|
2309
|
+
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, preserve the original merchant/MCP session and x402 details, call get_payment_status later, and use resume_x402_payment only when next_action is retry_original_x402_request. Do not start a new merchant session or 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.";
|
|
2310
|
+
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 or merchant session.";
|
|
1586
2311
|
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.";
|
|
1587
2312
|
function claudeTools() {
|
|
1588
2313
|
return [
|
|
@@ -1664,6 +2389,18 @@ var havenTools = {
|
|
|
1664
2389
|
openai: openaiTools
|
|
1665
2390
|
};
|
|
1666
2391
|
|
|
2392
|
+
exports.AGENT_PAYMENT_NEXT_ACTION_VALUES = AGENT_PAYMENT_NEXT_ACTION_VALUES;
|
|
2393
|
+
exports.AGENT_PAYMENT_PHASE_VALUES = AGENT_PAYMENT_PHASE_VALUES;
|
|
2394
|
+
exports.AGENT_PAYMENT_RAIL_VALUES = AGENT_PAYMENT_RAIL_VALUES;
|
|
2395
|
+
exports.AgentPaymentNextAction = AgentPaymentNextAction;
|
|
2396
|
+
exports.AgentPaymentNextActionDescriptions = AgentPaymentNextActionDescriptions;
|
|
2397
|
+
exports.AgentPaymentNextActionSchema = AgentPaymentNextActionSchema;
|
|
2398
|
+
exports.AgentPaymentPhase = AgentPaymentPhase;
|
|
2399
|
+
exports.AgentPaymentPhaseDescriptions = AgentPaymentPhaseDescriptions;
|
|
2400
|
+
exports.AgentPaymentPhaseSchema = AgentPaymentPhaseSchema;
|
|
2401
|
+
exports.AgentPaymentRail = AgentPaymentRail;
|
|
2402
|
+
exports.AgentPaymentRailDescriptions = AgentPaymentRailDescriptions;
|
|
2403
|
+
exports.AgentPaymentRailSchema = AgentPaymentRailSchema;
|
|
1667
2404
|
exports.HavenApiError = HavenApiError;
|
|
1668
2405
|
exports.HavenClient = HavenClient;
|
|
1669
2406
|
exports.HavenError = HavenError;
|