@haven_ai/sdk 0.0.0-dev.202609190936.6937e31 → 0.0.0-dev.202609191414.43bc74c
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 +17 -0
- package/dist/index.cjs +170 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -7
- package/dist/index.d.ts +9 -7
- package/dist/index.js +170 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -147,6 +147,23 @@ if (apiResponse.status === 402) {
|
|
|
147
147
|
}
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
**MCP merchants, both dialects (#3118).** A merchant may serve x402 over MCP
|
|
151
|
+
in Haven's HTTP layering (HTTP 402 with the `PAYMENT-REQUIRED` header or JSON
|
|
152
|
+
body, payment in `PAYMENT-SIGNATURE` / `X-PAYMENT`, settlement in
|
|
153
|
+
`PAYMENT-RESPONSE`) or in the official x402 MCP transport profile (a tool
|
|
154
|
+
result with `isError: true` and the `PaymentRequired` as `structuredContent`,
|
|
155
|
+
payment in `params._meta["x402/payment"]`, settlement in
|
|
156
|
+
`result._meta["x402/payment-response"]`). `fetch()`, `quoteX402()`,
|
|
157
|
+
`quoteMcpX402()` and `completeX402MerchantCall()` handle both: a payment-
|
|
158
|
+
required tool result quotes like a 402, the paid retry carries the header and
|
|
159
|
+
— for a `tools/call` body — the `_meta` object, and settlement is read from the
|
|
160
|
+
header first, the `_meta` second. An in-band refusal (an `isError` challenge on
|
|
161
|
+
the paid retry, or `success: false`) is a rejection even under HTTP 200. An
|
|
162
|
+
ordinary tool error, or a successful result that merely resembles a challenge,
|
|
163
|
+
is never paid for. The tool-result challenge is read only from a response
|
|
164
|
+
declaring `application/json` or `text/event-stream`; any other content type is
|
|
165
|
+
passed through untouched rather than buffered.
|
|
166
|
+
|
|
150
167
|
### Idempotency: what the key guarantees, and what it costs
|
|
151
168
|
|
|
152
169
|
**Omit `idempotencyKey` and the SDK synthesises one** from the merchant's
|
package/dist/index.cjs
CHANGED
|
@@ -1488,6 +1488,9 @@ var MCP_NOTIFICATION_TIMEOUT = 1e4;
|
|
|
1488
1488
|
var MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
1489
1489
|
var MCP_ACCEPT = "application/json, text/event-stream";
|
|
1490
1490
|
var MCP_CLIENT_INFO = { name: "haven-sdk", version: "1" };
|
|
1491
|
+
var MCP_X402_PAYMENT_META_KEY = "x402/payment";
|
|
1492
|
+
var MCP_X402_PAYMENT_RESPONSE_META_KEY = "x402/payment-response";
|
|
1493
|
+
var X402_RETRY_REJECTED_STATUS = 402;
|
|
1491
1494
|
var McpMerchantTransport = class {
|
|
1492
1495
|
merchantTimeout;
|
|
1493
1496
|
fetchImpl;
|
|
@@ -1583,7 +1586,7 @@ var McpMerchantTransport = class {
|
|
|
1583
1586
|
return response;
|
|
1584
1587
|
}
|
|
1585
1588
|
const message = selectJsonRpcResult(parseSseJsonRpcMessages(text));
|
|
1586
|
-
if (!message) return response;
|
|
1589
|
+
if (!message || !("result" in message || "error" in message)) return response;
|
|
1587
1590
|
const body = "result" in message ? message.result : message;
|
|
1588
1591
|
const headers = new Headers(response.headers);
|
|
1589
1592
|
headers.set("content-type", "application/json");
|
|
@@ -1646,7 +1649,34 @@ var McpMerchantTransport = class {
|
|
|
1646
1649
|
if (send.includes(name)) headers.set(name, paymentHeader);
|
|
1647
1650
|
else headers.delete(name);
|
|
1648
1651
|
}
|
|
1649
|
-
return this.fetch(url, { ...init, headers });
|
|
1652
|
+
return this.fetch(url, withMcpPaymentMeta({ ...init, headers }, paymentHeader));
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* #3118: read a native MCP payment-required tool result from a response
|
|
1656
|
+
* WITHOUT consuming it — JSON or SSE framed. `undefined` for anything that
|
|
1657
|
+
* is not an `isError: true` tool result carrying a valid `PaymentRequired`
|
|
1658
|
+
* (structured content first, text fallback second), so an ordinary tool
|
|
1659
|
+
* error, a successful result that merely resembles a challenge, or an
|
|
1660
|
+
* unparseable body is never mistaken for a payment demand.
|
|
1661
|
+
*/
|
|
1662
|
+
async extractToolResultChallenge(response, init) {
|
|
1663
|
+
const toolResult = await this.readToolResult(response, init);
|
|
1664
|
+
return toolResult ? extractMcpPaymentRequired(toolResult) : void 0;
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* #3118: the tool RESULT a response carries, read WITHOUT consuming it, or
|
|
1668
|
+
* `undefined`. Only a JSON or SSE body is read at all (#3155 review S2):
|
|
1669
|
+
* a non-402 answer used to be returned untouched, and buffering an
|
|
1670
|
+
* arbitrary body — a streaming or binary paid resource — to look for a
|
|
1671
|
+
* challenge would block the caller until the stream ended or the merchant
|
|
1672
|
+
* timeout fired. A merchant that speaks JSON-RPC declares one of those two
|
|
1673
|
+
* content types.
|
|
1674
|
+
*/
|
|
1675
|
+
async readToolResult(response, init) {
|
|
1676
|
+
if (init !== void 0 && !isJsonRpcToolsCallBody(init.body)) return void 0;
|
|
1677
|
+
const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
|
|
1678
|
+
if (!contentType.includes("application/json") && !contentType.includes("text/event-stream")) return void 0;
|
|
1679
|
+
return mcpToolResultOf(await this.readMessage(response));
|
|
1650
1680
|
}
|
|
1651
1681
|
async notifyInitialized(url, init, sessionId, wallet) {
|
|
1652
1682
|
try {
|
|
@@ -1668,6 +1698,88 @@ var McpMerchantTransport = class {
|
|
|
1668
1698
|
}
|
|
1669
1699
|
}
|
|
1670
1700
|
};
|
|
1701
|
+
function mcpToolResultOf(value) {
|
|
1702
|
+
if (!isRecord(value)) return void 0;
|
|
1703
|
+
if ("jsonrpc" in value) {
|
|
1704
|
+
return isRecord(value.result) ? value.result : void 0;
|
|
1705
|
+
}
|
|
1706
|
+
if ("isError" in value || Array.isArray(value.content) || isRecord(value._meta)) return value;
|
|
1707
|
+
return void 0;
|
|
1708
|
+
}
|
|
1709
|
+
function extractMcpPaymentRequired(toolResult) {
|
|
1710
|
+
if (toolResult.isError !== true) return void 0;
|
|
1711
|
+
if (isRecord(toolResult.structuredContent)) {
|
|
1712
|
+
const parsed = normalizePaymentRequired(toolResult.structuredContent);
|
|
1713
|
+
if (parsed) return parsed;
|
|
1714
|
+
}
|
|
1715
|
+
if (Array.isArray(toolResult.content)) {
|
|
1716
|
+
for (const item of toolResult.content) {
|
|
1717
|
+
if (!isRecord(item) || item.type !== "text" || typeof item.text !== "string") continue;
|
|
1718
|
+
let candidate;
|
|
1719
|
+
try {
|
|
1720
|
+
candidate = JSON.parse(item.text);
|
|
1721
|
+
} catch {
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
const parsed = normalizePaymentRequired(candidate);
|
|
1725
|
+
if (parsed) return parsed;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
return void 0;
|
|
1729
|
+
}
|
|
1730
|
+
function mcpSettlementFromToolResult(toolResult) {
|
|
1731
|
+
const meta = toolResult._meta;
|
|
1732
|
+
if (!isRecord(meta)) return void 0;
|
|
1733
|
+
const settlement = meta[MCP_X402_PAYMENT_RESPONSE_META_KEY];
|
|
1734
|
+
if (!isRecord(settlement) || typeof settlement.success !== "boolean") return void 0;
|
|
1735
|
+
return {
|
|
1736
|
+
success: settlement.success,
|
|
1737
|
+
...typeof settlement.transaction === "string" ? { transaction: settlement.transaction } : {},
|
|
1738
|
+
...typeof settlement.network === "string" ? { network: settlement.network } : {},
|
|
1739
|
+
...typeof settlement.payer === "string" ? { payer: settlement.payer } : {},
|
|
1740
|
+
...typeof settlement.errorReason === "string" ? { errorReason: settlement.errorReason } : {}
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
function withMcpPaymentMeta(init, paymentHeader) {
|
|
1744
|
+
const request = parseJsonRpcToolsCall(init.body);
|
|
1745
|
+
if (!request) return init;
|
|
1746
|
+
let envelope;
|
|
1747
|
+
try {
|
|
1748
|
+
envelope = decodeBase64Json(paymentHeader);
|
|
1749
|
+
} catch {
|
|
1750
|
+
return init;
|
|
1751
|
+
}
|
|
1752
|
+
if (!isRecord(envelope)) return init;
|
|
1753
|
+
const params = request.params;
|
|
1754
|
+
const existingMeta = isRecord(params._meta) ? params._meta : {};
|
|
1755
|
+
return {
|
|
1756
|
+
...init,
|
|
1757
|
+
body: JSON.stringify({
|
|
1758
|
+
...request,
|
|
1759
|
+
params: { ...params, _meta: { ...existingMeta, [MCP_X402_PAYMENT_META_KEY]: envelope } }
|
|
1760
|
+
})
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
function encodeMcpSettlementReceipt(settlement) {
|
|
1764
|
+
return encodeBase64Json(settlement);
|
|
1765
|
+
}
|
|
1766
|
+
function parseJsonRpcToolsCall(body) {
|
|
1767
|
+
if (typeof body !== "string") return void 0;
|
|
1768
|
+
let request;
|
|
1769
|
+
try {
|
|
1770
|
+
request = JSON.parse(body);
|
|
1771
|
+
} catch {
|
|
1772
|
+
return void 0;
|
|
1773
|
+
}
|
|
1774
|
+
if (!isRecord(request) || request.method !== "tools/call" || !isRecord(request.params)) return void 0;
|
|
1775
|
+
return request;
|
|
1776
|
+
}
|
|
1777
|
+
function isJsonRpcToolsCallBody(body) {
|
|
1778
|
+
return parseJsonRpcToolsCall(body) !== void 0;
|
|
1779
|
+
}
|
|
1780
|
+
function isRecord(value) {
|
|
1781
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1782
|
+
}
|
|
1671
1783
|
async function captureMerchantResponse(response) {
|
|
1672
1784
|
const merchant_body = await response.text().catch(() => "");
|
|
1673
1785
|
return {
|
|
@@ -2821,7 +2933,11 @@ var MerchantCompletion = class {
|
|
|
2821
2933
|
initialInit,
|
|
2822
2934
|
receipt.paymentHeader
|
|
2823
2935
|
);
|
|
2824
|
-
|
|
2936
|
+
const toolResult = retryResponse.ok ? await this.merchantTransport.readToolResult(retryResponse) : void 0;
|
|
2937
|
+
const inBandChallenge = toolResult ? extractMcpPaymentRequired(toolResult) : void 0;
|
|
2938
|
+
const metaSettlement = toolResult ? mcpSettlementFromToolResult(toolResult) : void 0;
|
|
2939
|
+
const inBandRejection = inBandChallenge !== void 0 || metaSettlement?.success === false;
|
|
2940
|
+
if (!retryResponse.ok || inBandRejection) {
|
|
2825
2941
|
const merchant = await captureMerchantResponse(retryResponse);
|
|
2826
2942
|
await this.recordRetryRejected({
|
|
2827
2943
|
rail: "x402",
|
|
@@ -2836,7 +2952,7 @@ var MerchantCompletion = class {
|
|
|
2836
2952
|
});
|
|
2837
2953
|
throw new HavenApiError(
|
|
2838
2954
|
"x402 retry failed after Haven funded the delegate wallet; reconciliation may be required.",
|
|
2839
|
-
merchant.merchant_status,
|
|
2955
|
+
inBandRejection ? X402_RETRY_REJECTED_STATUS : merchant.merchant_status,
|
|
2840
2956
|
{
|
|
2841
2957
|
marker: "x402_retry_rejected_after_funding",
|
|
2842
2958
|
payment_id: receipt.paymentId,
|
|
@@ -2848,7 +2964,10 @@ var MerchantCompletion = class {
|
|
|
2848
2964
|
}
|
|
2849
2965
|
);
|
|
2850
2966
|
}
|
|
2851
|
-
const
|
|
2967
|
+
const headerReceipt = retryResponse.headers.get("PAYMENT-RESPONSE");
|
|
2968
|
+
const protocolReceiptHeader = headerReceipt ?? (metaSettlement ? encodeMcpSettlementReceipt(metaSettlement) : void 0);
|
|
2969
|
+
const protocolReceiptHeaderName = headerReceipt ? "PAYMENT-RESPONSE" : metaSettlement ? `_meta.${MCP_X402_PAYMENT_RESPONSE_META_KEY}` : void 0;
|
|
2970
|
+
const merchantSettlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
|
|
2852
2971
|
if (receipt.merchant && merchantSettlement.settlementTxHash) {
|
|
2853
2972
|
receipt.merchant.settlementTxHash = merchantSettlement.settlementTxHash;
|
|
2854
2973
|
receipt.merchant.settlementExplorerUrl = buildExplorerUrl(
|
|
@@ -2866,8 +2985,8 @@ var MerchantCompletion = class {
|
|
|
2866
2985
|
selectedPayment: receipt.accepted,
|
|
2867
2986
|
paymentProofHeaderName: x402PaymentHeaderNamesSent(receipt.paymentHeader),
|
|
2868
2987
|
paymentProofHeader: receipt.paymentHeader,
|
|
2869
|
-
protocolReceiptHeaderName
|
|
2870
|
-
protocolReceiptHeader
|
|
2988
|
+
protocolReceiptHeaderName,
|
|
2989
|
+
protocolReceiptHeader
|
|
2871
2990
|
});
|
|
2872
2991
|
await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
|
|
2873
2992
|
return retryResponse;
|
|
@@ -3842,6 +3961,11 @@ var HavenClient = class {
|
|
|
3842
3961
|
const request = snapshotX402Request(url, initialInit);
|
|
3843
3962
|
const response = await this.merchantTransport.fetch(url, initialInit);
|
|
3844
3963
|
if (response.status !== 402) {
|
|
3964
|
+
const toolResultChallenge = await this.merchantTransport.extractToolResultChallenge(response, initialInit);
|
|
3965
|
+
if (toolResultChallenge) {
|
|
3966
|
+
const mcpTransport2 = await this.merchantTransport.detect(url, toolResultChallenge, response);
|
|
3967
|
+
return buildX402Quote(toolResultChallenge, request, options.idempotencyKey, mcpTransport2);
|
|
3968
|
+
}
|
|
3845
3969
|
let body;
|
|
3846
3970
|
try {
|
|
3847
3971
|
body = await response.clone().json();
|
|
@@ -4008,10 +4132,11 @@ var HavenClient = class {
|
|
|
4008
4132
|
return this.merchantCompletion.retryRequest(url ?? paymentRequired.resource.url, initialInit, paymentRequired, receipt);
|
|
4009
4133
|
}
|
|
4010
4134
|
/**
|
|
4011
|
-
* Fetch wrapper that automatically handles HTTP 402 responses
|
|
4135
|
+
* Fetch wrapper that automatically handles HTTP 402 responses — and, since
|
|
4136
|
+
* #3118, a native-MCP payment-required tool result answered under HTTP 200.
|
|
4012
4137
|
*
|
|
4013
|
-
* Works like the standard `fetch()` but intercepts 402 responses
|
|
4014
|
-
* pays via x402 through Haven, and retries the request.
|
|
4138
|
+
* Works like the standard `fetch()` but intercepts 402 responses (or that
|
|
4139
|
+
* tool-result challenge), pays via x402 through Haven, and retries the request.
|
|
4015
4140
|
*
|
|
4016
4141
|
* ```ts
|
|
4017
4142
|
* const response = await haven.fetch('https://paid-api.com/data')
|
|
@@ -4019,8 +4144,9 @@ var HavenClient = class {
|
|
|
4019
4144
|
* ```
|
|
4020
4145
|
*
|
|
4021
4146
|
* **MCP-over-x402 auto-handshake (issue #315):** when the endpoint is
|
|
4022
|
-
* MCP-shaped — the URL path ends in `/mcp`, or the 402 body
|
|
4023
|
-
*
|
|
4147
|
+
* MCP-shaped — the URL path ends in `/mcp`, or the 402 body (or, since
|
|
4148
|
+
* #3118, the tool-result challenge) carries a Coinbase Bazaar
|
|
4149
|
+
* `extensions.bazaar` block — the SDK runs the MCP
|
|
4024
4150
|
* `initialize` handshake, threads the resulting `mcp-session-id`,
|
|
4025
4151
|
* `Accept: application/json, text/event-stream`, and `x402-wallet` headers
|
|
4026
4152
|
* through every request, and collapses SSE responses to the JSON-RPC
|
|
@@ -4038,16 +4164,23 @@ var HavenClient = class {
|
|
|
4038
4164
|
let requestInit = withX402Wallet(init, x402PayerAddress(this.delegateAddress, this.x402Wallet));
|
|
4039
4165
|
if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
|
|
4040
4166
|
const response = await this.merchantTransport.fetch(url, requestInit);
|
|
4041
|
-
if (response.status !== 402) {
|
|
4042
|
-
return mcpSessionId ? this.merchantTransport.surfaceResult(response) : response;
|
|
4043
|
-
}
|
|
4044
4167
|
let paymentRequired;
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4168
|
+
let nativeChallenge = false;
|
|
4169
|
+
if (response.status !== 402) {
|
|
4170
|
+
const toolResultChallenge = await this.merchantTransport.extractToolResultChallenge(response, requestInit);
|
|
4171
|
+
if (!toolResultChallenge) {
|
|
4172
|
+
return mcpSessionId ? this.merchantTransport.surfaceResult(response) : response;
|
|
4173
|
+
}
|
|
4174
|
+
paymentRequired = toolResultChallenge;
|
|
4175
|
+
nativeChallenge = true;
|
|
4176
|
+
} else {
|
|
4177
|
+
try {
|
|
4178
|
+
paymentRequired = await parsePaymentRequiredResponse(response);
|
|
4179
|
+
} catch {
|
|
4180
|
+
return response;
|
|
4181
|
+
}
|
|
4049
4182
|
}
|
|
4050
|
-
if (!mcpSessionId && await this.merchantTransport.hasBazaarExtension(response)) {
|
|
4183
|
+
if (!mcpSessionId && (paymentRequired.extensions?.bazaar != null || await this.merchantTransport.hasBazaarExtension(response))) {
|
|
4051
4184
|
mcpSessionId = await this.merchantTransport.initialize(url, init);
|
|
4052
4185
|
if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
|
|
4053
4186
|
}
|
|
@@ -4069,7 +4202,7 @@ var HavenClient = class {
|
|
|
4069
4202
|
throw err;
|
|
4070
4203
|
}
|
|
4071
4204
|
const retryResponse = await this.merchantCompletion.retryRequest(url, requestInit, paymentRequired, receipt);
|
|
4072
|
-
return mcpSessionId ? this.merchantTransport.surfaceResult(retryResponse) : retryResponse;
|
|
4205
|
+
return mcpSessionId || nativeChallenge ? this.merchantTransport.surfaceResult(retryResponse) : retryResponse;
|
|
4073
4206
|
}
|
|
4074
4207
|
/**
|
|
4075
4208
|
* Deliver an already-signed x402 payment header to the merchant and return
|
|
@@ -4130,9 +4263,7 @@ var HavenClient = class {
|
|
|
4130
4263
|
let requestInit = withX402Wallet(input.init, x402Wallet) ?? {};
|
|
4131
4264
|
if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
|
|
4132
4265
|
const response = await this.merchantTransport.deliverPayment(input.url, requestInit, input.paymentHeader);
|
|
4133
|
-
const surfaced =
|
|
4134
|
-
const protocolReceiptHeader = surfaced.headers.get("PAYMENT-RESPONSE") ?? void 0;
|
|
4135
|
-
const settlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
|
|
4266
|
+
const surfaced = await this.merchantTransport.surfaceResult(response);
|
|
4136
4267
|
const text = await surfaced.text();
|
|
4137
4268
|
let body;
|
|
4138
4269
|
try {
|
|
@@ -4140,8 +4271,16 @@ var HavenClient = class {
|
|
|
4140
4271
|
} catch {
|
|
4141
4272
|
body = text;
|
|
4142
4273
|
}
|
|
4274
|
+
const toolResult = mcpToolResultOf(body);
|
|
4275
|
+
const inBandChallenge = toolResult ? extractMcpPaymentRequired(toolResult) : void 0;
|
|
4276
|
+
const metaSettlement = toolResult ? mcpSettlementFromToolResult(toolResult) : void 0;
|
|
4277
|
+
const headerReceipt = surfaced.headers.get("PAYMENT-RESPONSE") ?? void 0;
|
|
4278
|
+
const protocolReceiptHeaderName = headerReceipt ? "PAYMENT-RESPONSE" : metaSettlement ? `_meta.${MCP_X402_PAYMENT_RESPONSE_META_KEY}` : void 0;
|
|
4279
|
+
const protocolReceiptHeader = headerReceipt ?? (metaSettlement ? encodeMcpSettlementReceipt(metaSettlement) : void 0);
|
|
4280
|
+
const settlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
|
|
4281
|
+
const merchantAccepted = surfaced.ok && inBandChallenge === void 0 && metaSettlement?.success !== false;
|
|
4143
4282
|
let evidenceOutcome;
|
|
4144
|
-
if (!
|
|
4283
|
+
if (!merchantAccepted) {
|
|
4145
4284
|
if (!input.noFundingLeg && fundingTxHash) {
|
|
4146
4285
|
await this.merchantCompletion.recordRetryRejected({
|
|
4147
4286
|
rail: "x402",
|
|
@@ -4171,7 +4310,7 @@ var HavenClient = class {
|
|
|
4171
4310
|
merchantStatus: surfaced.status,
|
|
4172
4311
|
paymentProofHeaderName: x402PaymentHeaderNamesSent(input.paymentHeader),
|
|
4173
4312
|
paymentProofHeader: input.paymentHeader,
|
|
4174
|
-
protocolReceiptHeaderName
|
|
4313
|
+
protocolReceiptHeaderName,
|
|
4175
4314
|
protocolReceiptHeader
|
|
4176
4315
|
});
|
|
4177
4316
|
}
|
|
@@ -4179,9 +4318,10 @@ var HavenClient = class {
|
|
|
4179
4318
|
}
|
|
4180
4319
|
return {
|
|
4181
4320
|
status: surfaced.status,
|
|
4182
|
-
ok:
|
|
4321
|
+
ok: merchantAccepted,
|
|
4183
4322
|
body,
|
|
4184
|
-
|
|
4323
|
+
// #3155 review S3: a transaction beside a rejection is not a settlement.
|
|
4324
|
+
settlementTxHash: merchantAccepted ? settlement.settlementTxHash ?? void 0 : void 0,
|
|
4185
4325
|
evidenceOutcome
|
|
4186
4326
|
};
|
|
4187
4327
|
}
|
|
@@ -4371,12 +4511,12 @@ var toolDescriptions = {
|
|
|
4371
4511
|
payX402OneShot: {
|
|
4372
4512
|
summary: "Fetch an x402 paid HTTP resource in a single call. Handles the full probe -> pay -> retry round trip and returns the merchant response.",
|
|
4373
4513
|
selectionGuidance: "Prefer this over the quote+pay split when the agent just wants the paid resource and does not need to inspect the price first. If you already have a quote from haven_quote_x402, use haven_pay_x402_quote instead. Do not use for read-only allowance, budget, spend-limit, remaining-amount, reset-period, or what-can-I-spend questions; use the allowance lookup tool instead.",
|
|
4374
|
-
behavior: "Calls the URL, parses any HTTP 402 x402 challenge, signs the payment locally, then retries the original request with the signed payment header (sent under PAYMENT-SIGNATURE, plus the legacy X-PAYMENT on the EIP-3009 path only) and returns the merchant response. Settlement is either direct account-to-merchant with no funding leg, or a bridge that first redeems the agent's budget delegation to fund the delegate wallet for an EIP-3009 authorization. A payment outside the on-chain budget is declined before any money moves; nothing is queued for a human to approve later. If the resource returns a non-402 status, returns it unchanged without contacting Haven.",
|
|
4514
|
+
behavior: "Calls the URL, parses any HTTP 402 x402 challenge (or, since #3118, a native-MCP payment-required tool result under HTTP 200), signs the payment locally, then retries the original request with the signed payment header (sent under PAYMENT-SIGNATURE, plus the legacy X-PAYMENT on the EIP-3009 path only) and returns the merchant response. Settlement is either direct account-to-merchant with no funding leg, or a bridge that first redeems the agent's budget delegation to fund the delegate wallet for an EIP-3009 authorization. A payment outside the on-chain budget is declined before any money moves; nothing is queued for a human to approve later. If the resource returns a non-402 status with no payment-required tool result, returns it unchanged without contacting Haven.",
|
|
4375
4515
|
nextActionGuidance: "Preserve the returned resume_state or paymentId \u2014 either identifies this payment if you need to ask about it later. This tool performs the merchant retry itself, so do not wait on a signal while the call is in flight. If the process crashes after this call and a later haven_get_payment_status reports nextAction=retry_original_x402_request, Haven's funding leg confirmed but no merchant response was ever recorded \u2014 call the resume tool with the preserved resume_state or payment_id instead of paying again. If the response carries phase=insufficient_funds and nextAction=fund_account_or_raise_allowance, the payment cannot be retried until the account is funded or the agent budget raised \u2014 stop and tell the user the shortfall reported on the response."
|
|
4376
4516
|
},
|
|
4377
4517
|
resumeX402: {
|
|
4378
4518
|
summary: "Resume an x402 payment whose Haven-side authorization already succeeded but whose merchant retry did not complete.",
|
|
4379
|
-
behavior: "Accepts either resume_state or payment_id, validates the
|
|
4519
|
+
behavior: "Accepts either resume_state or payment_id, validates the x402 details against the authorized funding, and retries the merchant request with the signed payment header (PAYMENT-SIGNATURE, plus legacy X-PAYMENT on EIP-3009 only; also params._meta on an MCP tools/call body). No new Haven payment is created.",
|
|
4380
4520
|
nextActionGuidance: "Only call this after haven_get_payment_status reports nextAction=retry_original_x402_request \u2014 that means Haven's funding leg confirmed but no merchant response was ever recorded, most often because the process crashed between funding and the merchant retry. Any other nextAction reports a conflict instead of retrying, so do not call this speculatively. Do not start a new merchant session and do not pay again \u2014 that would pay twice for one resource."
|
|
4381
4521
|
},
|
|
4382
4522
|
// #1328: quoteMpp / payMpp / resumeMpp (the mpp_demo challenge/quote/resume
|