@haven_ai/sdk 0.0.0-dev.202609191028.9c54815 → 0.0.0-dev.202609191429.4d85f59
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 +175 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -10
- package/dist/index.d.ts +28 -10
- package/dist/index.js +175 -32
- 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
|
@@ -1297,6 +1297,9 @@ function mapPaymentReceipt(raw) {
|
|
|
1297
1297
|
if ("payment_intent_id" in raw) {
|
|
1298
1298
|
receipt.paymentIntentId = raw.payment_intent_id ?? null;
|
|
1299
1299
|
}
|
|
1300
|
+
if (raw.scope) {
|
|
1301
|
+
receipt.scope = { source: raw.scope.source, filter: raw.scope.filter };
|
|
1302
|
+
}
|
|
1300
1303
|
if ("approval_request_id" in raw) {
|
|
1301
1304
|
receipt.approvalRequestId = raw.approval_request_id ?? null;
|
|
1302
1305
|
}
|
|
@@ -1488,6 +1491,9 @@ var MCP_NOTIFICATION_TIMEOUT = 1e4;
|
|
|
1488
1491
|
var MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
1489
1492
|
var MCP_ACCEPT = "application/json, text/event-stream";
|
|
1490
1493
|
var MCP_CLIENT_INFO = { name: "haven-sdk", version: "1" };
|
|
1494
|
+
var MCP_X402_PAYMENT_META_KEY = "x402/payment";
|
|
1495
|
+
var MCP_X402_PAYMENT_RESPONSE_META_KEY = "x402/payment-response";
|
|
1496
|
+
var X402_RETRY_REJECTED_STATUS = 402;
|
|
1491
1497
|
var McpMerchantTransport = class {
|
|
1492
1498
|
merchantTimeout;
|
|
1493
1499
|
fetchImpl;
|
|
@@ -1583,7 +1589,7 @@ var McpMerchantTransport = class {
|
|
|
1583
1589
|
return response;
|
|
1584
1590
|
}
|
|
1585
1591
|
const message = selectJsonRpcResult(parseSseJsonRpcMessages(text));
|
|
1586
|
-
if (!message) return response;
|
|
1592
|
+
if (!message || !("result" in message || "error" in message)) return response;
|
|
1587
1593
|
const body = "result" in message ? message.result : message;
|
|
1588
1594
|
const headers = new Headers(response.headers);
|
|
1589
1595
|
headers.set("content-type", "application/json");
|
|
@@ -1646,7 +1652,34 @@ var McpMerchantTransport = class {
|
|
|
1646
1652
|
if (send.includes(name)) headers.set(name, paymentHeader);
|
|
1647
1653
|
else headers.delete(name);
|
|
1648
1654
|
}
|
|
1649
|
-
return this.fetch(url, { ...init, headers });
|
|
1655
|
+
return this.fetch(url, withMcpPaymentMeta({ ...init, headers }, paymentHeader));
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* #3118: read a native MCP payment-required tool result from a response
|
|
1659
|
+
* WITHOUT consuming it — JSON or SSE framed. `undefined` for anything that
|
|
1660
|
+
* is not an `isError: true` tool result carrying a valid `PaymentRequired`
|
|
1661
|
+
* (structured content first, text fallback second), so an ordinary tool
|
|
1662
|
+
* error, a successful result that merely resembles a challenge, or an
|
|
1663
|
+
* unparseable body is never mistaken for a payment demand.
|
|
1664
|
+
*/
|
|
1665
|
+
async extractToolResultChallenge(response, init) {
|
|
1666
|
+
const toolResult = await this.readToolResult(response, init);
|
|
1667
|
+
return toolResult ? extractMcpPaymentRequired(toolResult) : void 0;
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* #3118: the tool RESULT a response carries, read WITHOUT consuming it, or
|
|
1671
|
+
* `undefined`. Only a JSON or SSE body is read at all (#3155 review S2):
|
|
1672
|
+
* a non-402 answer used to be returned untouched, and buffering an
|
|
1673
|
+
* arbitrary body — a streaming or binary paid resource — to look for a
|
|
1674
|
+
* challenge would block the caller until the stream ended or the merchant
|
|
1675
|
+
* timeout fired. A merchant that speaks JSON-RPC declares one of those two
|
|
1676
|
+
* content types.
|
|
1677
|
+
*/
|
|
1678
|
+
async readToolResult(response, init) {
|
|
1679
|
+
if (init !== void 0 && !isJsonRpcToolsCallBody(init.body)) return void 0;
|
|
1680
|
+
const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
|
|
1681
|
+
if (!contentType.includes("application/json") && !contentType.includes("text/event-stream")) return void 0;
|
|
1682
|
+
return mcpToolResultOf(await this.readMessage(response));
|
|
1650
1683
|
}
|
|
1651
1684
|
async notifyInitialized(url, init, sessionId, wallet) {
|
|
1652
1685
|
try {
|
|
@@ -1668,6 +1701,88 @@ var McpMerchantTransport = class {
|
|
|
1668
1701
|
}
|
|
1669
1702
|
}
|
|
1670
1703
|
};
|
|
1704
|
+
function mcpToolResultOf(value) {
|
|
1705
|
+
if (!isRecord(value)) return void 0;
|
|
1706
|
+
if ("jsonrpc" in value) {
|
|
1707
|
+
return isRecord(value.result) ? value.result : void 0;
|
|
1708
|
+
}
|
|
1709
|
+
if ("isError" in value || Array.isArray(value.content) || isRecord(value._meta)) return value;
|
|
1710
|
+
return void 0;
|
|
1711
|
+
}
|
|
1712
|
+
function extractMcpPaymentRequired(toolResult) {
|
|
1713
|
+
if (toolResult.isError !== true) return void 0;
|
|
1714
|
+
if (isRecord(toolResult.structuredContent)) {
|
|
1715
|
+
const parsed = normalizePaymentRequired(toolResult.structuredContent);
|
|
1716
|
+
if (parsed) return parsed;
|
|
1717
|
+
}
|
|
1718
|
+
if (Array.isArray(toolResult.content)) {
|
|
1719
|
+
for (const item of toolResult.content) {
|
|
1720
|
+
if (!isRecord(item) || item.type !== "text" || typeof item.text !== "string") continue;
|
|
1721
|
+
let candidate;
|
|
1722
|
+
try {
|
|
1723
|
+
candidate = JSON.parse(item.text);
|
|
1724
|
+
} catch {
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
const parsed = normalizePaymentRequired(candidate);
|
|
1728
|
+
if (parsed) return parsed;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
return void 0;
|
|
1732
|
+
}
|
|
1733
|
+
function mcpSettlementFromToolResult(toolResult) {
|
|
1734
|
+
const meta = toolResult._meta;
|
|
1735
|
+
if (!isRecord(meta)) return void 0;
|
|
1736
|
+
const settlement = meta[MCP_X402_PAYMENT_RESPONSE_META_KEY];
|
|
1737
|
+
if (!isRecord(settlement) || typeof settlement.success !== "boolean") return void 0;
|
|
1738
|
+
return {
|
|
1739
|
+
success: settlement.success,
|
|
1740
|
+
...typeof settlement.transaction === "string" ? { transaction: settlement.transaction } : {},
|
|
1741
|
+
...typeof settlement.network === "string" ? { network: settlement.network } : {},
|
|
1742
|
+
...typeof settlement.payer === "string" ? { payer: settlement.payer } : {},
|
|
1743
|
+
...typeof settlement.errorReason === "string" ? { errorReason: settlement.errorReason } : {}
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
function withMcpPaymentMeta(init, paymentHeader) {
|
|
1747
|
+
const request = parseJsonRpcToolsCall(init.body);
|
|
1748
|
+
if (!request) return init;
|
|
1749
|
+
let envelope;
|
|
1750
|
+
try {
|
|
1751
|
+
envelope = decodeBase64Json(paymentHeader);
|
|
1752
|
+
} catch {
|
|
1753
|
+
return init;
|
|
1754
|
+
}
|
|
1755
|
+
if (!isRecord(envelope)) return init;
|
|
1756
|
+
const params = request.params;
|
|
1757
|
+
const existingMeta = isRecord(params._meta) ? params._meta : {};
|
|
1758
|
+
return {
|
|
1759
|
+
...init,
|
|
1760
|
+
body: JSON.stringify({
|
|
1761
|
+
...request,
|
|
1762
|
+
params: { ...params, _meta: { ...existingMeta, [MCP_X402_PAYMENT_META_KEY]: envelope } }
|
|
1763
|
+
})
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
function encodeMcpSettlementReceipt(settlement) {
|
|
1767
|
+
return encodeBase64Json(settlement);
|
|
1768
|
+
}
|
|
1769
|
+
function parseJsonRpcToolsCall(body) {
|
|
1770
|
+
if (typeof body !== "string") return void 0;
|
|
1771
|
+
let request;
|
|
1772
|
+
try {
|
|
1773
|
+
request = JSON.parse(body);
|
|
1774
|
+
} catch {
|
|
1775
|
+
return void 0;
|
|
1776
|
+
}
|
|
1777
|
+
if (!isRecord(request) || request.method !== "tools/call" || !isRecord(request.params)) return void 0;
|
|
1778
|
+
return request;
|
|
1779
|
+
}
|
|
1780
|
+
function isJsonRpcToolsCallBody(body) {
|
|
1781
|
+
return parseJsonRpcToolsCall(body) !== void 0;
|
|
1782
|
+
}
|
|
1783
|
+
function isRecord(value) {
|
|
1784
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1785
|
+
}
|
|
1671
1786
|
async function captureMerchantResponse(response) {
|
|
1672
1787
|
const merchant_body = await response.text().catch(() => "");
|
|
1673
1788
|
return {
|
|
@@ -2821,7 +2936,11 @@ var MerchantCompletion = class {
|
|
|
2821
2936
|
initialInit,
|
|
2822
2937
|
receipt.paymentHeader
|
|
2823
2938
|
);
|
|
2824
|
-
|
|
2939
|
+
const toolResult = retryResponse.ok ? await this.merchantTransport.readToolResult(retryResponse) : void 0;
|
|
2940
|
+
const inBandChallenge = toolResult ? extractMcpPaymentRequired(toolResult) : void 0;
|
|
2941
|
+
const metaSettlement = toolResult ? mcpSettlementFromToolResult(toolResult) : void 0;
|
|
2942
|
+
const inBandRejection = inBandChallenge !== void 0 || metaSettlement?.success === false;
|
|
2943
|
+
if (!retryResponse.ok || inBandRejection) {
|
|
2825
2944
|
const merchant = await captureMerchantResponse(retryResponse);
|
|
2826
2945
|
await this.recordRetryRejected({
|
|
2827
2946
|
rail: "x402",
|
|
@@ -2836,7 +2955,7 @@ var MerchantCompletion = class {
|
|
|
2836
2955
|
});
|
|
2837
2956
|
throw new HavenApiError(
|
|
2838
2957
|
"x402 retry failed after Haven funded the delegate wallet; reconciliation may be required.",
|
|
2839
|
-
merchant.merchant_status,
|
|
2958
|
+
inBandRejection ? X402_RETRY_REJECTED_STATUS : merchant.merchant_status,
|
|
2840
2959
|
{
|
|
2841
2960
|
marker: "x402_retry_rejected_after_funding",
|
|
2842
2961
|
payment_id: receipt.paymentId,
|
|
@@ -2848,7 +2967,10 @@ var MerchantCompletion = class {
|
|
|
2848
2967
|
}
|
|
2849
2968
|
);
|
|
2850
2969
|
}
|
|
2851
|
-
const
|
|
2970
|
+
const headerReceipt = retryResponse.headers.get("PAYMENT-RESPONSE");
|
|
2971
|
+
const protocolReceiptHeader = headerReceipt ?? (metaSettlement ? encodeMcpSettlementReceipt(metaSettlement) : void 0);
|
|
2972
|
+
const protocolReceiptHeaderName = headerReceipt ? "PAYMENT-RESPONSE" : metaSettlement ? `_meta.${MCP_X402_PAYMENT_RESPONSE_META_KEY}` : void 0;
|
|
2973
|
+
const merchantSettlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
|
|
2852
2974
|
if (receipt.merchant && merchantSettlement.settlementTxHash) {
|
|
2853
2975
|
receipt.merchant.settlementTxHash = merchantSettlement.settlementTxHash;
|
|
2854
2976
|
receipt.merchant.settlementExplorerUrl = buildExplorerUrl(
|
|
@@ -2866,8 +2988,8 @@ var MerchantCompletion = class {
|
|
|
2866
2988
|
selectedPayment: receipt.accepted,
|
|
2867
2989
|
paymentProofHeaderName: x402PaymentHeaderNamesSent(receipt.paymentHeader),
|
|
2868
2990
|
paymentProofHeader: receipt.paymentHeader,
|
|
2869
|
-
protocolReceiptHeaderName
|
|
2870
|
-
protocolReceiptHeader
|
|
2991
|
+
protocolReceiptHeaderName,
|
|
2992
|
+
protocolReceiptHeader
|
|
2871
2993
|
});
|
|
2872
2994
|
await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
|
|
2873
2995
|
return retryResponse;
|
|
@@ -3842,6 +3964,11 @@ var HavenClient = class {
|
|
|
3842
3964
|
const request = snapshotX402Request(url, initialInit);
|
|
3843
3965
|
const response = await this.merchantTransport.fetch(url, initialInit);
|
|
3844
3966
|
if (response.status !== 402) {
|
|
3967
|
+
const toolResultChallenge = await this.merchantTransport.extractToolResultChallenge(response, initialInit);
|
|
3968
|
+
if (toolResultChallenge) {
|
|
3969
|
+
const mcpTransport2 = await this.merchantTransport.detect(url, toolResultChallenge, response);
|
|
3970
|
+
return buildX402Quote(toolResultChallenge, request, options.idempotencyKey, mcpTransport2);
|
|
3971
|
+
}
|
|
3845
3972
|
let body;
|
|
3846
3973
|
try {
|
|
3847
3974
|
body = await response.clone().json();
|
|
@@ -4008,10 +4135,11 @@ var HavenClient = class {
|
|
|
4008
4135
|
return this.merchantCompletion.retryRequest(url ?? paymentRequired.resource.url, initialInit, paymentRequired, receipt);
|
|
4009
4136
|
}
|
|
4010
4137
|
/**
|
|
4011
|
-
* Fetch wrapper that automatically handles HTTP 402 responses
|
|
4138
|
+
* Fetch wrapper that automatically handles HTTP 402 responses — and, since
|
|
4139
|
+
* #3118, a native-MCP payment-required tool result answered under HTTP 200.
|
|
4012
4140
|
*
|
|
4013
|
-
* Works like the standard `fetch()` but intercepts 402 responses
|
|
4014
|
-
* pays via x402 through Haven, and retries the request.
|
|
4141
|
+
* Works like the standard `fetch()` but intercepts 402 responses (or that
|
|
4142
|
+
* tool-result challenge), pays via x402 through Haven, and retries the request.
|
|
4015
4143
|
*
|
|
4016
4144
|
* ```ts
|
|
4017
4145
|
* const response = await haven.fetch('https://paid-api.com/data')
|
|
@@ -4019,8 +4147,9 @@ var HavenClient = class {
|
|
|
4019
4147
|
* ```
|
|
4020
4148
|
*
|
|
4021
4149
|
* **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
|
-
*
|
|
4150
|
+
* MCP-shaped — the URL path ends in `/mcp`, or the 402 body (or, since
|
|
4151
|
+
* #3118, the tool-result challenge) carries a Coinbase Bazaar
|
|
4152
|
+
* `extensions.bazaar` block — the SDK runs the MCP
|
|
4024
4153
|
* `initialize` handshake, threads the resulting `mcp-session-id`,
|
|
4025
4154
|
* `Accept: application/json, text/event-stream`, and `x402-wallet` headers
|
|
4026
4155
|
* through every request, and collapses SSE responses to the JSON-RPC
|
|
@@ -4038,16 +4167,23 @@ var HavenClient = class {
|
|
|
4038
4167
|
let requestInit = withX402Wallet(init, x402PayerAddress(this.delegateAddress, this.x402Wallet));
|
|
4039
4168
|
if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
|
|
4040
4169
|
const response = await this.merchantTransport.fetch(url, requestInit);
|
|
4041
|
-
if (response.status !== 402) {
|
|
4042
|
-
return mcpSessionId ? this.merchantTransport.surfaceResult(response) : response;
|
|
4043
|
-
}
|
|
4044
4170
|
let paymentRequired;
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4171
|
+
let nativeChallenge = false;
|
|
4172
|
+
if (response.status !== 402) {
|
|
4173
|
+
const toolResultChallenge = await this.merchantTransport.extractToolResultChallenge(response, requestInit);
|
|
4174
|
+
if (!toolResultChallenge) {
|
|
4175
|
+
return mcpSessionId ? this.merchantTransport.surfaceResult(response) : response;
|
|
4176
|
+
}
|
|
4177
|
+
paymentRequired = toolResultChallenge;
|
|
4178
|
+
nativeChallenge = true;
|
|
4179
|
+
} else {
|
|
4180
|
+
try {
|
|
4181
|
+
paymentRequired = await parsePaymentRequiredResponse(response);
|
|
4182
|
+
} catch {
|
|
4183
|
+
return response;
|
|
4184
|
+
}
|
|
4049
4185
|
}
|
|
4050
|
-
if (!mcpSessionId && await this.merchantTransport.hasBazaarExtension(response)) {
|
|
4186
|
+
if (!mcpSessionId && (paymentRequired.extensions?.bazaar != null || await this.merchantTransport.hasBazaarExtension(response))) {
|
|
4051
4187
|
mcpSessionId = await this.merchantTransport.initialize(url, init);
|
|
4052
4188
|
if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
|
|
4053
4189
|
}
|
|
@@ -4069,7 +4205,7 @@ var HavenClient = class {
|
|
|
4069
4205
|
throw err;
|
|
4070
4206
|
}
|
|
4071
4207
|
const retryResponse = await this.merchantCompletion.retryRequest(url, requestInit, paymentRequired, receipt);
|
|
4072
|
-
return mcpSessionId ? this.merchantTransport.surfaceResult(retryResponse) : retryResponse;
|
|
4208
|
+
return mcpSessionId || nativeChallenge ? this.merchantTransport.surfaceResult(retryResponse) : retryResponse;
|
|
4073
4209
|
}
|
|
4074
4210
|
/**
|
|
4075
4211
|
* Deliver an already-signed x402 payment header to the merchant and return
|
|
@@ -4130,9 +4266,7 @@ var HavenClient = class {
|
|
|
4130
4266
|
let requestInit = withX402Wallet(input.init, x402Wallet) ?? {};
|
|
4131
4267
|
if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
|
|
4132
4268
|
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);
|
|
4269
|
+
const surfaced = await this.merchantTransport.surfaceResult(response);
|
|
4136
4270
|
const text = await surfaced.text();
|
|
4137
4271
|
let body;
|
|
4138
4272
|
try {
|
|
@@ -4140,8 +4274,16 @@ var HavenClient = class {
|
|
|
4140
4274
|
} catch {
|
|
4141
4275
|
body = text;
|
|
4142
4276
|
}
|
|
4277
|
+
const toolResult = mcpToolResultOf(body);
|
|
4278
|
+
const inBandChallenge = toolResult ? extractMcpPaymentRequired(toolResult) : void 0;
|
|
4279
|
+
const metaSettlement = toolResult ? mcpSettlementFromToolResult(toolResult) : void 0;
|
|
4280
|
+
const headerReceipt = surfaced.headers.get("PAYMENT-RESPONSE") ?? void 0;
|
|
4281
|
+
const protocolReceiptHeaderName = headerReceipt ? "PAYMENT-RESPONSE" : metaSettlement ? `_meta.${MCP_X402_PAYMENT_RESPONSE_META_KEY}` : void 0;
|
|
4282
|
+
const protocolReceiptHeader = headerReceipt ?? (metaSettlement ? encodeMcpSettlementReceipt(metaSettlement) : void 0);
|
|
4283
|
+
const settlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
|
|
4284
|
+
const merchantAccepted = surfaced.ok && inBandChallenge === void 0 && metaSettlement?.success !== false;
|
|
4143
4285
|
let evidenceOutcome;
|
|
4144
|
-
if (!
|
|
4286
|
+
if (!merchantAccepted) {
|
|
4145
4287
|
if (!input.noFundingLeg && fundingTxHash) {
|
|
4146
4288
|
await this.merchantCompletion.recordRetryRejected({
|
|
4147
4289
|
rail: "x402",
|
|
@@ -4171,7 +4313,7 @@ var HavenClient = class {
|
|
|
4171
4313
|
merchantStatus: surfaced.status,
|
|
4172
4314
|
paymentProofHeaderName: x402PaymentHeaderNamesSent(input.paymentHeader),
|
|
4173
4315
|
paymentProofHeader: input.paymentHeader,
|
|
4174
|
-
protocolReceiptHeaderName
|
|
4316
|
+
protocolReceiptHeaderName,
|
|
4175
4317
|
protocolReceiptHeader
|
|
4176
4318
|
});
|
|
4177
4319
|
}
|
|
@@ -4179,9 +4321,10 @@ var HavenClient = class {
|
|
|
4179
4321
|
}
|
|
4180
4322
|
return {
|
|
4181
4323
|
status: surfaced.status,
|
|
4182
|
-
ok:
|
|
4324
|
+
ok: merchantAccepted,
|
|
4183
4325
|
body,
|
|
4184
|
-
|
|
4326
|
+
// #3155 review S3: a transaction beside a rejection is not a settlement.
|
|
4327
|
+
settlementTxHash: merchantAccepted ? settlement.settlementTxHash ?? void 0 : void 0,
|
|
4185
4328
|
evidenceOutcome
|
|
4186
4329
|
};
|
|
4187
4330
|
}
|
|
@@ -4371,12 +4514,12 @@ var toolDescriptions = {
|
|
|
4371
4514
|
payX402OneShot: {
|
|
4372
4515
|
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
4516
|
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.",
|
|
4517
|
+
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
4518
|
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
4519
|
},
|
|
4377
4520
|
resumeX402: {
|
|
4378
4521
|
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
|
|
4522
|
+
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
4523
|
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
4524
|
},
|
|
4382
4525
|
// #1328: quoteMpp / payMpp / resumeMpp (the mpp_demo challenge/quote/resume
|
|
@@ -4415,8 +4558,8 @@ var toolDescriptions = {
|
|
|
4415
4558
|
},
|
|
4416
4559
|
listReceipts: {
|
|
4417
4560
|
summary: "List machine-payment receipts, newest first, by page.",
|
|
4418
|
-
selectionGuidance: "
|
|
4419
|
-
behavior: "Page
|
|
4561
|
+
selectionGuidance: "This agent's payment evidence, not the wallet's transaction history (sweeps, funding legs, other agents); use the allowance tool instead for remaining allowance or what-can-I-spend questions.",
|
|
4562
|
+
behavior: "Page { receipts, total, hasMore, nextCursor }; hasMore: pass nextCursor as cursor. parties.treasuryAccount is Haven's authoritative payer. protocolReceiptPayload is the merchant's PAYMENT-RESPONSE relayed verbatim: merchant-controlled, unverified, not Haven's record; payer may differ from payerAddress. Proof header values omitted.",
|
|
4420
4563
|
nextActionGuidance: ""
|
|
4421
4564
|
},
|
|
4422
4565
|
verifyReceipt: {
|