@haven_ai/sdk 0.1.14-alpha.0 → 0.1.15-alpha.0
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 +18 -0
- package/dist/index.cjs +316 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +215 -10
- package/dist/index.d.ts +215 -10
- package/dist/index.js +306 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -61,6 +61,11 @@ var AgentPaymentNextAction = {
|
|
|
61
61
|
StopAndTellUser: "stop_and_tell_user",
|
|
62
62
|
/** Ask again only if the user still wants the payment after expiry. */
|
|
63
63
|
RequestAgainIfUserStillWantsIt: "request_again_if_user_still_wants_it",
|
|
64
|
+
/**
|
|
65
|
+
* The x402 funding/quote window expired. Re-quote the same logical merchant
|
|
66
|
+
* operation with the same idempotency key to stay double-charge-safe.
|
|
67
|
+
*/
|
|
68
|
+
PaymentWindowExpired: "payment_window_expired",
|
|
64
69
|
/**
|
|
65
70
|
* Stop and tell the user that the originating Safe needs to be funded or
|
|
66
71
|
* the agent's per-token allowance needs to be raised before the payment
|
|
@@ -74,6 +79,14 @@ var AgentPaymentNextAction = {
|
|
|
74
79
|
*/
|
|
75
80
|
SweepStrandedFunds: "sweep_stranded_funds"
|
|
76
81
|
};
|
|
82
|
+
var AgentPaymentFailureCode = {
|
|
83
|
+
/** A merchant-authoritative x402 price exceeds the caller's pre-funding max_amount cap. */
|
|
84
|
+
PriceExceedsMax: "PRICE_EXCEEDS_MAX",
|
|
85
|
+
/** The x402 funding/quote window expired before the signer or hosted settle step could finish. */
|
|
86
|
+
PaymentWindowExpired: "PAYMENT_WINDOW_EXPIRED",
|
|
87
|
+
/** The Haven funding leg succeeded, but the merchant rejected the paid retry. */
|
|
88
|
+
MerchantRejectedAfterFunding: "MERCHANT_REJECTED_AFTER_FUNDING"
|
|
89
|
+
};
|
|
77
90
|
var AgentPaymentRail = {
|
|
78
91
|
/** Standard Haven payment from the user's Safe through an approved delegate allowance. */
|
|
79
92
|
Direct: "direct",
|
|
@@ -92,6 +105,7 @@ var AgentPaymentRail = {
|
|
|
92
105
|
};
|
|
93
106
|
var AGENT_PAYMENT_PHASE_VALUES = Object.values(AgentPaymentPhase);
|
|
94
107
|
var AGENT_PAYMENT_NEXT_ACTION_VALUES = Object.values(AgentPaymentNextAction);
|
|
108
|
+
var AGENT_PAYMENT_FAILURE_CODE_VALUES = Object.values(AgentPaymentFailureCode);
|
|
95
109
|
var AGENT_PAYMENT_RAIL_VALUES = Object.values(AgentPaymentRail);
|
|
96
110
|
var AgentPaymentPhaseDescriptions = {
|
|
97
111
|
[AgentPaymentPhase.AgentSignatureRequired]: "The agent must sign and submit the prepared payment before Haven can relay it.",
|
|
@@ -116,9 +130,15 @@ var AgentPaymentNextActionDescriptions = {
|
|
|
116
130
|
[AgentPaymentNextAction.RetryOriginalX402Request]: "Resume this payment id and retry the original x402 request with the merchant payment header.",
|
|
117
131
|
[AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
|
|
118
132
|
[AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry.",
|
|
133
|
+
[AgentPaymentNextAction.PaymentWindowExpired]: "The x402 funding/quote window expired. Re-quote with the same idempotency key before asking the signer to build a merchant payment header again.",
|
|
119
134
|
[AgentPaymentNextAction.FundSafeOrRaiseAllowance]: "Stop and tell the user that the originating Safe needs to be funded or the agent allowance raised before the payment can succeed.",
|
|
120
135
|
[AgentPaymentNextAction.SweepStrandedFunds]: "Tell the user that funds may be stranded in the delegate wallet and prompt them to initiate a sweep in Haven to return them to the originating Safe."
|
|
121
136
|
};
|
|
137
|
+
var AgentPaymentFailureCodeDescriptions = {
|
|
138
|
+
[AgentPaymentFailureCode.PriceExceedsMax]: "The merchant-authoritative x402 amount exceeds the caller's max_amount cap. No funding transfer was created; ask the user before retrying with a larger cap.",
|
|
139
|
+
[AgentPaymentFailureCode.PaymentWindowExpired]: "The x402 funding/quote window expired before the signer or hosted settle step could finish. Re-quote via haven_pay_mcp_tool with the same idempotency key to avoid duplicate funding.",
|
|
140
|
+
[AgentPaymentFailureCode.MerchantRejectedAfterFunding]: "The Haven funding leg succeeded, but the merchant rejected the paid retry. Stop retrying the merchant and reconcile stranded delegate funds with haven_sweep_delegate."
|
|
141
|
+
};
|
|
122
142
|
var AgentPaymentRailDescriptions = {
|
|
123
143
|
[AgentPaymentRail.Direct]: "Standard Haven payment from the user-controlled Safe through an approved delegate allowance.",
|
|
124
144
|
[AgentPaymentRail.X402]: "x402 HTTP 402 payment flow with a Haven funding leg and merchant retry leg.",
|
|
@@ -140,6 +160,12 @@ var AgentPaymentNextActionSchema = {
|
|
|
140
160
|
description: "Stable next action an agent should take for a Haven payment state.",
|
|
141
161
|
"x-enumDescriptions": AgentPaymentNextActionDescriptions
|
|
142
162
|
};
|
|
163
|
+
var AgentPaymentFailureCodeSchema = {
|
|
164
|
+
type: "string",
|
|
165
|
+
enum: AGENT_PAYMENT_FAILURE_CODE_VALUES,
|
|
166
|
+
description: "Stable machine-readable failure codes for Haven agent payment recovery paths.",
|
|
167
|
+
"x-enumDescriptions": AgentPaymentFailureCodeDescriptions
|
|
168
|
+
};
|
|
143
169
|
var AgentPaymentRailSchema = {
|
|
144
170
|
type: "string",
|
|
145
171
|
enum: AGENT_PAYMENT_RAIL_VALUES,
|
|
@@ -327,7 +353,8 @@ function normalizePaymentRequired(value) {
|
|
|
327
353
|
x402Version: candidate.x402Version,
|
|
328
354
|
resource,
|
|
329
355
|
accepts,
|
|
330
|
-
error: candidate.error
|
|
356
|
+
error: candidate.error,
|
|
357
|
+
...candidate.extensions && typeof candidate.extensions === "object" ? { extensions: candidate.extensions } : {}
|
|
331
358
|
};
|
|
332
359
|
}
|
|
333
360
|
var SUPPORTED_X402_NETWORKS = {
|
|
@@ -423,8 +450,7 @@ function x402AuthorizationAmount(option) {
|
|
|
423
450
|
return amount;
|
|
424
451
|
}
|
|
425
452
|
function buildX402ExpectedMessage(context) {
|
|
426
|
-
|
|
427
|
-
${stableStringify({
|
|
453
|
+
const payload = {
|
|
428
454
|
version: 1,
|
|
429
455
|
kind: "haven.x402.expected",
|
|
430
456
|
paymentId: context.paymentId,
|
|
@@ -434,7 +460,12 @@ ${stableStringify({
|
|
|
434
460
|
amount: context.amount,
|
|
435
461
|
asset: context.asset.toLowerCase(),
|
|
436
462
|
network: context.network
|
|
437
|
-
}
|
|
463
|
+
};
|
|
464
|
+
if (context.expiresAt) {
|
|
465
|
+
payload.expiresAt = context.expiresAt;
|
|
466
|
+
}
|
|
467
|
+
return `Haven x402 expected context v1
|
|
468
|
+
${stableStringify(payload)}`;
|
|
438
469
|
}
|
|
439
470
|
function toStandardPaymentRequirements(paymentRequired, option) {
|
|
440
471
|
const network = STANDARD_X402_NETWORKS[option.network];
|
|
@@ -890,6 +921,7 @@ var HavenClient = class {
|
|
|
890
921
|
}
|
|
891
922
|
return {
|
|
892
923
|
paymentId: raw.payment_id,
|
|
924
|
+
idempotencyKey,
|
|
893
925
|
status: "pending_signature",
|
|
894
926
|
expiresAt: raw.expires_at,
|
|
895
927
|
signData: raw.sign_data,
|
|
@@ -1041,6 +1073,30 @@ var HavenClient = class {
|
|
|
1041
1073
|
transfers
|
|
1042
1074
|
};
|
|
1043
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Hosted (keyless) split-signer sweep — step 1 of 2.
|
|
1078
|
+
*
|
|
1079
|
+
* Asks the backend to build a gasless EIP-3009 sweep authorization for the
|
|
1080
|
+
* delegate's stranded USDC. Returns `nothing_stranded` when the delegate is
|
|
1081
|
+
* empty, otherwise an `authorization` + Haven `expected_auth` to hand to the
|
|
1082
|
+
* edge signer's `haven_sign_sweep_delegate`. No key is required on this client.
|
|
1083
|
+
*/
|
|
1084
|
+
async prepareSweep() {
|
|
1085
|
+
return this.post("/machine-payments/sweep/prepare", {});
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Hosted (keyless) split-signer sweep — step 2 of 2.
|
|
1089
|
+
*
|
|
1090
|
+
* Relays the delegate-signed authorization. The Haven relayer submits the
|
|
1091
|
+
* on-chain `transferWithAuthorization` and pays gas; this client never holds
|
|
1092
|
+
* the key.
|
|
1093
|
+
*/
|
|
1094
|
+
async submitSweep(authorization, signature) {
|
|
1095
|
+
return this.post("/machine-payments/sweep/submit", {
|
|
1096
|
+
authorization,
|
|
1097
|
+
signature
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1044
1100
|
/**
|
|
1045
1101
|
* Get configured and on-chain allowances for the authenticated agent.
|
|
1046
1102
|
*/
|
|
@@ -1196,7 +1252,8 @@ var HavenClient = class {
|
|
|
1196
1252
|
throw new HavenApiError("quoteX402 only supports standard x402 Payment Required responses.", 400);
|
|
1197
1253
|
}
|
|
1198
1254
|
const paymentRequired = await parsePaymentRequiredResponse(response);
|
|
1199
|
-
|
|
1255
|
+
const mcpTransport = await this.detectX402McpTransport(url, paymentRequired, response);
|
|
1256
|
+
return this.buildX402Quote(paymentRequired, request, options.idempotencyKey, mcpTransport);
|
|
1200
1257
|
}
|
|
1201
1258
|
/**
|
|
1202
1259
|
* Pay a previously inspected x402 quote and retry the exact captured request.
|
|
@@ -1401,12 +1458,11 @@ var HavenClient = class {
|
|
|
1401
1458
|
* a transport/HTTP error, a missing session id, or a JSON-RPC error in the
|
|
1402
1459
|
* handshake response — so the caller can fall back to plain x402.
|
|
1403
1460
|
*/
|
|
1404
|
-
async mcpInitialize(url, init) {
|
|
1461
|
+
async mcpInitialize(url, init, wallet = this.x402PayerAddress()) {
|
|
1405
1462
|
try {
|
|
1406
1463
|
const headers = new Headers(init?.headers);
|
|
1407
1464
|
headers.set("Content-Type", "application/json");
|
|
1408
1465
|
headers.set("Accept", MCP_ACCEPT);
|
|
1409
|
-
const wallet = this.x402PayerAddress();
|
|
1410
1466
|
if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
|
|
1411
1467
|
const response = await globalThis.fetch(url, {
|
|
1412
1468
|
method: "POST",
|
|
@@ -1427,7 +1483,7 @@ var HavenClient = class {
|
|
|
1427
1483
|
if (!sessionId) return void 0;
|
|
1428
1484
|
const message = await this.readMcpMessage(response);
|
|
1429
1485
|
if (message && "error" in message) return void 0;
|
|
1430
|
-
await this.mcpNotifyInitialized(url, init, sessionId);
|
|
1486
|
+
await this.mcpNotifyInitialized(url, init, sessionId, wallet);
|
|
1431
1487
|
return sessionId;
|
|
1432
1488
|
} catch {
|
|
1433
1489
|
return void 0;
|
|
@@ -1438,13 +1494,12 @@ var HavenClient = class {
|
|
|
1438
1494
|
* lifecycle handshake. Best-effort: the session is already established, so a
|
|
1439
1495
|
* failed notification must not abort the payment.
|
|
1440
1496
|
*/
|
|
1441
|
-
async mcpNotifyInitialized(url, init, sessionId) {
|
|
1497
|
+
async mcpNotifyInitialized(url, init, sessionId, wallet = this.x402PayerAddress()) {
|
|
1442
1498
|
try {
|
|
1443
1499
|
const headers = new Headers(init?.headers);
|
|
1444
1500
|
headers.set("Content-Type", "application/json");
|
|
1445
1501
|
headers.set("Accept", MCP_ACCEPT);
|
|
1446
1502
|
headers.set("mcp-session-id", sessionId);
|
|
1447
|
-
const wallet = this.x402PayerAddress();
|
|
1448
1503
|
if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
|
|
1449
1504
|
await globalThis.fetch(url, {
|
|
1450
1505
|
method: "POST",
|
|
@@ -1622,30 +1677,33 @@ var HavenClient = class {
|
|
|
1622
1677
|
* amount/merchant/nonce-bound EIP-3009 authorization the edge signer already
|
|
1623
1678
|
* produced — the hosted server cannot mint or reuse signing authority.
|
|
1624
1679
|
*
|
|
1625
|
-
* When the URL is MCP-shaped (`/mcp` path)
|
|
1680
|
+
* When the URL is MCP-shaped (`/mcp` path) or the quote-time transport context
|
|
1681
|
+
* says the merchant was Bazaar-discoverable, runs a fresh `initialize`
|
|
1626
1682
|
* handshake (the quote-time session is gone once funding confirms; the x402
|
|
1627
1683
|
* challenge is stateless w.r.t. the MCP session, so a fresh session is
|
|
1628
1684
|
* accepted), threads the session + wallet headers, sets `X-PAYMENT`, and
|
|
1629
1685
|
* collapses an SSE JSON-RPC response to its `result`.
|
|
1630
|
-
*
|
|
1631
|
-
* Limitation: detects MCP only by the `/mcp` path convention, not the
|
|
1632
|
-
* Coinbase Bazaar `extensions.bazaar` 402 signal that `fetch()` also honors.
|
|
1633
|
-
* A Bazaar-discoverable merchant on a non-`/mcp` URL would need the standard
|
|
1634
|
-
* `fetch()` path. All current MCP-tool merchants use the `/mcp` convention.
|
|
1635
1686
|
*/
|
|
1636
1687
|
async completeX402MerchantCall(input) {
|
|
1688
|
+
const evidenceContext = await this.resolveX402MerchantCompletionContext({
|
|
1689
|
+
paymentId: input.paymentId,
|
|
1690
|
+
url: input.url
|
|
1691
|
+
});
|
|
1692
|
+
const shouldHandshakeMcp = isMcpUrl(input.url) || input.mcpTransport?.handshakeRequired === true;
|
|
1693
|
+
const x402Wallet = shouldHandshakeMcp ? await this.resolveX402WalletForMerchantCall() : this.x402PayerAddress();
|
|
1637
1694
|
let mcpSessionId;
|
|
1638
|
-
if (
|
|
1639
|
-
mcpSessionId = await this.mcpInitialize(input.url, input.init);
|
|
1695
|
+
if (shouldHandshakeMcp) {
|
|
1696
|
+
mcpSessionId = await this.mcpInitialize(input.url, input.init, x402Wallet);
|
|
1640
1697
|
}
|
|
1641
|
-
let requestInit = this.withX402Wallet(input.init,
|
|
1698
|
+
let requestInit = this.withX402Wallet(input.init, x402Wallet) ?? {};
|
|
1642
1699
|
if (mcpSessionId) requestInit = this.withMcpHeaders(requestInit, mcpSessionId);
|
|
1643
1700
|
const headers = new Headers(requestInit.headers);
|
|
1644
1701
|
headers.set("X-PAYMENT", input.paymentHeader);
|
|
1645
1702
|
requestInit = { ...requestInit, headers };
|
|
1646
1703
|
const response = await globalThis.fetch(input.url, requestInit);
|
|
1647
1704
|
const surfaced = mcpSessionId ? await this.surfaceMcpResult(response) : response;
|
|
1648
|
-
const
|
|
1705
|
+
const protocolReceiptHeader = surfaced.headers.get("PAYMENT-RESPONSE") ?? void 0;
|
|
1706
|
+
const settlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
|
|
1649
1707
|
const text = await surfaced.text();
|
|
1650
1708
|
let body;
|
|
1651
1709
|
try {
|
|
@@ -1653,6 +1711,35 @@ var HavenClient = class {
|
|
|
1653
1711
|
} catch {
|
|
1654
1712
|
body = text;
|
|
1655
1713
|
}
|
|
1714
|
+
if (!surfaced.ok) {
|
|
1715
|
+
await this.recordMerchantRetryRejected({
|
|
1716
|
+
rail: "x402",
|
|
1717
|
+
paymentId: evidenceContext.paymentId,
|
|
1718
|
+
txHash: evidenceContext.txHash,
|
|
1719
|
+
resourceUrl: evidenceContext.resourceUrl,
|
|
1720
|
+
merchant: {
|
|
1721
|
+
merchant_status: surfaced.status,
|
|
1722
|
+
merchant_status_text: surfaced.statusText,
|
|
1723
|
+
merchant_headers: Object.fromEntries(surfaced.headers.entries()),
|
|
1724
|
+
merchant_body: text
|
|
1725
|
+
},
|
|
1726
|
+
details: {
|
|
1727
|
+
merchant_to: evidenceContext.merchantAddress
|
|
1728
|
+
}
|
|
1729
|
+
});
|
|
1730
|
+
} else {
|
|
1731
|
+
await this.reportMachinePaymentEvidence({
|
|
1732
|
+
paymentId: evidenceContext.paymentId,
|
|
1733
|
+
rail: "x402",
|
|
1734
|
+
txHash: evidenceContext.txHash,
|
|
1735
|
+
resourceUrl: evidenceContext.resourceUrl,
|
|
1736
|
+
merchantStatus: surfaced.status,
|
|
1737
|
+
paymentProofHeaderName: "X-PAYMENT",
|
|
1738
|
+
paymentProofHeader: input.paymentHeader,
|
|
1739
|
+
protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
|
|
1740
|
+
protocolReceiptHeader
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1656
1743
|
return {
|
|
1657
1744
|
status: surfaced.status,
|
|
1658
1745
|
ok: surfaced.ok,
|
|
@@ -1660,6 +1747,53 @@ var HavenClient = class {
|
|
|
1660
1747
|
settlementTxHash: settlement.settlementTxHash ?? void 0
|
|
1661
1748
|
};
|
|
1662
1749
|
}
|
|
1750
|
+
async resolveX402MerchantCompletionContext(input) {
|
|
1751
|
+
const status = await this.getPaymentStatus(input.paymentId);
|
|
1752
|
+
if (status.rail !== "x402") {
|
|
1753
|
+
throw new HavenPaymentStateError(
|
|
1754
|
+
`Payment ${status.paymentId} is ${status.rail}, not x402.`,
|
|
1755
|
+
409,
|
|
1756
|
+
status
|
|
1757
|
+
);
|
|
1758
|
+
}
|
|
1759
|
+
const readyForMerchantCompletion = status.nextAction === AgentPaymentNextAction.RetryOriginalX402Request || status.kind === "payment_intent" && status.status === "confirmed" && status.phase === AgentPaymentPhase.PaymentConfirmed && status.nextAction === AgentPaymentNextAction.None;
|
|
1760
|
+
if (!readyForMerchantCompletion) {
|
|
1761
|
+
throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
|
|
1762
|
+
}
|
|
1763
|
+
if (!status.txHash) {
|
|
1764
|
+
throw new HavenApiError(
|
|
1765
|
+
`x402 payment ${status.paymentId} is ready for merchant completion but has no Haven transaction hash.`,
|
|
1766
|
+
502,
|
|
1767
|
+
status,
|
|
1768
|
+
status.paymentId
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
const approvedResourceUrl = status.resourceUrl ?? status.x402?.resourceUrl ?? null;
|
|
1772
|
+
if (approvedResourceUrl && approvedResourceUrl !== input.url) {
|
|
1773
|
+
throw new HavenApiError(
|
|
1774
|
+
"x402 merchant completion does not match the approved resource URL.",
|
|
1775
|
+
409,
|
|
1776
|
+
{ status, url: input.url },
|
|
1777
|
+
status.paymentId
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
return {
|
|
1781
|
+
paymentId: status.paymentId,
|
|
1782
|
+
txHash: status.txHash,
|
|
1783
|
+
resourceUrl: approvedResourceUrl ?? input.url,
|
|
1784
|
+
merchantAddress: status.merchantAddress ?? status.x402?.merchantAddress ?? null
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
async resolveX402WalletForMerchantCall() {
|
|
1788
|
+
const localWallet = this.x402PayerAddress();
|
|
1789
|
+
if (localWallet) return localWallet;
|
|
1790
|
+
try {
|
|
1791
|
+
const agent = await this.getAgent();
|
|
1792
|
+
return agent.delegateAddress ?? void 0;
|
|
1793
|
+
} catch {
|
|
1794
|
+
return void 0;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1663
1797
|
async authorizeMachinePayment(challenge, options = {}) {
|
|
1664
1798
|
if (!this.delegateKey) {
|
|
1665
1799
|
throw new HavenSigningError(
|
|
@@ -2257,7 +2391,7 @@ var HavenClient = class {
|
|
|
2257
2391
|
headers
|
|
2258
2392
|
};
|
|
2259
2393
|
}
|
|
2260
|
-
buildX402Quote(paymentRequired, request, idempotencyKey) {
|
|
2394
|
+
buildX402Quote(paymentRequired, request, idempotencyKey, mcpTransport) {
|
|
2261
2395
|
const option = selectStandardPaymentOption(paymentRequired.accepts);
|
|
2262
2396
|
if (!option) {
|
|
2263
2397
|
throw new HavenApiError(
|
|
@@ -2272,6 +2406,7 @@ var HavenClient = class {
|
|
|
2272
2406
|
paymentRequired,
|
|
2273
2407
|
accepted: option,
|
|
2274
2408
|
request,
|
|
2409
|
+
...mcpTransport ? { mcpTransport } : {},
|
|
2275
2410
|
resourceUrl: paymentRequired.resource.url,
|
|
2276
2411
|
description: paymentRequired.resource.description ?? option.description ?? null,
|
|
2277
2412
|
mimeType: paymentRequired.resource.mimeType ?? option.mimeType ?? null,
|
|
@@ -2285,6 +2420,18 @@ var HavenClient = class {
|
|
|
2285
2420
|
maxTimeoutSeconds: option.maxTimeoutSeconds
|
|
2286
2421
|
};
|
|
2287
2422
|
}
|
|
2423
|
+
async detectX402McpTransport(url, paymentRequired, response) {
|
|
2424
|
+
if (isMcpUrl(url)) {
|
|
2425
|
+
return { handshakeRequired: true, source: "path" };
|
|
2426
|
+
}
|
|
2427
|
+
if (paymentRequired.extensions?.bazaar != null) {
|
|
2428
|
+
return { handshakeRequired: true, source: "bazaar" };
|
|
2429
|
+
}
|
|
2430
|
+
if (await responseHasBazaarExtension(response)) {
|
|
2431
|
+
return { handshakeRequired: true, source: "bazaar" };
|
|
2432
|
+
}
|
|
2433
|
+
return void 0;
|
|
2434
|
+
}
|
|
2288
2435
|
buildX402ResumeState(input) {
|
|
2289
2436
|
const token = resolveTokenFromAddress(input.accepted.asset, input.accepted.network);
|
|
2290
2437
|
return {
|
|
@@ -2837,8 +2984,8 @@ var toolDescriptions = {
|
|
|
2837
2984
|
discoverTools: {
|
|
2838
2985
|
summary: "Discover payable services from Haven's curated merchant catalog \u2014 names, prices, and which pay tool to use.",
|
|
2839
2986
|
selectionGuidance: "Use this when the user asks what the agent can buy, pay for, or which paid services exist \u2014 or when you need a resource URL for a service the user described. Do NOT use for balance, budget, or spend-limit questions \u2014 use haven_get_allowances. Do NOT use to pay \u2014 each returned entry names the pay tool to use next.",
|
|
2840
|
-
behavior: "Read-only lookup against Haven's curated catalog. Entries are periodically re-verified against the live merchant; degraded entries are flagged. Returns name, description, price, rail, resource URL, and a suggested_tool field naming the exact Haven pay tool for that entry. Never creates a payment, signature, or approval.",
|
|
2841
|
-
nextActionGuidance: "Pick an entry
|
|
2987
|
+
behavior: "Read-only lookup against Haven's curated catalog. Entries are periodically re-verified against the live merchant; degraded entries are flagged. Returns name, description, price, rail, resource URL, and a suggested_tool field naming the exact Haven pay tool for that entry. The catalog price (price_display/price_atomic, marked price_is_indicative) is a last-verified hint, NOT authoritative \u2014 the real price comes from the merchant's live 402 at pay time. Never creates a payment, signature, or approval.",
|
|
2988
|
+
nextActionGuidance: "Pick an entry and pay it with the tool named in suggested_tool, passing the entry's resource_url (and tool_name for MCP merchants). Confirm the price from the live pay-tool result (not the catalog), and pass max_amount when the user has a cap."
|
|
2842
2989
|
},
|
|
2843
2990
|
sweep_delegate: {
|
|
2844
2991
|
summary: "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating Safe.",
|
|
@@ -3105,6 +3252,9 @@ the Haven MCP tools. Every payment is checked against the agent's on-chain
|
|
|
3105
3252
|
budget before money moves; payments above the remaining budget wait for the
|
|
3106
3253
|
user's approval in Haven.
|
|
3107
3254
|
|
|
3255
|
+
Hosted tools run in the \`mcp__haven__\` namespace. Local signing tools run in
|
|
3256
|
+
the \`mcp__haven-signer__\` namespace and keep the delegate key on this machine.
|
|
3257
|
+
|
|
3108
3258
|
## When to use this skill
|
|
3109
3259
|
|
|
3110
3260
|
- The user asks to send money, pay someone, tip, donate, or transfer tokens.
|
|
@@ -3130,13 +3280,33 @@ normal, not an error.
|
|
|
3130
3280
|
the local Haven signer; follow the tool results \u2014 they tell you the next
|
|
3131
3281
|
action at every step. Retry the original request only when the result says
|
|
3132
3282
|
\`retry_original_x402_request\`.
|
|
3133
|
-
- **Paid MCP tool call:** \`
|
|
3134
|
-
name, and arguments
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
\`
|
|
3139
|
-
|
|
3283
|
+
- **Paid MCP tool call:** \`mcp__haven__haven_pay_mcp_tool\` with the merchant
|
|
3284
|
+
URL, tool name, and arguments, then finish in two calls (fast path):
|
|
3285
|
+
\`mcp__haven-signer__haven_sign_x402\` on the local signer (pass
|
|
3286
|
+
\`payload_hash\`, \`x402_expected\` as the nested \`x402.expected\` object, and
|
|
3287
|
+
\`payment_required\`) returns \`{ signature, payment_header }\`; then
|
|
3288
|
+
\`mcp__haven__haven_settle_mcp_tool\` (pass \`payment_id\`, \`signature\`,
|
|
3289
|
+
\`payment_header\`, \`merchant_url\`, \`tool_name\`, \`arguments\`,
|
|
3290
|
+
\`mcp_transport\`) funds and settles in one step and returns the tool result.
|
|
3291
|
+
If it returns \`settled: false\`, funding is queued for the user's approval \u2014
|
|
3292
|
+
tell them and check status later, do not re-pay. Step-by-step alternative:
|
|
3293
|
+
\`mcp__haven-signer__haven_sign\` \u2192 \`mcp__haven__haven_submit\` \u2192
|
|
3294
|
+
\`mcp__haven-signer__haven_x402_sign_header\` \u2192
|
|
3295
|
+
\`mcp__haven__haven_complete_mcp_tool\`. Pass \`payment_required\`,
|
|
3296
|
+
\`arguments\`, and \`mcp_transport\` verbatim from the
|
|
3297
|
+
\`mcp__haven__haven_pay_mcp_tool\` result. The returned \`expires_at\` is the
|
|
3298
|
+
signing window; if a tool returns \`PAYMENT_WINDOW_EXPIRED\`, re-run
|
|
3299
|
+
\`mcp__haven__haven_pay_mcp_tool\` with the same
|
|
3300
|
+
\`idempotency_key\`. Do not call the merchant yourself \u2014 Haven completes the
|
|
3301
|
+
merchant leg for you.
|
|
3302
|
+
- **Prices:** show the user the live price from the pay-tool result, never a
|
|
3303
|
+
catalog price. \`haven_discover_tools\` prices are indicative
|
|
3304
|
+
(\`price_is_indicative\`) and can be stale. The pay-tool result's \`amount\` /
|
|
3305
|
+
\`amount_atomic\` is the amount Haven authorizes for the call \u2014 a ceiling the
|
|
3306
|
+
merchant settles at or below \u2014 so present it as the most the user will pay.
|
|
3307
|
+
Pass \`max_amount\` (atomic units) to \`haven_pay_mcp_tool\` /
|
|
3308
|
+
\`haven_pay_x402_quote\` to reject a quote whose authorized amount is above the
|
|
3309
|
+
user's cap, before any funds move.
|
|
3140
3310
|
- **Status:** \`haven_get_payment_status\` with a \`payment_id\` to check on
|
|
3141
3311
|
queued or in-flight payments. Do not poll in a tight loop.
|
|
3142
3312
|
|
|
@@ -3145,18 +3315,26 @@ normal, not an error.
|
|
|
3145
3315
|
- A result with \`pending_approval\` means the payment exceeded the remaining
|
|
3146
3316
|
budget and is waiting for the user in Haven. Tell the user, then check
|
|
3147
3317
|
status later.
|
|
3148
|
-
- Never ask the user for private keys
|
|
3149
|
-
|
|
3150
|
-
tell the user to re-run the Haven
|
|
3318
|
+
- Never ask the user for private keys. Signing happens only in the local Haven
|
|
3319
|
+
signer; the hosted Haven tools never receive the signing key. If a tool
|
|
3320
|
+
reports a missing or invalid credential, tell the user to re-run the Haven
|
|
3321
|
+
setup command.
|
|
3151
3322
|
|
|
3152
3323
|
## Failure handling
|
|
3153
3324
|
|
|
3154
|
-
Haven
|
|
3155
|
-
|
|
3325
|
+
Haven tool failures are shaped like \`{ success: false, code, message, ... }\`
|
|
3326
|
+
or older \`{ error, status, details? }\` responses. Branch on \`code\` when
|
|
3327
|
+
present and surface \`message\` or \`error\` verbatim. Common cases:
|
|
3156
3328
|
|
|
3157
3329
|
- \`pending_approval\`: queued for the user's approval (see above).
|
|
3158
3330
|
- \`insufficient_funds\`: the Haven wallet doesn't hold enough of that token.
|
|
3159
3331
|
Suggest the user add funds in the Haven dashboard.
|
|
3332
|
+
- \`PRICE_EXCEEDS_MAX\`: the live merchant price exceeded your \`max_amount\`.
|
|
3333
|
+
No funds moved; ask the user before retrying with a higher cap.
|
|
3334
|
+
- \`PAYMENT_WINDOW_EXPIRED\`: re-run \`mcp__haven__haven_pay_mcp_tool\` with the same
|
|
3335
|
+
\`idempotency_key\`, then sign the fresh \`payload_hash\`.
|
|
3336
|
+
- \`MERCHANT_REJECTED_AFTER_FUNDING\`: stop retrying the merchant and use
|
|
3337
|
+
\`mcp__haven__haven_sweep_delegate\` to recover stranded delegate funds.
|
|
3160
3338
|
- Budget exceeded: tell the user how much remains (from
|
|
3161
3339
|
\`haven_get_allowances\`) and that they can raise the budget in Haven.
|
|
3162
3340
|
|
|
@@ -3168,6 +3346,98 @@ for that credential.
|
|
|
3168
3346
|
`;
|
|
3169
3347
|
var SKILL_FOLDER_NAME = "haven-pay";
|
|
3170
3348
|
|
|
3171
|
-
|
|
3349
|
+
// src/sweep.ts
|
|
3350
|
+
var SWEEP_BASE_CHAIN_ID = 8453;
|
|
3351
|
+
var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
3352
|
+
var USDC_EIP712_DOMAIN_BY_CHAIN = {
|
|
3353
|
+
[SWEEP_BASE_CHAIN_ID]: {
|
|
3354
|
+
name: "USD Coin",
|
|
3355
|
+
version: "2",
|
|
3356
|
+
chainId: SWEEP_BASE_CHAIN_ID,
|
|
3357
|
+
verifyingContract: SWEEP_BASE_USDC_ADDRESS
|
|
3358
|
+
}
|
|
3359
|
+
};
|
|
3360
|
+
var USDC_ADDRESS_BY_CHAIN = {
|
|
3361
|
+
[SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS
|
|
3362
|
+
};
|
|
3363
|
+
var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
3364
|
+
TransferWithAuthorization: [
|
|
3365
|
+
{ name: "from", type: "address" },
|
|
3366
|
+
{ name: "to", type: "address" },
|
|
3367
|
+
{ name: "value", type: "uint256" },
|
|
3368
|
+
{ name: "validAfter", type: "uint256" },
|
|
3369
|
+
{ name: "validBefore", type: "uint256" },
|
|
3370
|
+
{ name: "nonce", type: "bytes32" }
|
|
3371
|
+
]
|
|
3372
|
+
};
|
|
3373
|
+
function sweepUsdcAddress(chainId) {
|
|
3374
|
+
const address = USDC_ADDRESS_BY_CHAIN[chainId];
|
|
3375
|
+
if (!address) {
|
|
3376
|
+
throw new HavenSigningError(
|
|
3377
|
+
`Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
|
|
3378
|
+
);
|
|
3379
|
+
}
|
|
3380
|
+
return address;
|
|
3381
|
+
}
|
|
3382
|
+
function sweepUsdcDomain(chainId) {
|
|
3383
|
+
const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
|
|
3384
|
+
if (!domain) {
|
|
3385
|
+
throw new HavenSigningError(
|
|
3386
|
+
`Sweep is only supported on Base (chainId ${SWEEP_BASE_CHAIN_ID}). Got chainId ${chainId}.`
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
return domain;
|
|
3390
|
+
}
|
|
3391
|
+
function sameAddress2(a, b) {
|
|
3392
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
3393
|
+
}
|
|
3394
|
+
function buildSweepTypedData(auth) {
|
|
3395
|
+
const domain = sweepUsdcDomain(auth.chainId);
|
|
3396
|
+
const expectedToken = sweepUsdcAddress(auth.chainId);
|
|
3397
|
+
if (!sameAddress2(auth.token, expectedToken)) {
|
|
3398
|
+
throw new HavenSigningError(
|
|
3399
|
+
`Sweep token ${auth.token} is not the canonical USDC contract for chain ${auth.chainId}.`
|
|
3400
|
+
);
|
|
3401
|
+
}
|
|
3402
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(auth.nonce)) {
|
|
3403
|
+
throw new HavenSigningError("Sweep nonce must be a 0x-prefixed 32-byte hex string.");
|
|
3404
|
+
}
|
|
3405
|
+
return {
|
|
3406
|
+
domain,
|
|
3407
|
+
types: TRANSFER_WITH_AUTHORIZATION_TYPES,
|
|
3408
|
+
primaryType: "TransferWithAuthorization",
|
|
3409
|
+
message: {
|
|
3410
|
+
from: auth.from,
|
|
3411
|
+
to: auth.to,
|
|
3412
|
+
value: BigInt(auth.value),
|
|
3413
|
+
validAfter: BigInt(auth.validAfter),
|
|
3414
|
+
validBefore: BigInt(auth.validBefore),
|
|
3415
|
+
nonce: auth.nonce
|
|
3416
|
+
}
|
|
3417
|
+
};
|
|
3418
|
+
}
|
|
3419
|
+
function buildSweepAuthorizationMessage(auth) {
|
|
3420
|
+
return `Haven sweep authorization v1
|
|
3421
|
+
${stableStringify2({
|
|
3422
|
+
version: 1,
|
|
3423
|
+
kind: "haven.sweep.authorization",
|
|
3424
|
+
from: auth.from.toLowerCase(),
|
|
3425
|
+
to: auth.to.toLowerCase(),
|
|
3426
|
+
value: auth.value,
|
|
3427
|
+
validAfter: auth.validAfter,
|
|
3428
|
+
validBefore: auth.validBefore,
|
|
3429
|
+
nonce: auth.nonce.toLowerCase(),
|
|
3430
|
+
token: auth.token.toLowerCase(),
|
|
3431
|
+
chainId: auth.chainId
|
|
3432
|
+
})}`;
|
|
3433
|
+
}
|
|
3434
|
+
function stableStringify2(value) {
|
|
3435
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
3436
|
+
if (Array.isArray(value)) return `[${value.map((item) => stableStringify2(item)).join(",")}]`;
|
|
3437
|
+
const object = value;
|
|
3438
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
export { AGENT_PAYMENT_FAILURE_CODE_VALUES, AGENT_PAYMENT_NEXT_ACTION_VALUES, AGENT_PAYMENT_PHASE_VALUES, AGENT_PAYMENT_RAIL_VALUES, AgentPaymentFailureCode, AgentPaymentFailureCodeDescriptions, AgentPaymentFailureCodeSchema, AgentPaymentNextAction, AgentPaymentNextActionDescriptions, AgentPaymentNextActionSchema, AgentPaymentPhase, AgentPaymentPhaseDescriptions, AgentPaymentPhaseSchema, AgentPaymentRail, AgentPaymentRailDescriptions, AgentPaymentRailSchema, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_USDC_ADDRESS, TRANSFER_WITH_AUTHORIZATION_TYPES, addressFromKey, buildMachinePaymentIdempotencyKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, composeDescription, decodeBase64Json, decodeBase64Utf8, encodeBase64Json, encodeBase64Utf8, encodeMachinePaymentProof, encodePaymentProof, havenTools, parseMachinePaymentChallenge, parseMachinePaymentChallengeResponse, parsePaymentRequired, parsePaymentRequiredResponse, selectPaymentOption, selectStandardPaymentOption, signHash, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, verifySignature, x402AuthorizationAmount };
|
|
3172
3442
|
//# sourceMappingURL=index.js.map
|
|
3173
3443
|
//# sourceMappingURL=index.js.map
|