@haven_ai/sdk 0.1.27-alpha.0 → 0.1.29-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/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
- import { AsyncLocalStorage } from 'async_hooks';
2
- import { exact } from 'x402/schemes';
3
1
  import { recoverTypedDataAddress, hashTypedData } from 'viem';
4
- import { privateKeyToAccount } from 'viem/accounts';
5
2
  import { ethers } from 'ethers';
6
3
  import { createHash } from 'crypto';
4
+ import { AsyncLocalStorage } from 'async_hooks';
5
+ import { exact } from 'x402/schemes';
6
+ import { privateKeyToAccount } from 'viem/accounts';
7
7
 
8
8
  // src/client.ts
9
9
 
10
10
  // src/types.ts
11
+ var DEFAULT_CONFIRMATION_TIMEOUT_MS = 9e4;
11
12
  var AgentPaymentPhase = {
12
13
  /** The agent must sign and submit the prepared payment before Haven can relay it. */
13
14
  AgentSignatureRequired: "agent_signature_required",
@@ -392,24 +393,6 @@ function verifySignature(hash, signature, expectedAddress) {
392
393
  return false;
393
394
  }
394
395
  }
395
- var RECEIPT_VERSION = "haven-receipt-1";
396
- function defaultRecover(hash, signature) {
397
- return ethers.recoverAddress(hash, signature);
398
- }
399
- function verifyPaymentReceipt(receipt, recover = defaultRecover) {
400
- const { delegate, signHash: signHash2, signature } = receipt.authorization;
401
- if (!signature) return { verified: false, reason: "missing_signature" };
402
- let recovered;
403
- try {
404
- recovered = recover(signHash2, signature);
405
- } catch {
406
- return { verified: false, reason: "bad_signature" };
407
- }
408
- if (recovered.toLowerCase() !== delegate.toLowerCase()) {
409
- return { verified: false, reason: "signer_mismatch", recoveredSigner: recovered };
410
- }
411
- return { verified: true, recoveredSigner: recovered };
412
- }
413
396
 
414
397
  // src/base64.ts
415
398
  function normalizeBase64(value) {
@@ -777,7 +760,7 @@ function x402AuthorizationAmount(option) {
777
760
  return amount;
778
761
  }
779
762
  function buildX402ExpectedMessage(context) {
780
- const version = context.typedDataHash ? 2 : 1;
763
+ const version = context.payerDelegate ? 3 : context.typedDataHash ? 2 : 1;
781
764
  const payload = {
782
765
  version,
783
766
  kind: "haven.x402.expected",
@@ -795,6 +778,12 @@ function buildX402ExpectedMessage(context) {
795
778
  if (context.typedDataHash) {
796
779
  payload.typedDataHash = context.typedDataHash.toLowerCase();
797
780
  }
781
+ if (context.payerDelegate) {
782
+ payload.payerDelegate = context.payerDelegate.toLowerCase();
783
+ if (context.payerAgentId) {
784
+ payload.payerAgentId = context.payerAgentId;
785
+ }
786
+ }
798
787
  return `Haven x402 expected context v${version}
799
788
  ${stableStringify2(payload)}`;
800
789
  }
@@ -956,61 +945,173 @@ function stableStringify2(value) {
956
945
  const object = value;
957
946
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
958
947
  }
959
- function createJsonRpcProvider(url) {
960
- return new ethers.JsonRpcProvider(url);
961
- }
962
- function createWallet(privateKey, provider) {
963
- return new ethers.Wallet(privateKey, provider);
964
- }
965
- function createErc20Contract(address, abi, runner) {
966
- return new ethers.Contract(address, abi, runner);
967
- }
968
-
969
- // src/client.ts
970
948
  var DEFAULT_BASE_URL = "http://localhost:3001";
971
- var CHAIN_EXPLORER_TX = {
972
- 100: "https://gnosisscan.io/tx",
973
- 8453: "https://basescan.org/tx"
974
- };
975
- var CHAIN_USDC = {
976
- 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
977
- 84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
949
+ var DEFAULT_REQUEST_TIMEOUT = 3e4;
950
+ var HavenApiTransport = class {
951
+ apiKey;
952
+ baseUrl;
953
+ requestTimeout;
954
+ defaultHeaders;
955
+ requestContext = new AsyncLocalStorage();
956
+ constructor(config) {
957
+ this.apiKey = config.apiKey;
958
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
959
+ this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
960
+ this.defaultHeaders = { ...config.defaultHeaders ?? {} };
961
+ }
962
+ /** Run `fn` with extra headers scoped to its asynchronous Haven API work. */
963
+ withRequestContext(headers, fn) {
964
+ return this.requestContext.run({ headers: { ...headers } }, fn);
965
+ }
966
+ async post(path, body) {
967
+ return this.request("POST", path, body);
968
+ }
969
+ async get(path) {
970
+ return this.request("GET", path);
971
+ }
972
+ async request(method, path, body) {
973
+ const url = `${this.baseUrl}${path}`;
974
+ const controller = new AbortController();
975
+ const timeout = setTimeout(() => controller.abort(), this.requestTimeout);
976
+ try {
977
+ const contextHeaders = this.requestContext.getStore()?.headers ?? {};
978
+ const res = await fetch(url, {
979
+ method,
980
+ headers: {
981
+ "Content-Type": "application/json",
982
+ "Authorization": `Bearer ${this.apiKey}`,
983
+ ...this.defaultHeaders,
984
+ ...contextHeaders
985
+ },
986
+ body: body ? JSON.stringify(body) : void 0,
987
+ signal: controller.signal
988
+ });
989
+ const data = await res.json();
990
+ if (!res.ok) {
991
+ const record = data;
992
+ const errorText = typeof record.error === "string" ? record.error : void 0;
993
+ const rawDetails = record.details ?? record.detail;
994
+ const detailsText = typeof rawDetails === "string" ? rawDetails : rawDetails != null ? JSON.stringify(rawDetails) : void 0;
995
+ const message = errorText && detailsText ? `${errorText}: ${detailsText}` : errorText ?? detailsText ?? "API request failed";
996
+ throw new HavenApiError(message, res.status, data);
997
+ }
998
+ return data;
999
+ } catch (err) {
1000
+ if (err instanceof HavenApiError) throw err;
1001
+ if (err instanceof Error && err.name === "AbortError") {
1002
+ throw new HavenApiError(`Request to ${path} timed out`, 408);
1003
+ }
1004
+ throw new HavenApiError(
1005
+ `Request to ${path} failed: ${err instanceof Error ? err.message : String(err)}`,
1006
+ 0
1007
+ );
1008
+ } finally {
1009
+ clearTimeout(timeout);
1010
+ }
1011
+ }
978
1012
  };
979
- function buildExplorerUrl(chainId, txHash) {
980
- const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
981
- return `${base}/${txHash}`;
982
- }
983
- function explorerUrlOrEmpty(chainId, txHash) {
984
- return txHash ? buildExplorerUrl(chainId, txHash) : "";
1013
+
1014
+ // src/payment-mappers.ts
1015
+ function mapPaymentResult(raw, buildExplorerUrl2) {
1016
+ return {
1017
+ paymentId: raw.payment_id,
1018
+ status: raw.status,
1019
+ token: raw.token,
1020
+ amount: raw.amount,
1021
+ to: raw.to,
1022
+ txHash: raw.tx_hash,
1023
+ errorMessage: raw.error_message,
1024
+ explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl2(raw.chain_id, raw.tx_hash) : null),
1025
+ fee: raw.fee ? {
1026
+ amount: raw.fee.amount,
1027
+ token: raw.fee.token,
1028
+ basisPoints: raw.fee.basis_points,
1029
+ applied: raw.fee.applied
1030
+ } : null,
1031
+ createdAt: raw.created_at,
1032
+ signedAt: raw.signed_at,
1033
+ submittedAt: raw.submitted_at,
1034
+ confirmedAt: raw.confirmed_at,
1035
+ expiresAt: raw.expires_at
1036
+ };
985
1037
  }
986
- var DEFAULT_REQUEST_TIMEOUT = 3e4;
987
- var DEFAULT_MERCHANT_TIMEOUT = 3e5;
988
- var NOTIFY_TIMEOUT = 1e4;
989
- var DEFAULT_CONFIRMATION_TIMEOUT = 9e4;
990
- var DEFAULT_POLLING_INTERVAL = 3e3;
991
- function formatAtomicAmount(atomic, decimals) {
992
- if (atomic < 0n) return "0.0";
993
- const s = atomic.toString().padStart(decimals + 1, "0");
994
- const intPart = s.slice(0, s.length - decimals) || "0";
995
- const fracPart = s.slice(s.length - decimals).replace(/0+$/, "") || "0";
996
- return `${intPart}.${fracPart}`;
1038
+ function mapPaymentStatusResult(raw) {
1039
+ return {
1040
+ paymentId: raw.payment_id,
1041
+ kind: raw.kind,
1042
+ rail: raw.rail,
1043
+ status: raw.status,
1044
+ phase: raw.phase,
1045
+ nextAction: raw.next_action,
1046
+ amount: raw.amount,
1047
+ token: raw.token,
1048
+ resourceUrl: raw.resource_url,
1049
+ merchantAddress: raw.merchant_address,
1050
+ payerAddress: raw.payer_address ?? null,
1051
+ txHash: raw.tx_hash,
1052
+ expiresAt: raw.expires_at,
1053
+ chainId: raw.chain_id,
1054
+ message: raw.message,
1055
+ fee: raw.fee ? {
1056
+ amount: raw.fee.amount,
1057
+ token: raw.fee.token,
1058
+ basisPoints: raw.fee.basis_points,
1059
+ applied: raw.fee.applied
1060
+ } : null,
1061
+ amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? null,
1062
+ asset: raw.asset ?? raw.x402?.asset ?? null,
1063
+ network: raw.network ?? raw.x402?.network ?? null,
1064
+ description: raw.description ?? raw.x402?.description ?? null,
1065
+ idempotencyKey: raw.idempotency_key ?? raw.x402?.idempotency_key ?? null,
1066
+ x402: raw.x402 ? {
1067
+ amountAtomic: raw.x402.amount_atomic ?? raw.amount_atomic ?? null,
1068
+ asset: raw.x402.asset ?? raw.asset ?? null,
1069
+ network: raw.x402.network ?? raw.network ?? null,
1070
+ resourceUrl: raw.x402.resource_url ?? raw.resource_url,
1071
+ merchantAddress: raw.x402.merchant_address ?? raw.merchant_address,
1072
+ description: raw.x402.description ?? raw.description ?? null,
1073
+ idempotencyKey: raw.x402.idempotency_key ?? raw.idempotency_key ?? null
1074
+ } : void 0
1075
+ };
997
1076
  }
998
- function safeBigInt(value) {
999
- try {
1000
- return BigInt(value);
1001
- } catch {
1002
- return 0n;
1077
+ function mapPaymentReceipt(raw) {
1078
+ const receipt = {
1079
+ id: raw.id,
1080
+ paymentId: raw.payment_id,
1081
+ rail: raw.rail,
1082
+ proofStatus: raw.proof_status,
1083
+ txHash: raw.tx_hash,
1084
+ chainId: raw.chain_id,
1085
+ resourceUrl: raw.resource_url,
1086
+ merchantAddress: raw.merchant_address,
1087
+ payerAddress: raw.payer_address,
1088
+ settlementAddress: raw.settlement_address,
1089
+ tokenSymbol: raw.token_symbol,
1090
+ tokenAddress: raw.token_address,
1091
+ amountRaw: raw.amount_raw,
1092
+ amount: raw.amount_human,
1093
+ challengeId: raw.challenge_id,
1094
+ idempotencyKey: raw.idempotency_key,
1095
+ challengePayload: raw.challenge_payload,
1096
+ selectedPayment: raw.selected_payment,
1097
+ paymentProofHeaderName: raw.payment_proof_header_name,
1098
+ protocolReceiptHeaderName: raw.protocol_receipt_header_name,
1099
+ protocolReceiptPayload: raw.protocol_receipt_payload,
1100
+ merchantStatus: raw.merchant_status,
1101
+ confirmedAt: raw.confirmed_at,
1102
+ createdAt: raw.created_at,
1103
+ updatedAt: raw.updated_at
1104
+ };
1105
+ if ("payment_intent_id" in raw) {
1106
+ receipt.paymentIntentId = raw.payment_intent_id ?? null;
1003
1107
  }
1108
+ if ("approval_request_id" in raw) {
1109
+ receipt.approvalRequestId = raw.approval_request_id ?? null;
1110
+ }
1111
+ return receipt;
1004
1112
  }
1005
- function deriveReadiness(status, allowances) {
1006
- if (status !== "active") return "revoked";
1007
- const hasSpendable = allowances.some((a) => safeBigInt(a.remainingAtomic) > 0n);
1008
- return hasSpendable ? "ready" : "needs_approval";
1009
- }
1010
- var MCP_PROTOCOL_VERSION = "2025-06-18";
1011
- var MCP_ACCEPT = "application/json, text/event-stream";
1012
- var MCP_CLIENT_INFO = { name: "haven-sdk", version: "1" };
1013
- var MERCHANT_BODY_SNIPPET_LIMIT = 1e3;
1113
+
1114
+ // src/payment-state.ts
1014
1115
  var PAYMENT_STATE_STATUS_CODES = {
1015
1116
  pending: 202,
1016
1117
  pending_approval: 202,
@@ -1023,15 +1124,8 @@ var PAYMENT_STATE_STATUS_CODES = {
1023
1124
  failed: 502,
1024
1125
  rejected: 409
1025
1126
  };
1026
- function chainIdFromNetwork(network) {
1027
- if (network === "base") return 8453;
1028
- if (network === "base-sepolia") return 84532;
1029
- if (!network?.startsWith("eip155:")) return void 0;
1030
- const chainId = Number(network.slice("eip155:".length));
1031
- return Number.isFinite(chainId) ? chainId : void 0;
1032
- }
1033
- function chainIdOrNull(network) {
1034
- return chainIdFromNetwork(network) ?? null;
1127
+ function paymentStateStatusCode(status, fallback = 502) {
1128
+ return PAYMENT_STATE_STATUS_CODES[status] ?? fallback;
1035
1129
  }
1036
1130
  function phaseForStatus(status) {
1037
1131
  if (status === "pending_signature") return AgentPaymentPhase.AgentSignatureRequired;
@@ -1074,69 +1168,1495 @@ function messageForState(label, status, paymentId, nextAction) {
1074
1168
  }
1075
1169
  return `${label} is ${status}; next_action=${nextAction} (payment_id: ${paymentId}).`;
1076
1170
  }
1077
- function sameAddress3(a, b) {
1078
- return Boolean(a && b && a.toLowerCase() === b.toLowerCase());
1079
- }
1080
- function decimalFromUsdcAtomic(value) {
1081
- const amount = BigInt(value);
1082
- const whole = amount / 1000000n;
1083
- const fraction = (amount % 1000000n).toString().padStart(6, "0").replace(/0+$/, "");
1084
- return fraction ? `${whole}.${fraction}` : whole.toString();
1085
- }
1086
- function normalizeDecimal(value) {
1087
- if (!value.includes(".")) return value.replace(/^0+(?=\d)/, "") || "0";
1088
- const [whole, fraction = ""] = value.split(".");
1089
- const normalizedWhole = whole.replace(/^0+(?=\d)/, "") || "0";
1090
- const normalizedFraction = fraction.replace(/0+$/, "");
1091
- return normalizedFraction ? `${normalizedWhole}.${normalizedFraction}` : normalizedWhole;
1092
- }
1093
- function parseMerchantSettlement(header) {
1094
- if (!header) return {};
1095
- const parsed = parseProtocolReceiptHeader(header);
1096
- const tx = typeof parsed?.transaction === "string" ? parsed.transaction : typeof parsed?.txHash === "string" ? parsed.txHash : typeof parsed?.tx_hash === "string" ? parsed.tx_hash : null;
1097
- return { settlementTxHash: tx };
1171
+ function paymentStateFromRaw(label, raw) {
1172
+ if (!raw.payment_id || !raw.status) return null;
1173
+ const phase = raw.phase ?? phaseForStatus(raw.status);
1174
+ const nextAction = raw.next_action ?? nextActionForStatus(raw.status);
1175
+ if (!phase || !nextAction) return null;
1176
+ const amount = raw.amount ?? raw.requested ?? "";
1177
+ const token = raw.token ?? "";
1178
+ const message = raw.message ?? raw.error ?? messageForState(label, raw.status, raw.payment_id, nextAction);
1179
+ return {
1180
+ paymentId: raw.payment_id,
1181
+ kind: raw.kind === "payment_intent" ? "payment_intent" : "approval_request",
1182
+ rail: raw.rail ?? "direct",
1183
+ status: raw.status === "pending" ? "pending_approval" : raw.status,
1184
+ phase,
1185
+ nextAction,
1186
+ amount,
1187
+ token,
1188
+ resourceUrl: raw.resource_url ?? null,
1189
+ merchantAddress: raw.merchant_address ?? raw.merchant_to ?? null,
1190
+ txHash: raw.tx_hash ?? null,
1191
+ expiresAt: raw.expires_at ?? "",
1192
+ chainId: raw.chain_id ?? 0,
1193
+ message,
1194
+ amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? raw.mpp?.amount_atomic ?? null,
1195
+ asset: raw.asset ?? raw.x402?.asset ?? raw.mpp?.asset ?? null,
1196
+ network: raw.network ?? raw.x402?.network ?? raw.mpp?.network ?? null,
1197
+ description: raw.description ?? raw.x402?.description ?? raw.mpp?.description ?? null,
1198
+ idempotencyKey: raw.idempotency_key ?? raw.x402?.idempotency_key ?? raw.mpp?.idempotency_key ?? null,
1199
+ x402: raw.x402 ? {
1200
+ amountAtomic: raw.x402.amount_atomic ?? raw.amount_atomic ?? null,
1201
+ asset: raw.x402.asset ?? raw.asset ?? null,
1202
+ network: raw.x402.network ?? raw.network ?? null,
1203
+ resourceUrl: raw.x402.resource_url ?? raw.resource_url ?? null,
1204
+ merchantAddress: raw.x402.merchant_address ?? raw.merchant_address ?? raw.merchant_to ?? null,
1205
+ description: raw.x402.description ?? raw.description ?? null,
1206
+ idempotencyKey: raw.x402.idempotency_key ?? raw.idempotency_key ?? null
1207
+ } : void 0,
1208
+ mpp: raw.mpp ? {
1209
+ amountAtomic: raw.mpp.amount_atomic ?? raw.amount_atomic ?? null,
1210
+ asset: raw.mpp.asset ?? raw.asset ?? null,
1211
+ network: raw.mpp.network ?? raw.network ?? null,
1212
+ resourceUrl: raw.mpp.resource_url ?? raw.resource_url ?? null,
1213
+ merchantAddress: raw.mpp.merchant_address ?? raw.merchant_address ?? raw.merchant_to ?? null,
1214
+ description: raw.mpp.description ?? raw.description ?? null,
1215
+ idempotencyKey: raw.mpp.idempotency_key ?? raw.idempotency_key ?? null,
1216
+ challengeId: raw.mpp.challenge_id ?? raw.challenge_id ?? null
1217
+ } : void 0
1218
+ };
1098
1219
  }
1099
- function isMcpUrl(url) {
1100
- try {
1101
- return new URL(url).pathname.replace(/\/+$/, "").endsWith("/mcp");
1102
- } catch {
1103
- return /\/mcp(?:[/?#]|$)/.test(url);
1220
+ function throwPaymentStateError(label, raw) {
1221
+ const statusCode = paymentStateStatusCode(raw.status);
1222
+ const state = paymentStateFromRaw(label, raw);
1223
+ if (state) {
1224
+ throw new HavenPaymentStateError(state.message, statusCode, state, raw);
1104
1225
  }
1105
- }
1106
- async function responseHasBazaarExtension(response) {
1107
- try {
1108
- const body = await response.clone().json();
1109
- return body?.extensions?.bazaar != null;
1110
- } catch {
1111
- return false;
1226
+ if (raw.status === "pending_approval") {
1227
+ throw new HavenApiError(
1228
+ `${label} exceeds the on-chain allowance and was queued for owner approval (payment_id: ${raw.payment_id}).`,
1229
+ statusCode,
1230
+ raw
1231
+ );
1232
+ }
1233
+ if (raw.status === "expired") {
1234
+ throw new HavenApiError(
1235
+ `${label} expired before it could be completed (payment_id: ${raw.payment_id}).`,
1236
+ statusCode,
1237
+ raw
1238
+ );
1112
1239
  }
1240
+ const paymentId = raw.payment_id ? ` (payment_id: ${raw.payment_id})` : "";
1241
+ const message = raw.error ?? `${label} ${raw.status}${paymentId}`;
1242
+ throw new HavenApiError(message, statusCode, raw);
1113
1243
  }
1114
- function parseSseJsonRpcMessages(text) {
1115
- const messages = [];
1116
- let dataLines = [];
1117
- const flush = () => {
1118
- if (dataLines.length === 0) return;
1244
+
1245
+ // src/mcp-merchant-transport.ts
1246
+ var DEFAULT_MERCHANT_TIMEOUT = 3e5;
1247
+ var MCP_NOTIFICATION_TIMEOUT = 1e4;
1248
+ var MCP_PROTOCOL_VERSION = "2025-06-18";
1249
+ var MCP_ACCEPT = "application/json, text/event-stream";
1250
+ var MCP_CLIENT_INFO = { name: "haven-sdk", version: "1" };
1251
+ var McpMerchantTransport = class {
1252
+ merchantTimeout;
1253
+ fetchImpl;
1254
+ requestId = 0;
1255
+ constructor(options = {}) {
1256
+ this.merchantTimeout = options.merchantTimeout ?? DEFAULT_MERCHANT_TIMEOUT;
1257
+ this.fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
1258
+ }
1259
+ /** Fetch a merchant with a settlement-sized timeout and caller cancellation. */
1260
+ async fetch(url, init = {}, timeoutMs = this.merchantTimeout) {
1261
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
1262
+ const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
1119
1263
  try {
1120
- messages.push(JSON.parse(dataLines.join("\n")));
1264
+ return await this.fetchImpl(url, { ...init, signal });
1265
+ } catch (err) {
1266
+ if (timeoutSignal.aborted) {
1267
+ throw new MerchantTimeoutError(`Merchant request timed out after ${timeoutMs}ms: ${url}`);
1268
+ }
1269
+ throw err;
1270
+ }
1271
+ }
1272
+ /**
1273
+ * Establish an MCP Streamable-HTTP session. Any handshake failure degrades
1274
+ * to `undefined`, allowing the caller to fall back to plain x402.
1275
+ */
1276
+ async initialize(url, init, wallet) {
1277
+ try {
1278
+ const headers = new Headers(init?.headers);
1279
+ headers.set("Content-Type", "application/json");
1280
+ headers.set("Accept", MCP_ACCEPT);
1281
+ if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
1282
+ const response = await this.fetch(url, {
1283
+ method: "POST",
1284
+ headers,
1285
+ body: JSON.stringify({
1286
+ jsonrpc: "2.0",
1287
+ id: ++this.requestId,
1288
+ method: "initialize",
1289
+ params: {
1290
+ protocolVersion: MCP_PROTOCOL_VERSION,
1291
+ capabilities: {},
1292
+ clientInfo: MCP_CLIENT_INFO
1293
+ }
1294
+ })
1295
+ });
1296
+ if (!response.ok) return void 0;
1297
+ const sessionId = response.headers.get("mcp-session-id");
1298
+ if (!sessionId) return void 0;
1299
+ const message = await this.readMessage(response);
1300
+ if (message && "error" in message) return void 0;
1301
+ await this.notifyInitialized(url, init, sessionId, wallet);
1302
+ return sessionId;
1121
1303
  } catch {
1304
+ return void 0;
1122
1305
  }
1123
- dataLines = [];
1124
- };
1125
- for (const line of text.split(/\r?\n/)) {
1126
- if (line === "") {
1127
- flush();
1128
- continue;
1306
+ }
1307
+ /** Add the MCP session and response-content negotiation headers. */
1308
+ withSessionHeaders(init, sessionId) {
1309
+ const headers = new Headers(init?.headers);
1310
+ headers.set("mcp-session-id", sessionId);
1311
+ headers.set("Accept", MCP_ACCEPT);
1312
+ return { ...init, headers };
1313
+ }
1314
+ /** Read one JSON-RPC message from a JSON or SSE response without consuming it. */
1315
+ async readMessage(response) {
1316
+ let text;
1317
+ try {
1318
+ text = await response.clone().text();
1319
+ } catch {
1320
+ return void 0;
1321
+ }
1322
+ if ((response.headers.get("content-type") ?? "").includes("text/event-stream")) {
1323
+ return selectJsonRpcResult(parseSseJsonRpcMessages(text));
1129
1324
  }
1130
- if (line.startsWith("data:")) {
1131
- dataLines.push(line.slice(5).replace(/^ /, ""));
1325
+ try {
1326
+ return JSON.parse(text);
1327
+ } catch {
1328
+ return void 0;
1329
+ }
1330
+ }
1331
+ /**
1332
+ * Collapse an MCP SSE response to the JSON-RPC result. Non-SSE and
1333
+ * unparseable responses pass through with their original body untouched.
1334
+ */
1335
+ async surfaceResult(response) {
1336
+ if (!(response.headers.get("content-type") ?? "").includes("text/event-stream")) {
1337
+ return response;
1338
+ }
1339
+ let text;
1340
+ try {
1341
+ text = await response.clone().text();
1342
+ } catch {
1343
+ return response;
1344
+ }
1345
+ const message = selectJsonRpcResult(parseSseJsonRpcMessages(text));
1346
+ if (!message) return response;
1347
+ const body = "result" in message ? message.result : message;
1348
+ const headers = new Headers(response.headers);
1349
+ headers.set("content-type", "application/json");
1350
+ headers.delete("content-length");
1351
+ headers.delete("mcp-session-id");
1352
+ return new Response(JSON.stringify(body), {
1353
+ status: response.status,
1354
+ statusText: response.statusText,
1355
+ headers
1356
+ });
1357
+ }
1358
+ /** Detect MCP transport from the URL or Coinbase Bazaar extension. */
1359
+ async detect(url, paymentRequired, response) {
1360
+ if (isMcpUrl(url)) return { handshakeRequired: true, source: "path" };
1361
+ if (paymentRequired.extensions?.bazaar != null) {
1362
+ return { handshakeRequired: true, source: "bazaar" };
1363
+ }
1364
+ if (await responseHasBazaarExtension(response)) {
1365
+ return { handshakeRequired: true, source: "bazaar" };
1366
+ }
1367
+ return void 0;
1368
+ }
1369
+ /** Identify the conventional Streamable-HTTP MCP path without probing it. */
1370
+ isMcpUrl(url) {
1371
+ return isMcpUrl(url);
1372
+ }
1373
+ /** Detect Bazaar metadata without consuming the merchant response. */
1374
+ hasBazaarExtension(response) {
1375
+ return responseHasBazaarExtension(response);
1376
+ }
1377
+ /** Deliver an already-signed x402 header without changing the caller body. */
1378
+ async deliverPayment(url, init, paymentHeader) {
1379
+ const headers = new Headers(init?.headers);
1380
+ headers.set("X-PAYMENT", paymentHeader);
1381
+ return this.fetch(url, { ...init, headers });
1382
+ }
1383
+ async notifyInitialized(url, init, sessionId, wallet) {
1384
+ try {
1385
+ const headers = new Headers(init?.headers);
1386
+ headers.set("Content-Type", "application/json");
1387
+ headers.set("Accept", MCP_ACCEPT);
1388
+ headers.set("mcp-session-id", sessionId);
1389
+ if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
1390
+ await this.fetch(
1391
+ url,
1392
+ {
1393
+ method: "POST",
1394
+ headers,
1395
+ body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
1396
+ },
1397
+ MCP_NOTIFICATION_TIMEOUT
1398
+ );
1399
+ } catch {
1400
+ }
1401
+ }
1402
+ };
1403
+ async function captureMerchantResponse(response) {
1404
+ const merchant_body = await response.text().catch(() => "");
1405
+ return {
1406
+ merchant_status: response.status,
1407
+ merchant_status_text: response.statusText,
1408
+ merchant_headers: Object.fromEntries(response.headers.entries()),
1409
+ merchant_body
1410
+ };
1411
+ }
1412
+ function isMcpUrl(url) {
1413
+ try {
1414
+ return new URL(url).pathname.replace(/\/+$/, "").endsWith("/mcp");
1415
+ } catch {
1416
+ return /\/mcp(?:[/?#]|$)/.test(url);
1417
+ }
1418
+ }
1419
+ async function responseHasBazaarExtension(response) {
1420
+ try {
1421
+ const body = await response.clone().json();
1422
+ return body?.extensions?.bazaar != null;
1423
+ } catch {
1424
+ return false;
1425
+ }
1426
+ }
1427
+ function parseSseJsonRpcMessages(text) {
1428
+ const messages = [];
1429
+ let dataLines = [];
1430
+ const flush = () => {
1431
+ if (dataLines.length === 0) return;
1432
+ try {
1433
+ messages.push(JSON.parse(dataLines.join("\n")));
1434
+ } catch {
1435
+ }
1436
+ dataLines = [];
1437
+ };
1438
+ for (const line of text.split(/\r?\n/)) {
1439
+ if (line === "") {
1440
+ flush();
1441
+ continue;
1442
+ }
1443
+ if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
1444
+ }
1445
+ flush();
1446
+ return messages;
1447
+ }
1448
+ function selectJsonRpcResult(messages) {
1449
+ return messages.find((message) => "result" in message || "error" in message) ?? messages.at(-1);
1450
+ }
1451
+ var RECEIPT_VERSION = "haven-receipt-1";
1452
+ function defaultRecover(hash, signature) {
1453
+ return ethers.recoverAddress(hash, signature);
1454
+ }
1455
+ function verifyPaymentReceipt(receipt, recover = defaultRecover) {
1456
+ const { delegate, signHash: signHash2, signature } = receipt.authorization;
1457
+ if (!signature) return { verified: false, reason: "missing_signature" };
1458
+ let recovered;
1459
+ try {
1460
+ recovered = recover(signHash2, signature);
1461
+ } catch {
1462
+ return { verified: false, reason: "bad_signature" };
1463
+ }
1464
+ if (recovered.toLowerCase() !== delegate.toLowerCase()) {
1465
+ return { verified: false, reason: "signer_mismatch", recoveredSigner: recovered };
1466
+ }
1467
+ return { verified: true, recoveredSigner: recovered };
1468
+ }
1469
+
1470
+ // src/account-reads.ts
1471
+ function safeBigInt(value) {
1472
+ try {
1473
+ return BigInt(value);
1474
+ } catch {
1475
+ return 0n;
1476
+ }
1477
+ }
1478
+ function formatAtomicAmount(atomic, decimals) {
1479
+ if (atomic < 0n) return "0.0";
1480
+ const value = atomic.toString().padStart(decimals + 1, "0");
1481
+ const whole = value.slice(0, value.length - decimals) || "0";
1482
+ const fraction = value.slice(value.length - decimals).replace(/0+$/, "") || "0";
1483
+ return `${whole}.${fraction}`;
1484
+ }
1485
+ function deriveReadiness(status, allowances) {
1486
+ if (status !== "active") return "revoked";
1487
+ return allowances.some((allowance) => safeBigInt(allowance.remainingAtomic) > 0n) ? "ready" : "needs_approval";
1488
+ }
1489
+ var AccountReads = class {
1490
+ transport;
1491
+ getPaymentStatus;
1492
+ agentInFlight = null;
1493
+ constructor(options) {
1494
+ this.transport = options.transport;
1495
+ this.getPaymentStatus = options.getPaymentStatus;
1496
+ }
1497
+ async getAgent() {
1498
+ if (this.agentInFlight) return this.agentInFlight;
1499
+ const request = this.fetchAgent();
1500
+ this.agentInFlight = request;
1501
+ request.finally(() => {
1502
+ this.agentInFlight = null;
1503
+ }).catch(() => {
1504
+ });
1505
+ return request;
1506
+ }
1507
+ async getAgentSummary() {
1508
+ const [agent, allowanceSummary] = await Promise.all([this.getAgent(), this.getAllowances()]);
1509
+ const allowances = allowanceSummary.allowances.map((allowance) => {
1510
+ const token = resolveTokenFromAddress(allowance.tokenAddress);
1511
+ const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(allowance.onchain.remaining), token.decimals)} ${allowance.tokenSymbol}` : `${allowance.onchain.remaining} ${allowance.tokenSymbol} (atomic; unknown decimals)`;
1512
+ return {
1513
+ tokenSymbol: allowance.tokenSymbol,
1514
+ remainingAtomic: allowance.onchain.remaining,
1515
+ remainingDisplay,
1516
+ configuredAmount: allowance.configuredAmount,
1517
+ resetPeriodMin: allowance.resetPeriodMin,
1518
+ isResetPending: allowance.onchain.isResetPending
1519
+ };
1520
+ });
1521
+ const readiness = deriveReadiness(agent.status, allowances);
1522
+ return { ...agent, readiness, spend_authority_readiness: readiness, allowances };
1523
+ }
1524
+ async getAllowances() {
1525
+ const raw = await this.transport.get("/machine-payments/allowances");
1526
+ return {
1527
+ agentId: raw.agent_id,
1528
+ safeAddress: raw.safe_address,
1529
+ delegateAddress: raw.delegate_address,
1530
+ chainId: raw.chain_id,
1531
+ allowances: raw.allowances.map((allowance) => ({
1532
+ id: allowance.id,
1533
+ tokenAddress: allowance.token_address,
1534
+ tokenSymbol: allowance.token_symbol,
1535
+ configuredAmount: allowance.configured_amount,
1536
+ resetPeriodMin: allowance.reset_period_min,
1537
+ onchain: {
1538
+ amount: allowance.onchain.amount,
1539
+ spent: allowance.onchain.spent,
1540
+ remaining: allowance.onchain.remaining,
1541
+ effectiveSpent: allowance.onchain.effective_spent,
1542
+ resetTimeMin: allowance.onchain.reset_time_min,
1543
+ lastResetMin: allowance.onchain.last_reset_min,
1544
+ nonce: allowance.onchain.nonce,
1545
+ isResetPending: allowance.onchain.is_reset_pending,
1546
+ remainingIsFromChain: allowance.onchain.remaining_is_from_chain
1547
+ }
1548
+ }))
1549
+ };
1550
+ }
1551
+ async getPostPurchaseAllowanceSummary(paymentId) {
1552
+ const unavailable = (detail, payment2 = null) => ({
1553
+ payment: payment2,
1554
+ allowance: null,
1555
+ warnings: [{
1556
+ code: AgentPaymentWarningCode.AllowanceCheckUnavailable,
1557
+ message: `Could not read the post-purchase allowance/budget for payment ${paymentId} (${detail}). The payment itself succeeded \u2014 the on-chain policy remains the actual spend gate; this only affects the remaining-budget figure reported here.`
1558
+ }]
1559
+ });
1560
+ const [statusResult, agentResult, allowanceResult] = await Promise.allSettled([
1561
+ this.getPaymentStatus(paymentId),
1562
+ this.getAgent(),
1563
+ this.getAllowances()
1564
+ ]);
1565
+ if (statusResult.status === "rejected") {
1566
+ return unavailable(statusResult.reason instanceof Error ? statusResult.reason.message : String(statusResult.reason));
1567
+ }
1568
+ const payment = statusResult.value;
1569
+ if (agentResult.status === "rejected") {
1570
+ return unavailable(agentResult.reason instanceof Error ? agentResult.reason.message : String(agentResult.reason), payment);
1571
+ }
1572
+ if (allowanceResult.status === "rejected") {
1573
+ return unavailable(allowanceResult.reason instanceof Error ? allowanceResult.reason.message : String(allowanceResult.reason), payment);
1574
+ }
1575
+ try {
1576
+ const tokenAddress = payment.asset ?? payment.x402?.asset ?? null;
1577
+ if (!tokenAddress) return unavailable("the settled payment does not carry a resolvable token address", payment);
1578
+ const match = allowanceResult.value.allowances.find(
1579
+ (allowance) => allowance.tokenAddress.toLowerCase() === tokenAddress.toLowerCase()
1580
+ );
1581
+ if (!match) return unavailable("no allowance/budget row matches the settled token", payment);
1582
+ const token = resolveTokenFromAddress(match.tokenAddress);
1583
+ const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(match.onchain.remaining), token.decimals)} ${match.tokenSymbol}` : void 0;
1584
+ const rail = agentResult.value.executionRail;
1585
+ return {
1586
+ payment,
1587
+ allowance: {
1588
+ rail,
1589
+ remaining_atomic: match.onchain.remaining,
1590
+ ...remainingDisplay ? { remaining_display: remainingDisplay } : {},
1591
+ token_symbol: match.tokenSymbol,
1592
+ token_address: match.tokenAddress,
1593
+ reset_period: match.resetPeriodMin,
1594
+ source: rail === "delegation" ? "active_delegations" : "allowance_module"
1595
+ },
1596
+ warnings: []
1597
+ };
1598
+ } catch (error) {
1599
+ return unavailable(error instanceof Error ? error.message : String(error));
1600
+ }
1601
+ }
1602
+ async listReceipts(options = {}) {
1603
+ const query = options.limit ? `?limit=${encodeURIComponent(String(options.limit))}` : "";
1604
+ const raw = await this.transport.get(`/machine-payments/receipts${query}`);
1605
+ return raw.receipts.map(mapPaymentReceipt);
1606
+ }
1607
+ async getReceipt(paymentId) {
1608
+ const { receipt } = await this.transport.get(`/payments/${paymentId}/receipt`);
1609
+ return { receipt, verification: verifyPaymentReceipt(receipt) };
1610
+ }
1611
+ async fetchAgent() {
1612
+ const raw = await this.transport.get("/machine-payments/agent");
1613
+ return {
1614
+ id: raw.id,
1615
+ name: raw.name,
1616
+ status: raw.status,
1617
+ safeAddress: raw.safe_address,
1618
+ delegateAddress: raw.delegate_address,
1619
+ chainId: raw.chain_id,
1620
+ executionRail: raw.execution_rail === "delegation" ? "delegation" : "legacy"
1621
+ };
1622
+ }
1623
+ };
1624
+ function createJsonRpcProvider(url) {
1625
+ return new ethers.JsonRpcProvider(url);
1626
+ }
1627
+ function createWallet(privateKey, provider) {
1628
+ return new ethers.Wallet(privateKey, provider);
1629
+ }
1630
+ function createErc20Contract(address, abi, runner) {
1631
+ return new ethers.Contract(address, abi, runner);
1632
+ }
1633
+
1634
+ // src/delegate-sweep.ts
1635
+ function isWaitTimeout(err) {
1636
+ return err?.code === "TIMEOUT";
1637
+ }
1638
+ async function waitForSweepTx(tx) {
1639
+ let receipt;
1640
+ try {
1641
+ receipt = await tx.wait(1, DEFAULT_CONFIRMATION_TIMEOUT_MS);
1642
+ } catch (err) {
1643
+ if (!isWaitTimeout(err)) throw err;
1644
+ return { txHash: tx.hash, confirmation: "unconfirmed" };
1645
+ }
1646
+ if (!receipt) return { txHash: tx.hash, confirmation: "unconfirmed" };
1647
+ return { txHash: receipt.hash, confirmation: "confirmed" };
1648
+ }
1649
+ var DelegateSweepApi = class {
1650
+ constructor(options) {
1651
+ this.options = options;
1652
+ }
1653
+ options;
1654
+ async sweepDelegate() {
1655
+ if (!this.options.delegateKey) throw new HavenSigningError("delegateKey is required for sweepDelegate.");
1656
+ const agent = await this.options.getAgent();
1657
+ if (!agent.delegateAddress) throw new HavenApiError("Agent has no delegate address.", 422);
1658
+ const rpcUrl = this.options.chainRpcs[agent.chainId];
1659
+ if (!rpcUrl) throw new HavenApiError(`chainRpcs[${agent.chainId}] must be configured to sweep the delegate wallet.`, 422);
1660
+ const provider = createJsonRpcProvider(rpcUrl);
1661
+ const wallet = createWallet(this.options.delegateKey, provider);
1662
+ const transfers = [];
1663
+ if (isSweepableChain(agent.chainId)) {
1664
+ const contract = createErc20Contract(sweepUsdcAddress(agent.chainId), ["function balanceOf(address) view returns (uint256)", "function transfer(address to, uint256 amount) returns (bool)"], wallet);
1665
+ const balance2 = await contract.balanceOf(agent.delegateAddress);
1666
+ if (balance2 > 0n) {
1667
+ const tx = await contract.transfer(agent.safeAddress, balance2);
1668
+ const { txHash, confirmation } = await waitForSweepTx(tx);
1669
+ transfers.push({ asset: "USDC", amount: format(balance2, 6), amountAtomic: balance2.toString(), txHash, explorerUrl: this.options.buildExplorerUrl(agent.chainId, txHash), confirmation });
1670
+ }
1671
+ }
1672
+ const balance = await provider.getBalance(agent.delegateAddress);
1673
+ if (balance > 0n) {
1674
+ const fee = await provider.getFeeData();
1675
+ const send = balance - (fee.maxFeePerGas ?? fee.gasPrice ?? 1000000n) * 21000n * 2n;
1676
+ if (send > 0n) {
1677
+ const tx = await wallet.sendTransaction({ to: agent.safeAddress, value: send });
1678
+ const { txHash, confirmation } = await waitForSweepTx(tx);
1679
+ transfers.push({ asset: "ETH", amount: format(send, 18), amountAtomic: send.toString(), txHash, explorerUrl: this.options.buildExplorerUrl(agent.chainId, txHash), confirmation });
1680
+ }
1681
+ }
1682
+ return { fromAddress: agent.delegateAddress, toAddress: agent.safeAddress, chainId: agent.chainId, transfers, unconfirmed: transfers.some((t) => t.confirmation === "unconfirmed") };
1683
+ }
1684
+ prepareSweep() {
1685
+ return this.options.transport.post("/machine-payments/sweep/prepare", {});
1686
+ }
1687
+ submitSweep(authorization, signature) {
1688
+ return this.options.transport.post("/machine-payments/sweep/submit", { authorization, signature });
1689
+ }
1690
+ };
1691
+ function format(value, decimals) {
1692
+ const raw = value.toString().padStart(decimals + 1, "0");
1693
+ return `${raw.slice(0, -decimals) || "0"}.${raw.slice(-decimals).replace(/0+$/, "") || "0"}`;
1694
+ }
1695
+
1696
+ // src/x402-protocol.ts
1697
+ var CHAIN_EXPLORER_TX = {
1698
+ 100: "https://gnosisscan.io/tx",
1699
+ 8453: "https://basescan.org/tx"
1700
+ };
1701
+ function buildExplorerUrl(chainId, txHash) {
1702
+ const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
1703
+ return `${base}/${txHash}`;
1704
+ }
1705
+ function explorerUrlOrEmpty(chainId, txHash) {
1706
+ return txHash ? buildExplorerUrl(chainId, txHash) : "";
1707
+ }
1708
+ function chainIdFromNetwork(network) {
1709
+ if (network === "base") return 8453;
1710
+ if (network === "base-sepolia") return 84532;
1711
+ if (!network?.startsWith("eip155:")) return void 0;
1712
+ const chainId = Number(network.slice("eip155:".length));
1713
+ return Number.isFinite(chainId) ? chainId : void 0;
1714
+ }
1715
+ function chainIdOrNull(network) {
1716
+ return chainIdFromNetwork(network) ?? null;
1717
+ }
1718
+ function sameAddress3(a, b) {
1719
+ return Boolean(a && b && a.toLowerCase() === b.toLowerCase());
1720
+ }
1721
+ function decimalFromUsdcAtomic(value) {
1722
+ const amount = BigInt(value);
1723
+ const whole = amount / 1000000n;
1724
+ const fraction = (amount % 1000000n).toString().padStart(6, "0").replace(/0+$/, "");
1725
+ return fraction ? `${whole}.${fraction}` : whole.toString();
1726
+ }
1727
+ function normalizeDecimal(value) {
1728
+ if (!value.includes(".")) return value.replace(/^0+(?=\d)/, "") || "0";
1729
+ const [whole, fraction = ""] = value.split(".");
1730
+ const normalizedWhole = whole.replace(/^0+(?=\d)/, "") || "0";
1731
+ const normalizedFraction = fraction.replace(/0+$/, "");
1732
+ return normalizedFraction ? `${normalizedWhole}.${normalizedFraction}` : normalizedWhole;
1733
+ }
1734
+ function x402PayerAddress(delegateAddress, x402Wallet) {
1735
+ return delegateAddress ?? x402Wallet;
1736
+ }
1737
+ function withX402Wallet(init, wallet) {
1738
+ if (!wallet) return init;
1739
+ const headers = new Headers(init?.headers);
1740
+ if (!headers.has("x402-wallet")) {
1741
+ headers.set("x402-wallet", wallet);
1742
+ }
1743
+ return {
1744
+ ...init,
1745
+ headers
1746
+ };
1747
+ }
1748
+ function snapshotRequestBody(body) {
1749
+ if (body == null) return void 0;
1750
+ if (typeof body === "string") return body;
1751
+ if (body instanceof URLSearchParams) return body.toString();
1752
+ throw new HavenApiError(
1753
+ "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.",
1754
+ 400
1755
+ );
1756
+ }
1757
+ function snapshotX402Request(url, init) {
1758
+ return {
1759
+ url,
1760
+ method: init?.method ?? "GET",
1761
+ headers: Array.from(new Headers(init?.headers).entries()),
1762
+ body: snapshotRequestBody(init?.body)
1763
+ };
1764
+ }
1765
+ function requestInitFromSnapshot(request) {
1766
+ return {
1767
+ method: request.method,
1768
+ headers: request.headers,
1769
+ body: request.body
1770
+ };
1771
+ }
1772
+ function buildX402Quote(paymentRequired, request, idempotencyKey, mcpTransport) {
1773
+ const option = selectStandardPaymentOption(paymentRequired.accepts);
1774
+ if (!option) {
1775
+ throw new HavenApiError(
1776
+ "No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC.",
1777
+ 400
1778
+ );
1779
+ }
1780
+ const token = resolveTokenFromAddress(option.asset, option.network);
1781
+ return {
1782
+ rail: "x402",
1783
+ idempotencyKey: idempotencyKey ?? buildX402IdempotencyKey(paymentRequired, option),
1784
+ paymentRequired,
1785
+ accepted: option,
1786
+ request,
1787
+ ...mcpTransport ? { mcpTransport } : {},
1788
+ resourceUrl: paymentRequired.resource.url,
1789
+ description: paymentRequired.resource.description ?? option.description ?? null,
1790
+ mimeType: paymentRequired.resource.mimeType ?? option.mimeType ?? null,
1791
+ amountAtomic: x402AuthorizationAmount(option),
1792
+ amount: decimalFromUsdcAtomic(x402AuthorizationAmount(option)),
1793
+ token: token?.symbol ?? "USDC",
1794
+ // #1351: null when the asset is unrecognised on this network — the
1795
+ // `token` fallback above is a LABEL, not evidence of 6 decimals, and a
1796
+ // human-denominated cap must fail closed rather than convert against a
1797
+ // guess. Same resolution as `token`, so the two never disagree.
1798
+ decimals: token?.decimals ?? null,
1799
+ asset: option.asset,
1800
+ network: option.network,
1801
+ chainId: chainIdOrNull(option.network),
1802
+ merchantAddress: option.payTo,
1803
+ maxTimeoutSeconds: option.maxTimeoutSeconds
1804
+ };
1805
+ }
1806
+ function buildX402Receipt(input) {
1807
+ const fundingExplorerUrl = input.explorerUrl || explorerUrlOrEmpty(input.chainId, input.txHash);
1808
+ return {
1809
+ success: true,
1810
+ paymentId: input.paymentId,
1811
+ txHash: input.txHash,
1812
+ token: input.token,
1813
+ amount: input.amount,
1814
+ to: input.to,
1815
+ resourceUrl: input.resourceUrl,
1816
+ explorerUrl: input.explorerUrl,
1817
+ accepted: input.accepted,
1818
+ paymentHeader: input.paymentHeader,
1819
+ merchantTo: input.merchantTo ?? input.accepted.payTo,
1820
+ payer: input.payer,
1821
+ chainId: input.chainId,
1822
+ haven: {
1823
+ paymentId: input.paymentId,
1824
+ fundingTxHash: input.txHash,
1825
+ fundingExplorerUrl
1826
+ },
1827
+ merchant: {
1828
+ payTo: input.merchantTo ?? input.accepted.payTo
1829
+ },
1830
+ x402: {
1831
+ amount: x402AuthorizationAmount(input.accepted),
1832
+ token: input.token,
1833
+ network: input.accepted.network,
1834
+ asset: input.accepted.asset,
1835
+ resource: input.accepted.resource ?? input.resourceUrl
1836
+ }
1837
+ };
1838
+ }
1839
+ function buildX402ResumeState(input) {
1840
+ const token = resolveTokenFromAddress(input.accepted.asset, input.accepted.network);
1841
+ return {
1842
+ rail: "x402",
1843
+ paymentId: input.paymentId,
1844
+ idempotencyKey: input.idempotencyKey,
1845
+ paymentRequired: input.paymentRequired,
1846
+ accepted: input.accepted,
1847
+ url: input.request?.url ?? input.paymentRequired.resource.url,
1848
+ request: input.request,
1849
+ resourceUrl: input.paymentRequired.resource.url,
1850
+ description: input.paymentRequired.resource.description ?? input.accepted.description ?? null,
1851
+ amountAtomic: x402AuthorizationAmount(input.accepted),
1852
+ amount: decimalFromUsdcAtomic(x402AuthorizationAmount(input.accepted)),
1853
+ token: token?.symbol ?? "USDC",
1854
+ asset: input.accepted.asset,
1855
+ network: input.accepted.network,
1856
+ chainId: chainIdOrNull(input.accepted.network),
1857
+ merchantAddress: input.accepted.payTo
1858
+ };
1859
+ }
1860
+ function attachX402ResumeState(err, paymentRequired, accepted, idempotencyKey, request) {
1861
+ if (!(err instanceof HavenPaymentStateError)) return;
1862
+ if (err.state.rail !== "x402") return;
1863
+ err.resumeState = buildX402ResumeState({
1864
+ paymentId: err.state.paymentId,
1865
+ paymentRequired,
1866
+ accepted,
1867
+ idempotencyKey,
1868
+ request
1869
+ });
1870
+ }
1871
+ function attachResumeState(err, input) {
1872
+ attachX402ResumeState(
1873
+ err,
1874
+ input.paymentRequired,
1875
+ input.accepted,
1876
+ input.idempotencyKey,
1877
+ input.request
1878
+ );
1879
+ }
1880
+ function assertCanResumeX402(status, paymentRequired, option) {
1881
+ if (status.rail !== "x402") {
1882
+ throw new HavenPaymentStateError(
1883
+ `Payment ${status.paymentId} is ${status.rail}, not x402.`,
1884
+ 409,
1885
+ status
1886
+ );
1887
+ }
1888
+ if (status.nextAction !== AgentPaymentNextAction.RetryOriginalX402Request) {
1889
+ throw new HavenPaymentStateError(status.message, paymentStateStatusCode(status.status, 409), status);
1890
+ }
1891
+ if (!status.txHash) {
1892
+ throw new HavenApiError(
1893
+ `x402 payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
1894
+ 502,
1895
+ status,
1896
+ status.paymentId
1897
+ );
1898
+ }
1899
+ if (status.resourceUrl && status.resourceUrl !== paymentRequired.resource.url) {
1900
+ throw new HavenApiError(
1901
+ "x402 resume request does not match the approved resource URL.",
1902
+ 409,
1903
+ { status, paymentRequired },
1904
+ status.paymentId
1905
+ );
1906
+ }
1907
+ if (status.merchantAddress && !sameAddress3(status.merchantAddress, option.payTo)) {
1908
+ throw new HavenApiError(
1909
+ "x402 resume request does not match the approved merchant.",
1910
+ 409,
1911
+ { status, selectedPayment: option },
1912
+ status.paymentId
1913
+ );
1914
+ }
1915
+ const optionChainId = chainIdFromNetwork(option.network);
1916
+ if (status.chainId && optionChainId && status.chainId !== optionChainId) {
1917
+ throw new HavenApiError(
1918
+ "x402 resume request does not match the approved network.",
1919
+ 409,
1920
+ { status, selectedPayment: option },
1921
+ status.paymentId
1922
+ );
1923
+ }
1924
+ if (status.token && status.token !== "USDC") {
1925
+ throw new HavenApiError(
1926
+ "x402 resume request does not match the approved token.",
1927
+ 409,
1928
+ { status, selectedPayment: option },
1929
+ status.paymentId
1930
+ );
1931
+ }
1932
+ const approvedAmount = status.amount ? normalizeDecimal(status.amount) : "";
1933
+ const requestedAmount = normalizeDecimal(decimalFromUsdcAtomic(x402AuthorizationAmount(option)));
1934
+ if (approvedAmount && approvedAmount !== requestedAmount) {
1935
+ throw new HavenApiError(
1936
+ "x402 resume request does not match the approved amount.",
1937
+ 409,
1938
+ { status, selectedPayment: option },
1939
+ status.paymentId
1940
+ );
1941
+ }
1942
+ }
1943
+ var X402FundingLeg = class {
1944
+ delegateKey;
1945
+ delegateAddress;
1946
+ x402Wallet;
1947
+ chainRpcs;
1948
+ post;
1949
+ signForData;
1950
+ assertSignableAuthorizationState;
1951
+ /**
1952
+ * Receipts keyed by idempotency key, held only as long as the underlying
1953
+ * EIP-3009 authorization is valid. The cache belongs to this module rather
1954
+ * than to the facade because its expiry is read out of the authorization
1955
+ * header itself — a 3009 artifact.
1956
+ */
1957
+ receiptCache = /* @__PURE__ */ new Map();
1958
+ constructor(options) {
1959
+ this.delegateKey = options.delegateKey;
1960
+ this.delegateAddress = options.delegateAddress;
1961
+ this.x402Wallet = options.x402Wallet;
1962
+ this.chainRpcs = options.chainRpcs;
1963
+ this.post = options.post;
1964
+ this.signForData = options.signForData;
1965
+ this.assertSignableAuthorizationState = options.assertSignableAuthorizationState;
1966
+ }
1967
+ // ── Receipt cache ────────────────────────────────────────────────
1968
+ /** A still-valid cached receipt for this key, or undefined. */
1969
+ cachedReceipt(idempotencyKey) {
1970
+ const cached = this.receiptCache.get(idempotencyKey);
1971
+ if (cached && cached.expiresAt > Date.now()) return cached.receipt;
1972
+ return void 0;
1973
+ }
1974
+ cacheReceipt(idempotencyKey, paymentHeader, receipt) {
1975
+ const expiresAt = getPaymentHeaderValidBefore(paymentHeader);
1976
+ if (expiresAt > Date.now()) {
1977
+ this.receiptCache.set(idempotencyKey, { expiresAt, receipt });
1978
+ }
1979
+ }
1980
+ // ── Authorization ────────────────────────────────────────────────
1981
+ async authorize(paymentRequired, option, idempotencyKey) {
1982
+ const raw = await this.post("/x402", {
1983
+ url: paymentRequired.resource.url,
1984
+ payTo: this.delegateAddress,
1985
+ merchantPayTo: option.payTo,
1986
+ amount: x402AuthorizationAmount(option),
1987
+ asset: option.asset,
1988
+ network: option.network,
1989
+ description: paymentRequired.resource.description,
1990
+ idempotencyKey,
1991
+ // #1360: same explicit funding-leg declaration as createX402Intent —
1992
+ // this local-key path derives payTo from the key (never stale), but the
1993
+ // declaration keeps both writers of the 3009 shape loud-by-default.
1994
+ settlementScheme: "eip3009"
1995
+ });
1996
+ const state = paymentStateFromRaw("x402 payment", raw);
1997
+ const executedReplay = raw.success && raw.tx_hash ? "idempotency-collision" : state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request ? "approval-resume" : null;
1998
+ if (executedReplay) {
1999
+ const canFund = await this.delegateCanFund(
2000
+ raw.chain_id ?? state?.chainId ?? chainIdFromNetwork(option.network),
2001
+ option.asset,
2002
+ x402AuthorizationAmount(option)
2003
+ );
2004
+ const refuse = executedReplay === "idempotency-collision" ? canFund !== true : canFund === false;
2005
+ if (refuse) {
2006
+ const settledReceipt = state && executedReplay === "approval-resume" ? this.receiptFromStatus(paymentRequired, option, void 0, state) : this.receiptFromAuthorization(paymentRequired, option, void 0, raw);
2007
+ throw new X402AlreadySettledError(
2008
+ canFund === false ? "This x402 payment already settled \u2014 the delegate no longer holds the funds to authorize it again. To buy the same item a second time, pass a distinct `idempotencyKey`; the synthesised key intentionally collapses repeat calls for the same product within a 5-minute window so a retried request cannot pay twice." : "This x402 payment already settled, and whether the delegate can still fund a new authorization could not be verified (no `chainRpcs` entry for this chain). Refusing rather than issue an authorization that may be unfundable. To buy the same item a second time, pass a distinct `idempotencyKey`; to finish an interrupted payment, resume it by `paymentId`.",
2009
+ settledReceipt,
2010
+ canFund === false ? "settled" : "unverifiable"
2011
+ );
2012
+ }
2013
+ }
2014
+ const paymentHeader = await this.createPaymentHeader(paymentRequired, option);
2015
+ if (raw.success && raw.tx_hash) {
2016
+ const receipt2 = this.receiptFromAuthorization(paymentRequired, option, paymentHeader, raw);
2017
+ this.cacheReceipt(idempotencyKey, paymentHeader, receipt2);
2018
+ return receipt2;
2019
+ }
2020
+ if (state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request) {
2021
+ const receipt2 = this.receiptFromStatus(paymentRequired, option, paymentHeader, state);
2022
+ this.cacheReceipt(idempotencyKey, paymentHeader, receipt2);
2023
+ return receipt2;
2024
+ }
2025
+ this.assertSignableAuthorizationState("x402 payment", raw);
2026
+ if (!raw.sign_data?.hash) {
2027
+ throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
2028
+ }
2029
+ const sig = await this.signForData(raw.sign_data);
2030
+ const execResult = await this.post(
2031
+ `/payments/${raw.payment_id}/sign`,
2032
+ { signature: sig }
2033
+ );
2034
+ if (execResult.status !== "confirmed") {
2035
+ throwPaymentStateError("x402 payment", execResult);
2036
+ }
2037
+ await this.waitForFundingTx(
2038
+ execResult.tx_hash,
2039
+ execResult.chain_id ?? chainIdFromNetwork(option.network)
2040
+ );
2041
+ const receipt = this.receiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult);
2042
+ this.cacheReceipt(idempotencyKey, paymentHeader, receipt);
2043
+ return receipt;
2044
+ }
2045
+ // ── Header minting ───────────────────────────────────────────────
2046
+ async createPaymentHeader(paymentRequired, option) {
2047
+ if (!this.delegateKey) {
2048
+ throw new HavenSigningError("delegateKey is required to sign x402 payment headers.");
2049
+ }
2050
+ const account = privateKeyToAccount(this.delegateKey);
2051
+ const requirements = toStandardPaymentRequirements(paymentRequired, option);
2052
+ const header = await exact.evm.createPaymentHeader(
2053
+ account,
2054
+ paymentRequired.x402Version,
2055
+ requirements
2056
+ );
2057
+ if (paymentRequired.x402Version < 2) return header;
2058
+ const payment = decodeBase64Json(header);
2059
+ return encodeBase64Json({
2060
+ x402Version: paymentRequired.x402Version,
2061
+ accepted: option,
2062
+ payload: payment.payload
2063
+ });
2064
+ }
2065
+ // ── Receipt mapping ──────────────────────────────────────────────
2066
+ receiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult) {
2067
+ const txHash = execResult?.tx_hash ?? raw.tx_hash ?? "";
2068
+ const chainId = execResult?.chain_id ?? raw.chain_id ?? chainIdFromNetwork(option.network);
2069
+ const token = execResult?.token ?? raw.token ?? "USDC";
2070
+ const amount = execResult?.amount ?? raw.amount ?? decimalFromUsdcAtomic(x402AuthorizationAmount(option));
2071
+ const to = execResult?.to ?? raw.to ?? this.delegateAddress ?? "";
2072
+ const explorerUrl = execResult?.explorer_url ?? raw.explorer_url ?? explorerUrlOrEmpty(chainId, txHash);
2073
+ const merchantTo = execResult?.merchant_to ?? raw.merchant_to ?? option.payTo;
2074
+ const payer = raw.payer ?? raw.safe_address ?? raw.sign_data?.components.safe;
2075
+ return buildX402Receipt({
2076
+ paymentId: raw.payment_id,
2077
+ txHash,
2078
+ token,
2079
+ amount,
2080
+ to,
2081
+ resourceUrl: paymentRequired.resource.url,
2082
+ explorerUrl,
2083
+ accepted: option,
2084
+ paymentHeader,
2085
+ merchantTo,
2086
+ payer,
2087
+ chainId
2088
+ });
2089
+ }
2090
+ receiptFromStatus(paymentRequired, option, paymentHeader, status) {
2091
+ if (!status.txHash) {
2092
+ throw new HavenApiError(
2093
+ `x402 payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
2094
+ 502,
2095
+ status,
2096
+ status.paymentId
2097
+ );
2098
+ }
2099
+ return buildX402Receipt({
2100
+ paymentId: status.paymentId,
2101
+ txHash: status.txHash,
2102
+ token: status.token || "USDC",
2103
+ amount: status.amount || decimalFromUsdcAtomic(x402AuthorizationAmount(option)),
2104
+ to: this.delegateAddress ?? "",
2105
+ resourceUrl: paymentRequired.resource.url,
2106
+ explorerUrl: explorerUrlOrEmpty(status.chainId, status.txHash),
2107
+ accepted: option,
2108
+ paymentHeader,
2109
+ merchantTo: status.merchantAddress ?? option.payTo,
2110
+ payer: this.x402Wallet,
2111
+ chainId: status.chainId || chainIdFromNetwork(option.network)
2112
+ });
2113
+ }
2114
+ // ── On-chain reads ───────────────────────────────────────────────
2115
+ /**
2116
+ * Wait for a funding tx to be mined with ≥1 confirmation before the
2117
+ * merchant retry, eliminating the race where the merchant's
2118
+ * `balanceOf(delegate)` runs before the funding block propagates.
2119
+ *
2120
+ * Skipped when `chainRpcs` does not include the chain; in that case Haven's
2121
+ * backend has already confirmed on-chain submission and callers accept the
2122
+ * small propagation window as a trade-off for not configuring an RPC URL.
2123
+ */
2124
+ async waitForFundingTx(txHash, chainId, timeoutMs = 3e4) {
2125
+ if (!txHash || !chainId) return;
2126
+ const rpcUrl = this.chainRpcs[chainId];
2127
+ if (!rpcUrl) return;
2128
+ const provider = createJsonRpcProvider(rpcUrl);
2129
+ const onChainReceipt = await provider.waitForTransaction(txHash, 1, timeoutMs);
2130
+ if (!onChainReceipt || onChainReceipt.status !== 1) {
2131
+ throw new HavenApiError(
2132
+ "Funding tx did not confirm on-chain within the timeout window.",
2133
+ 500,
2134
+ { txHash, chainId }
2135
+ );
2136
+ }
2137
+ }
2138
+ /**
2139
+ * Can the delegate EOA still fund an authorization for `amountAtomic`?
2140
+ *
2141
+ * #1521: the only question that separates a legitimate resume (funding
2142
+ * confirmed, merchant never paid — the delegate still holds the money) from
2143
+ * a replayed settled payment (funding confirmed, merchant paid, delegate
2144
+ * spent). The intent's own `status: 'confirmed'` is identical in both.
2145
+ *
2146
+ * The balance is asked of the CHAIN rather than of Haven's bookkeeping on
2147
+ * purpose: the merchant-settlement evidence record is written by this SDK
2148
+ * *after* the merchant call, so a client that dies between the two leaves
2149
+ * the backend believing the merchant was never paid — the exact case the
2150
+ * discriminator has to get right. The chain cannot be behind in that way.
2151
+ *
2152
+ * Returns `null` — never a guess — when `chainRpcs` has no entry for the
2153
+ * chain or the read fails. Callers must treat that as "unverifiable", not
2154
+ * as "funded".
2155
+ */
2156
+ async delegateCanFund(chainId, tokenAddress, amountAtomic, timeoutMs = 1e4) {
2157
+ if (!chainId || !this.delegateAddress) return null;
2158
+ const rpcUrl = this.chainRpcs[chainId];
2159
+ if (!rpcUrl) return null;
2160
+ try {
2161
+ const provider = createJsonRpcProvider(rpcUrl);
2162
+ const token = createErc20Contract(
2163
+ tokenAddress,
2164
+ ["function balanceOf(address) view returns (uint256)"],
2165
+ provider
2166
+ );
2167
+ const balance = await Promise.race([
2168
+ token.balanceOf(this.delegateAddress),
2169
+ new Promise((resolve) => setTimeout(() => resolve(null), timeoutMs).unref?.())
2170
+ ]);
2171
+ if (balance === null) return null;
2172
+ return balance >= BigInt(amountAtomic);
2173
+ } catch {
2174
+ return null;
2175
+ }
2176
+ }
2177
+ };
2178
+ function getPaymentHeaderValidBefore(paymentHeader) {
2179
+ try {
2180
+ const payment = decodeBase64Json(
2181
+ paymentHeader
2182
+ );
2183
+ const payload = payment.payload;
2184
+ const validBeforeSeconds = Number(payload.authorization?.validBefore);
2185
+ if (Number.isFinite(validBeforeSeconds)) return validBeforeSeconds * 1e3;
2186
+ } catch {
2187
+ }
2188
+ return 0;
2189
+ }
2190
+
2191
+ // src/x402-erc7710.ts
2192
+ var X402Erc7710 = class {
2193
+ delegateKey;
2194
+ post;
2195
+ signForData;
2196
+ getAgent;
2197
+ constructor(options) {
2198
+ this.delegateKey = options.delegateKey;
2199
+ this.post = options.post;
2200
+ this.signForData = options.signForData;
2201
+ this.getAgent = options.getAgent;
2202
+ }
2203
+ /**
2204
+ * Pay a merchant through **erc7710 direct settlement** (#1454, epic #1450).
2205
+ *
2206
+ * The whole point of this path is what it does NOT do. There is no funding
2207
+ * leg: the merchant redeems a delegation chain and pulls from the treasury
2208
+ * directly, so the delegate EOA never holds the money, no sweep can strand
2209
+ * it, and the #713 reconciliation class does not apply. It is also why this
2210
+ * method is SMALLER than the 3009 path — the backend assembles the merchant
2211
+ * `X-PAYMENT` header in `assembleSettlementPayload`, so the SDK builds no
2212
+ * header locally.
2213
+ *
2214
+ * authorize (payTo = the MERCHANT) → sign the child → settle → header
2215
+ *
2216
+ * The caller then retries the merchant with that header. **Nothing has
2217
+ * settled when this returns** — that is why it does not return an
2218
+ * `X402Receipt`.
2219
+ *
2220
+ * Requires a delegation-rail account. The backend enforces that
2221
+ * (`validateGenericSchemeRail`), and so does this method, before building a
2222
+ * request the backend would only reject: an error a client can explain is
2223
+ * worth more than a 400 it has to decode.
2224
+ *
2225
+ * **MCP callers must pass `options.resourceUrl`.** An in-band MCP 402
2226
+ * challenge frequently carries no `resource` object at all, so
2227
+ * `paymentRequired.resource?.url` is undefined and the backend answers
2228
+ * "Valid url is required". The QA scenario this path was ported from falls
2229
+ * back to the request URL for exactly that reason — the SDK cannot, because
2230
+ * it never saw the request. Pass it.
2231
+ */
2232
+ async settle(paymentRequired, options = {}) {
2233
+ if (!this.delegateKey) {
2234
+ throw new HavenSigningError(
2235
+ "delegateKey is required for x402 payments. Pass it in the HavenClient config."
2236
+ );
2237
+ }
2238
+ const prepared = await this.prepare(paymentRequired, options);
2239
+ const signature = await this.signForData(prepared.signData);
2240
+ const paymentHeader = await this.submit(prepared.paymentId, signature);
2241
+ return { ...prepared.settlement, paymentHeader };
2242
+ }
2243
+ /**
2244
+ * The AUTHORIZE half of erc7710 settlement (#1456): select the scheme, build
2245
+ * the request, and return the child to be signed — without signing it.
2246
+ *
2247
+ * Split out because the hosted topology cannot use `settleX402Erc7710()`:
2248
+ * that method signs in-process with `delegateKey`, and hosted Haven does not
2249
+ * have one and must not. The hosted MCP server drives these two halves with
2250
+ * the LOCAL signer in between, so the key stays where it belongs and the
2251
+ * request shaping stays in one place rather than being reimplemented.
2252
+ */
2253
+ async prepare(paymentRequired, options = {}) {
2254
+ const delegationRail = options.delegationRail ?? (await this.getAgent()).executionRail === "delegation";
2255
+ if (!delegationRail) {
2256
+ throw new HavenApiError(
2257
+ "erc7710 settlement requires a delegation-rail account; this one is not on it. Use authorizeX402() for the standard EIP-3009 path.",
2258
+ 400
2259
+ );
2260
+ }
2261
+ const selection = selectX402SettlementScheme(paymentRequired.accepts, { delegationRail });
2262
+ if (!selection || selection.scheme !== "erc7710") {
2263
+ throw new HavenApiError(
2264
+ "This merchant does not advertise an erc7710 settlement option (no accepts[] entry carries extra.assetTransferMethod: 'erc7710'). Use authorizeX402() for the standard EIP-3009 path.",
2265
+ 400
2266
+ );
2267
+ }
2268
+ const option = selection.option;
2269
+ const merchantPayTo = option.payTo;
2270
+ const amountAtomic = x402AuthorizationAmount(option);
2271
+ const raw = await this.post("/x402", {
2272
+ url: options.resourceUrl ?? paymentRequired.resource?.url,
2273
+ // payTo = the MERCHANT is what selects direct settlement server-side.
2274
+ // The explicit settlementScheme must AGREE with that shape (#1360) —
2275
+ // disagreement is a 400 by design, so that a stale delegate address
2276
+ // becomes a loud mismatch instead of a silent reroute to the 3009 leg.
2277
+ payTo: merchantPayTo,
2278
+ settlementScheme: "erc7710",
2279
+ amount: amountAtomic,
2280
+ asset: option.asset,
2281
+ network: option.network,
2282
+ // The v2 header echoes the accepted entry field-for-field, so the quoted
2283
+ // timeout must round-trip or the merchant rejects the echo (#1064).
2284
+ maxTimeoutSeconds: option.maxTimeoutSeconds,
2285
+ // #1058: forward the advertised facilitators verbatim — the child becomes
2286
+ // redeemable ONLY by them. `null` here means the merchant advertised none
2287
+ // (or an empty array, which the backend 400s on), so the field is OMITTED
2288
+ // rather than sent empty. See x402FacilitatorAddresses.
2289
+ ...selection.facilitatorAddresses ? { facilitatorAddresses: selection.facilitatorAddresses } : {},
2290
+ // #1307/#1547: persisted so the settle leg can rehydrate the merchant
2291
+ // call by payment_id on this scheme too, not only on the 3009 bridge.
2292
+ ...options.mcpCallContext ? { mcpCallContext: options.mcpCallContext } : {}
2293
+ });
2294
+ if (!raw.payment_id) {
2295
+ throw new HavenApiError("No payment_id returned from x402/authorize", 500, raw);
2296
+ }
2297
+ const signData = raw.sign_data;
2298
+ if (signData?.signature_scheme !== "eip712_delegation" || !signData.typed_data) {
2299
+ throw new HavenApiError(
2300
+ `x402/authorize did not return an erc7710 settlement child (signature_scheme was ${JSON.stringify(signData?.signature_scheme)}). Refusing to sign a payload this path did not ask for.`,
2301
+ 500,
2302
+ raw
2303
+ );
2304
+ }
2305
+ return {
2306
+ paymentId: raw.payment_id,
2307
+ signData,
2308
+ settlement: {
2309
+ paymentId: raw.payment_id,
2310
+ merchantPayTo,
2311
+ amountAtomic,
2312
+ asset: option.asset,
2313
+ network: option.network,
2314
+ facilitatorAddresses: selection.facilitatorAddresses
2315
+ }
2316
+ };
2317
+ }
2318
+ /**
2319
+ * The SETTLE half (#1456): exchange the signed child for the merchant header.
2320
+ *
2321
+ * The SDK builds no header on this path — the backend assembles the MetaMask
2322
+ * erc7710 payload in `assembleSettlementPayload`. Whoever produced the
2323
+ * signature (an in-process delegate key, or the local edge signer over the
2324
+ * hosted boundary) is irrelevant here.
2325
+ */
2326
+ async submit(paymentId, signature) {
2327
+ const settled = await this.post(
2328
+ `/x402/${paymentId}/settle`,
2329
+ { signature }
2330
+ );
2331
+ if (!settled.payment_header) {
2332
+ throw new HavenApiError(
2333
+ "x402 settle returned no payment_header \u2014 the merchant cannot be retried.",
2334
+ 500,
2335
+ settled
2336
+ );
2337
+ }
2338
+ return settled.payment_header;
2339
+ }
2340
+ };
2341
+
2342
+ // src/tool-adapter.ts
2343
+ function toolX402PaymentRequired(input) {
2344
+ return {
2345
+ x402Version: 2,
2346
+ resource: { url: input.url, description: input.description },
2347
+ accepts: [
2348
+ {
2349
+ scheme: "exact",
2350
+ network: input.network,
2351
+ amount: input.amount,
2352
+ asset: input.asset,
2353
+ payTo: input.payTo,
2354
+ maxTimeoutSeconds: 30
2355
+ }
2356
+ ]
2357
+ };
2358
+ }
2359
+ function x402ToolReceipt(receipt) {
2360
+ return {
2361
+ success: true,
2362
+ payment_id: receipt.paymentId,
2363
+ tx_hash: receipt.txHash,
2364
+ token: receipt.token,
2365
+ amount: receipt.amount,
2366
+ to: receipt.to,
2367
+ resource_url: receipt.resourceUrl,
2368
+ explorer_url: receipt.explorerUrl,
2369
+ payment_header: receipt.paymentHeader,
2370
+ merchant_to: receipt.merchantTo,
2371
+ payer: receipt.payer,
2372
+ chain_id: receipt.chainId,
2373
+ haven: receipt.haven,
2374
+ merchant: receipt.merchant,
2375
+ x402: receipt.x402
2376
+ };
2377
+ }
2378
+ function toolError(err) {
2379
+ if (err instanceof HavenPaymentStateError) {
2380
+ return {
2381
+ success: false,
2382
+ payment_id: err.state.paymentId,
2383
+ kind: err.state.kind,
2384
+ rail: err.state.rail,
2385
+ status: err.state.status,
2386
+ phase: err.state.phase,
2387
+ next_action: err.state.nextAction,
2388
+ tx_hash: err.state.txHash,
2389
+ token: err.state.token,
2390
+ amount: err.state.amount,
2391
+ resource_url: err.state.resourceUrl,
2392
+ merchant_address: err.state.merchantAddress,
2393
+ amount_atomic: err.state.amountAtomic,
2394
+ asset: err.state.asset,
2395
+ network: err.state.network,
2396
+ description: err.state.description,
2397
+ idempotency_key: err.state.idempotencyKey,
2398
+ x402: err.state.x402 ? {
2399
+ amount_atomic: err.state.x402.amountAtomic,
2400
+ asset: err.state.x402.asset,
2401
+ network: err.state.x402.network,
2402
+ resource_url: err.state.x402.resourceUrl,
2403
+ merchant_address: err.state.x402.merchantAddress,
2404
+ description: err.state.x402.description,
2405
+ idempotency_key: err.state.x402.idempotencyKey
2406
+ } : void 0,
2407
+ mpp: err.state.mpp ? {
2408
+ amount_atomic: err.state.mpp.amountAtomic,
2409
+ asset: err.state.mpp.asset,
2410
+ network: err.state.mpp.network,
2411
+ resource_url: err.state.mpp.resourceUrl,
2412
+ merchant_address: err.state.mpp.merchantAddress,
2413
+ description: err.state.mpp.description,
2414
+ idempotency_key: err.state.mpp.idempotencyKey,
2415
+ challenge_id: err.state.mpp.challengeId
2416
+ } : void 0,
2417
+ resume_state: err.resumeState,
2418
+ expires_at: err.state.expiresAt,
2419
+ chain_id: err.state.chainId,
2420
+ message: err.state.message,
2421
+ error: err.message
2422
+ };
2423
+ }
2424
+ if (err instanceof HavenApiError) {
2425
+ return {
2426
+ success: false,
2427
+ status_code: err.statusCode,
2428
+ error: err.message,
2429
+ body: err.body
2430
+ };
2431
+ }
2432
+ return {
2433
+ success: false,
2434
+ error: err instanceof Error ? err.message : String(err)
2435
+ };
2436
+ }
2437
+
2438
+ // src/merchant-completion.ts
2439
+ var MERCHANT_BODY_SNIPPET_LIMIT = 1e3;
2440
+ var MerchantCompletion = class {
2441
+ post;
2442
+ merchantTransport;
2443
+ getPaymentStatus;
2444
+ getAgent;
2445
+ delegateAddress;
2446
+ x402Wallet;
2447
+ constructor(options) {
2448
+ this.post = options.post;
2449
+ this.merchantTransport = options.merchantTransport;
2450
+ this.getPaymentStatus = options.getPaymentStatus;
2451
+ this.getAgent = options.getAgent;
2452
+ this.delegateAddress = options.delegateAddress;
2453
+ this.x402Wallet = options.x402Wallet;
2454
+ }
2455
+ async retryRequest(url, initialInit, paymentRequired, receipt) {
2456
+ if (!receipt.accepted) {
2457
+ throw new HavenApiError("No accepted x402 option was recorded for payment retry", 500);
2458
+ }
2459
+ if (!receipt.paymentHeader) {
2460
+ throw new HavenApiError("No x402 payment header was returned for payment retry", 500);
2461
+ }
2462
+ const retryResponse = await this.merchantTransport.deliverPayment(
2463
+ url,
2464
+ initialInit,
2465
+ receipt.paymentHeader
2466
+ );
2467
+ if (!retryResponse.ok) {
2468
+ const merchant = await captureMerchantResponse(retryResponse);
2469
+ await this.recordRetryRejected({
2470
+ rail: "x402",
2471
+ paymentId: receipt.paymentId,
2472
+ txHash: receipt.txHash,
2473
+ resourceUrl: receipt.resourceUrl,
2474
+ merchant,
2475
+ details: {
2476
+ merchant_to: receipt.merchantTo,
2477
+ delegate_to: receipt.to
2478
+ }
2479
+ });
2480
+ throw new HavenApiError(
2481
+ "x402 retry failed after Haven funded the delegate wallet; reconciliation may be required.",
2482
+ merchant.merchant_status,
2483
+ {
2484
+ marker: "x402_retry_rejected_after_funding",
2485
+ payment_id: receipt.paymentId,
2486
+ tx_hash: receipt.txHash,
2487
+ resource_url: receipt.resourceUrl,
2488
+ merchant_to: receipt.merchantTo,
2489
+ delegate_to: receipt.to,
2490
+ ...merchant
2491
+ }
2492
+ );
2493
+ }
2494
+ const merchantSettlement = parseMerchantSettlement(retryResponse.headers.get("PAYMENT-RESPONSE"));
2495
+ if (receipt.merchant && merchantSettlement.settlementTxHash) {
2496
+ receipt.merchant.settlementTxHash = merchantSettlement.settlementTxHash;
2497
+ receipt.merchant.settlementExplorerUrl = buildExplorerUrl(
2498
+ receipt.chainId,
2499
+ merchantSettlement.settlementTxHash
2500
+ );
2501
+ }
2502
+ await this.reportEvidence({
2503
+ paymentId: receipt.paymentId,
2504
+ rail: "x402",
2505
+ txHash: receipt.txHash,
2506
+ resourceUrl: receipt.resourceUrl,
2507
+ merchantStatus: retryResponse.status,
2508
+ challengePayload: paymentRequired,
2509
+ selectedPayment: receipt.accepted,
2510
+ paymentProofHeaderName: "X-PAYMENT",
2511
+ paymentProofHeader: receipt.paymentHeader,
2512
+ protocolReceiptHeaderName: "PAYMENT-RESPONSE",
2513
+ protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
2514
+ });
2515
+ await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
2516
+ return retryResponse;
2517
+ }
2518
+ /**
2519
+ * #956: capture the merchant's OWN receipt when the paid response carries
2520
+ * one, and report it to Haven so the reporting feed can attach it next to
2521
+ * the Haven-generated payment evidence (#498). Two supported signals on the
2522
+ * paid response:
2523
+ *
2524
+ * x-receipt-json: base64-encoded JSON receipt document (inline)
2525
+ * x-receipt-url: https URL to the receipt document (reference)
2526
+ *
2527
+ * Strictly best-effort: absence is the normal case, and no failure here may
2528
+ * ever affect the completed payment — the response is already paid for.
2529
+ */
2530
+ async reportMerchantReceipt(paymentId, response) {
2531
+ try {
2532
+ const inlineB64 = response.headers.get("x-receipt-json");
2533
+ const url = response.headers.get("x-receipt-url");
2534
+ if (!inlineB64 && !url) return;
2535
+ let body = null;
2536
+ if (inlineB64) {
2537
+ if (inlineB64.length > Math.ceil(64 * 1024 * 4 / 3)) return;
2538
+ const decoded = JSON.parse(Buffer.from(inlineB64, "base64").toString("utf8"));
2539
+ if (decoded && typeof decoded === "object") body = { json: decoded };
2540
+ } else if (url && url.startsWith("https://") && url.length <= 2048) {
2541
+ body = { url };
2542
+ }
2543
+ if (!body) return;
2544
+ await this.post(`/machine-payments/${paymentId}/merchant-receipt`, body);
2545
+ } catch {
2546
+ }
2547
+ }
2548
+ async resolveCompletionContext(input) {
2549
+ const status = await this.getPaymentStatus(input.paymentId);
2550
+ if (status.rail !== "x402") {
2551
+ throw new HavenPaymentStateError(
2552
+ `Payment ${status.paymentId} is ${status.rail}, not x402.`,
2553
+ 409,
2554
+ status
2555
+ );
2556
+ }
2557
+ const readyForMerchantCompletion = input.noFundingLeg ? status.kind === "payment_intent" && status.status === "submitted" : status.nextAction === AgentPaymentNextAction.RetryOriginalX402Request || status.kind === "payment_intent" && status.status === "confirmed" && status.phase === AgentPaymentPhase.PaymentConfirmed && status.nextAction === AgentPaymentNextAction.None;
2558
+ if (!readyForMerchantCompletion) {
2559
+ throw new HavenPaymentStateError(status.message, paymentStateStatusCode(status.status, 409), status);
2560
+ }
2561
+ if (!input.noFundingLeg && !status.txHash) {
2562
+ throw new HavenApiError(
2563
+ `x402 payment ${status.paymentId} is ready for merchant completion but has no Haven transaction hash.`,
2564
+ 502,
2565
+ status,
2566
+ status.paymentId
2567
+ );
2568
+ }
2569
+ const approvedResourceUrl = status.resourceUrl ?? status.x402?.resourceUrl ?? null;
2570
+ if (approvedResourceUrl && approvedResourceUrl !== input.url) {
2571
+ throw new HavenApiError(
2572
+ "x402 merchant completion does not match the approved resource URL.",
2573
+ 409,
2574
+ { status, url: input.url },
2575
+ status.paymentId
2576
+ );
2577
+ }
2578
+ return {
2579
+ paymentId: status.paymentId,
2580
+ txHash: status.txHash,
2581
+ resourceUrl: approvedResourceUrl ?? input.url,
2582
+ merchantAddress: status.merchantAddress ?? status.x402?.merchantAddress ?? null
2583
+ };
2584
+ }
2585
+ async resolveWalletForMerchantCall() {
2586
+ const localWallet = x402PayerAddress(this.delegateAddress, this.x402Wallet);
2587
+ if (localWallet) return localWallet;
2588
+ try {
2589
+ const agent = await this.getAgent();
2590
+ return agent.delegateAddress ?? void 0;
2591
+ } catch {
2592
+ return void 0;
2593
+ }
2594
+ }
2595
+ // #1328: authorizeMachinePayment / authorizeMppDemoPayment / resumeAuthorizedMpp
2596
+ // / resumeMppPayment / fetchWithMachinePayment / retryMppRequest (the
2597
+ // MACHINE-PAYMENT-CHALLENGE / mpp_demo client surface) are retired — the
2598
+ // backend's POST /machine-payments/authorize refuses unconditionally now,
2599
+ // and MACHINE-PAYMENT-CHALLENGE was never produced by any other Haven
2600
+ // surface. Use the x402 flow (authorizeX402 / fetch / quoteX402 / payX402Quote)
2601
+ // for agent-to-merchant payments.
2602
+ async recordRetryRejected(input) {
2603
+ try {
2604
+ await this.post("/machine-payments/reconciliation-events", {
2605
+ paymentId: input.paymentId,
2606
+ rail: input.rail,
2607
+ eventType: "merchant_retry_rejected_after_payment",
2608
+ txHash: input.txHash,
2609
+ reason: `Merchant returned HTTP ${input.merchant.merchant_status} after Haven payment confirmation`,
2610
+ details: {
2611
+ resource_url: input.resourceUrl,
2612
+ retry_status: input.merchant.merchant_status,
2613
+ retry_body: input.merchant.merchant_body.slice(0, MERCHANT_BODY_SNIPPET_LIMIT) || null,
2614
+ ...input.details
2615
+ }
2616
+ });
2617
+ } catch {
2618
+ }
2619
+ }
2620
+ async reportEvidence(input) {
2621
+ try {
2622
+ await this.post("/machine-payments/evidence", {
2623
+ paymentId: input.paymentId,
2624
+ rail: input.rail,
2625
+ txHash: input.txHash,
2626
+ resourceUrl: input.resourceUrl,
2627
+ merchantStatus: input.merchantStatus,
2628
+ challengePayload: input.challengePayload,
2629
+ selectedPayment: input.selectedPayment,
2630
+ paymentProofHeaderName: input.paymentProofHeaderName,
2631
+ paymentProofHeader: input.paymentProofHeader,
2632
+ protocolReceiptHeaderName: input.protocolReceiptHeaderName,
2633
+ protocolReceiptHeader: input.protocolReceiptHeader,
2634
+ protocolReceiptPayload: input.protocolReceiptHeader ? parseProtocolReceiptHeader(input.protocolReceiptHeader) : void 0
2635
+ });
2636
+ } catch {
2637
+ }
2638
+ }
2639
+ };
2640
+ function parseMerchantSettlement(header) {
2641
+ if (!header) return {};
2642
+ const parsed = parseProtocolReceiptHeader(header);
2643
+ const tx = typeof parsed?.transaction === "string" ? parsed.transaction : typeof parsed?.txHash === "string" ? parsed.txHash : typeof parsed?.tx_hash === "string" ? parsed.tx_hash : null;
2644
+ return { settlementTxHash: tx };
2645
+ }
2646
+ function parseProtocolReceiptHeader(value) {
2647
+ try {
2648
+ return decodeBase64Json(value);
2649
+ } catch {
2650
+ try {
2651
+ return JSON.parse(value);
2652
+ } catch {
2653
+ return void 0;
1132
2654
  }
1133
2655
  }
1134
- flush();
1135
- return messages;
1136
- }
1137
- function selectJsonRpcResult(messages) {
1138
- return messages.find((m) => "result" in m || "error" in m) ?? messages[messages.length - 1];
1139
2656
  }
2657
+
2658
+ // src/client.ts
2659
+ var DEFAULT_POLLING_INTERVAL = 3e3;
1140
2660
  function x402TypedDataDigest(typedData) {
1141
2661
  if (!typedData || typeof typedData !== "object") return void 0;
1142
2662
  try {
@@ -1167,48 +2687,78 @@ function mapCatalogEntry(entry) {
1167
2687
  };
1168
2688
  }
1169
2689
  var HavenClient = class {
1170
- apiKey;
1171
2690
  delegateKey;
1172
- baseUrl;
2691
+ havenApi;
2692
+ accountReads;
2693
+ delegateSweep;
1173
2694
  x402Wallet;
1174
- requestTimeout;
1175
- merchantTimeout;
2695
+ merchantTransport;
1176
2696
  confirmationTimeout;
1177
2697
  pollingInterval;
1178
2698
  chainRpcs;
1179
2699
  inFlightX402 = /* @__PURE__ */ new Map();
1180
- x402ReceiptCache = /* @__PURE__ */ new Map();
1181
2700
  /**
1182
- * Setup-time headers configured via `HavenClientConfig.defaultHeaders`.
1183
- * Read-only after construction — use `withRequestContext` for per-call
1184
- * scoping so concurrent requests don't race on shared mutable state.
2701
+ * The EIP-3009 funding-leg lifecycle (#1618). The facade holds a reference
2702
+ * and delegates; it does not reimplement any of it.
1185
2703
  */
1186
- defaultHeaders;
2704
+ fundingLeg;
1187
2705
  /**
1188
- * Async-local store for per-request context (currently: extra headers).
1189
- * Each `withRequestContext` invocation produces an isolated store, so
1190
- * overlapping async work — like two MCP tool dispatches in flight at
1191
- * the same time — see their own headers without stepping on each other.
2706
+ * The erc7710 direct-settlement lifecycle (#1619). Separate from the funding
2707
+ * leg on purpose: this scheme has no funding leg to share.
1192
2708
  */
1193
- requestContext = new AsyncLocalStorage();
1194
- /** Monotonic JSON-RPC id source for the MCP `initialize` handshake. */
1195
- mcpRequestId = 0;
2709
+ erc7710;
2710
+ /**
2711
+ * Merchant delivery and the evidence trail behind it (#1620). Scheme-neutral
2712
+ * on purpose — both settlement schemes finish through the same door.
2713
+ */
2714
+ merchantCompletion;
1196
2715
  /** Delegate address derived from the private key (if provided) */
1197
2716
  delegateAddress;
1198
2717
  constructor(config) {
1199
- this.apiKey = config.apiKey;
1200
2718
  this.delegateKey = config.delegateKey;
1201
- this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
2719
+ this.havenApi = new HavenApiTransport(config);
2720
+ this.accountReads = new AccountReads({
2721
+ transport: this.havenApi,
2722
+ getPaymentStatus: (paymentId) => this.getPaymentStatus(paymentId)
2723
+ });
2724
+ this.delegateSweep = new DelegateSweepApi({
2725
+ transport: this.havenApi,
2726
+ delegateKey: config.delegateKey,
2727
+ chainRpcs: config.chainRpcs ?? {},
2728
+ getAgent: () => this.getAgent(),
2729
+ buildExplorerUrl: (chainId, hash) => buildExplorerUrl(chainId, hash)
2730
+ });
1202
2731
  this.x402Wallet = config.x402Wallet;
1203
- this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
1204
- this.merchantTimeout = config.merchantTimeout ?? DEFAULT_MERCHANT_TIMEOUT;
1205
- this.confirmationTimeout = config.confirmationTimeout ?? DEFAULT_CONFIRMATION_TIMEOUT;
2732
+ this.merchantTransport = new McpMerchantTransport({ merchantTimeout: config.merchantTimeout });
2733
+ this.confirmationTimeout = config.confirmationTimeout ?? DEFAULT_CONFIRMATION_TIMEOUT_MS;
1206
2734
  this.pollingInterval = config.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
1207
2735
  this.chainRpcs = config.chainRpcs ?? {};
1208
- this.defaultHeaders = { ...config.defaultHeaders ?? {} };
1209
2736
  if (this.delegateKey) {
1210
2737
  this.delegateAddress = addressFromKey(this.delegateKey);
1211
2738
  }
2739
+ this.fundingLeg = new X402FundingLeg({
2740
+ delegateKey: this.delegateKey,
2741
+ delegateAddress: this.delegateAddress,
2742
+ x402Wallet: this.x402Wallet,
2743
+ chainRpcs: this.chainRpcs,
2744
+ post: (path, body) => this.post(path, body),
2745
+ signForData: (signData) => this.signForData(signData),
2746
+ assertSignableAuthorizationState: (label, raw) => this.throwIfNonSignableAuthorizationState(label, raw)
2747
+ });
2748
+ this.merchantCompletion = new MerchantCompletion({
2749
+ post: (path, body) => this.post(path, body),
2750
+ merchantTransport: this.merchantTransport,
2751
+ getPaymentStatus: (paymentId) => this.getPaymentStatus(paymentId),
2752
+ getAgent: () => this.getAgent(),
2753
+ delegateAddress: this.delegateAddress,
2754
+ x402Wallet: this.x402Wallet
2755
+ });
2756
+ this.erc7710 = new X402Erc7710({
2757
+ delegateKey: this.delegateKey,
2758
+ post: (path, body) => this.post(path, body),
2759
+ signForData: (signData) => this.signForData(signData),
2760
+ getAgent: () => this.getAgent()
2761
+ });
1212
2762
  }
1213
2763
  /**
1214
2764
  * Run `fn` with extra Haven-API headers scoped to the async work it
@@ -1226,7 +2776,7 @@ var HavenClient = class {
1226
2776
  * context.
1227
2777
  */
1228
2778
  withRequestContext(headers, fn) {
1229
- return this.requestContext.run({ headers: { ...headers } }, fn);
2779
+ return this.havenApi.withRequestContext(headers, fn);
1230
2780
  }
1231
2781
  // ── High-Level API ───────────────────────────────────────────────
1232
2782
  /**
@@ -1262,7 +2812,7 @@ var HavenClient = class {
1262
2812
  ...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {}
1263
2813
  });
1264
2814
  if (raw.status === "pending_approval") {
1265
- this.throwPaymentStateError("Payment", raw);
2815
+ throwPaymentStateError("Payment", raw);
1266
2816
  }
1267
2817
  return {
1268
2818
  paymentId: raw.payment_id,
@@ -1325,7 +2875,7 @@ var HavenClient = class {
1325
2875
  ...new TextEncoder().encode(JSON.stringify(paymentRequired)).length <= 65536 ? { paymentRequired } : {}
1326
2876
  });
1327
2877
  if (raw.status !== "pending_signature") {
1328
- this.throwPaymentStateError("x402 payment", raw);
2878
+ throwPaymentStateError("x402 payment", raw);
1329
2879
  }
1330
2880
  if (!raw.sign_data?.hash) {
1331
2881
  throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
@@ -1351,6 +2901,8 @@ var HavenClient = class {
1351
2901
  asset: option.asset,
1352
2902
  network: option.network,
1353
2903
  expectedAuth: raw.x402_expected_auth,
2904
+ payerDelegate: raw.payer_delegate,
2905
+ payerAgentId: raw.payer_agent_id,
1354
2906
  // #1138: the digest the delegation-rail expected context commits to.
1355
2907
  // Re-derived locally, exactly like every other context field the edge
1356
2908
  // signer is handed (amount, merchantTo, …) — none of them are trusted
@@ -1446,7 +2998,7 @@ var HavenClient = class {
1446
2998
  */
1447
2999
  async getPayment(paymentId) {
1448
3000
  const raw = await this.get(`/payments/${paymentId}`);
1449
- return this.mapPaymentResult(raw);
3001
+ return mapPaymentResult(raw, buildExplorerUrl);
1450
3002
  }
1451
3003
  /**
1452
3004
  * Get agent-actionable status for a payment intent or approval request.
@@ -1456,37 +3008,13 @@ var HavenClient = class {
1456
3008
  */
1457
3009
  async getPaymentStatus(paymentId) {
1458
3010
  const raw = await this.get(`/machine-payments/${paymentId}/status`);
1459
- return this.mapPaymentStatusResult(raw);
3011
+ return mapPaymentStatusResult(raw);
1460
3012
  }
1461
3013
  /**
1462
3014
  * Get the agent identity tied to this API key.
1463
3015
  */
1464
3016
  async getAgent() {
1465
- if (this.agentInFlight) return this.agentInFlight;
1466
- const request = this.fetchAgent();
1467
- this.agentInFlight = request;
1468
- request.finally(() => {
1469
- this.agentInFlight = null;
1470
- }).catch(() => {
1471
- });
1472
- return request;
1473
- }
1474
- agentInFlight = null;
1475
- async fetchAgent() {
1476
- const raw = await this.get("/machine-payments/agent");
1477
- return {
1478
- id: raw.id,
1479
- name: raw.name,
1480
- status: raw.status,
1481
- safeAddress: raw.safe_address,
1482
- delegateAddress: raw.delegate_address,
1483
- chainId: raw.chain_id,
1484
- // Defensive normalization, not trust: the backend contract is exactly
1485
- // 'legacy' | 'delegation' (#1306), but an older/mismatched backend
1486
- // during a rollout window should degrade to the wider legacy bucket
1487
- // rather than propagate an unrecognized string.
1488
- executionRail: raw.execution_rail === "delegation" ? "delegation" : "legacy"
1489
- };
3017
+ return this.accountReads.getAgent();
1490
3018
  }
1491
3019
  /**
1492
3020
  * One-shot "am I ready?" bootstrap: identity + live spend authority + a
@@ -1496,24 +3024,7 @@ var HavenClient = class {
1496
3024
  * without two round trips and manual assembly.
1497
3025
  */
1498
3026
  async getAgentSummary() {
1499
- const [agent, allowanceSummary] = await Promise.all([
1500
- this.getAgent(),
1501
- this.getAllowances()
1502
- ]);
1503
- const allowances = allowanceSummary.allowances.map((a) => {
1504
- const token = resolveTokenFromAddress(a.tokenAddress);
1505
- const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(a.onchain.remaining), token.decimals)} ${a.tokenSymbol}` : `${a.onchain.remaining} ${a.tokenSymbol} (atomic; unknown decimals)`;
1506
- return {
1507
- tokenSymbol: a.tokenSymbol,
1508
- remainingAtomic: a.onchain.remaining,
1509
- remainingDisplay,
1510
- configuredAmount: a.configuredAmount,
1511
- resetPeriodMin: a.resetPeriodMin,
1512
- isResetPending: a.onchain.isResetPending
1513
- };
1514
- });
1515
- const readiness = deriveReadiness(agent.status, allowances);
1516
- return { ...agent, readiness, spend_authority_readiness: readiness, allowances };
3027
+ return this.accountReads.getAgentSummary();
1517
3028
  }
1518
3029
  /**
1519
3030
  * Sweep stranded USDC and ETH from the delegate EOA back to the originating Safe.
@@ -1525,68 +3036,7 @@ var HavenClient = class {
1525
3036
  * Requires `chainRpcs` to be set for the agent's chain in `HavenClientConfig`.
1526
3037
  */
1527
3038
  async sweepDelegate() {
1528
- if (!this.delegateKey) {
1529
- throw new HavenSigningError("delegateKey is required for sweepDelegate.");
1530
- }
1531
- const agent = await this.getAgent();
1532
- const { safeAddress, delegateAddress, chainId } = agent;
1533
- if (!delegateAddress) {
1534
- throw new HavenApiError("Agent has no delegate address.", 422);
1535
- }
1536
- const rpcUrl = this.chainRpcs[chainId];
1537
- if (!rpcUrl) {
1538
- throw new HavenApiError(
1539
- `chainRpcs[${chainId}] must be configured to sweep the delegate wallet.`,
1540
- 422
1541
- );
1542
- }
1543
- const provider = createJsonRpcProvider(rpcUrl);
1544
- const wallet = createWallet(this.delegateKey, provider);
1545
- const ERC20_TRANSFER_ABI = ["function balanceOf(address) view returns (uint256)", "function transfer(address to, uint256 amount) returns (bool)"];
1546
- const transfers = [];
1547
- const usdcAddress = CHAIN_USDC[chainId];
1548
- if (usdcAddress) {
1549
- const usdcContract = createErc20Contract(usdcAddress, ERC20_TRANSFER_ABI, wallet);
1550
- const usdcBalance = await usdcContract.balanceOf(delegateAddress);
1551
- if (usdcBalance > 0n) {
1552
- const tx = await usdcContract.transfer(safeAddress, usdcBalance);
1553
- const receipt = await tx.wait(1);
1554
- const txHash = receipt?.hash ?? tx.hash;
1555
- transfers.push({
1556
- asset: "USDC",
1557
- amount: formatAtomicAmount(usdcBalance, 6),
1558
- amountAtomic: usdcBalance.toString(),
1559
- txHash,
1560
- explorerUrl: buildExplorerUrl(chainId, txHash)
1561
- });
1562
- }
1563
- }
1564
- const ethBalance = await provider.getBalance(delegateAddress);
1565
- if (ethBalance > 0n) {
1566
- const fee = await provider.getFeeData();
1567
- const effectiveGasPrice = fee.maxFeePerGas ?? fee.gasPrice ?? 1000000n;
1568
- const gasLimit = 21000n;
1569
- const gasCost = effectiveGasPrice * gasLimit * 2n;
1570
- const ethToSend = ethBalance > gasCost ? ethBalance - gasCost : 0n;
1571
- if (ethToSend > 0n) {
1572
- const tx = await wallet.sendTransaction({ to: safeAddress, value: ethToSend });
1573
- const receipt = await tx.wait(1);
1574
- const txHash = receipt?.hash ?? tx.hash;
1575
- transfers.push({
1576
- asset: "ETH",
1577
- amount: formatAtomicAmount(ethToSend, 18),
1578
- amountAtomic: ethToSend.toString(),
1579
- txHash,
1580
- explorerUrl: buildExplorerUrl(chainId, txHash)
1581
- });
1582
- }
1583
- }
1584
- return {
1585
- fromAddress: delegateAddress,
1586
- toAddress: safeAddress,
1587
- chainId,
1588
- transfers
1589
- };
3039
+ return this.delegateSweep.sweepDelegate();
1590
3040
  }
1591
3041
  /**
1592
3042
  * Hosted (keyless) split-signer sweep — step 1 of 2.
@@ -1597,7 +3047,7 @@ var HavenClient = class {
1597
3047
  * edge signer's `haven_sign_sweep_delegate`. No key is required on this client.
1598
3048
  */
1599
3049
  async prepareSweep() {
1600
- return this.post("/machine-payments/sweep/prepare", {});
3050
+ return this.delegateSweep.prepareSweep();
1601
3051
  }
1602
3052
  /**
1603
3053
  * Hosted (keyless) split-signer sweep — step 2 of 2.
@@ -1607,40 +3057,13 @@ var HavenClient = class {
1607
3057
  * the key.
1608
3058
  */
1609
3059
  async submitSweep(authorization, signature) {
1610
- return this.post("/machine-payments/sweep/submit", {
1611
- authorization,
1612
- signature
1613
- });
3060
+ return this.delegateSweep.submitSweep(authorization, signature);
1614
3061
  }
1615
3062
  /**
1616
3063
  * Get configured and on-chain allowances for the authenticated agent.
1617
3064
  */
1618
3065
  async getAllowances() {
1619
- const raw = await this.get("/machine-payments/allowances");
1620
- return {
1621
- agentId: raw.agent_id,
1622
- safeAddress: raw.safe_address,
1623
- delegateAddress: raw.delegate_address,
1624
- chainId: raw.chain_id,
1625
- allowances: raw.allowances.map((allowance) => ({
1626
- id: allowance.id,
1627
- tokenAddress: allowance.token_address,
1628
- tokenSymbol: allowance.token_symbol,
1629
- configuredAmount: allowance.configured_amount,
1630
- resetPeriodMin: allowance.reset_period_min,
1631
- onchain: {
1632
- amount: allowance.onchain.amount,
1633
- spent: allowance.onchain.spent,
1634
- remaining: allowance.onchain.remaining,
1635
- effectiveSpent: allowance.onchain.effective_spent,
1636
- resetTimeMin: allowance.onchain.reset_time_min,
1637
- lastResetMin: allowance.onchain.last_reset_min,
1638
- nonce: allowance.onchain.nonce,
1639
- isResetPending: allowance.onchain.is_reset_pending,
1640
- remainingIsFromChain: allowance.onchain.remaining_is_from_chain
1641
- }
1642
- }))
1643
- };
3066
+ return this.accountReads.getAllowances();
1644
3067
  }
1645
3068
  /**
1646
3069
  * Post-purchase allowance/budget summary for a settled payment (#1310).
@@ -1671,62 +3094,7 @@ var HavenClient = class {
1671
3094
  * phrase it as guaranteed-fresh.
1672
3095
  */
1673
3096
  async getPostPurchaseAllowanceSummary(paymentId) {
1674
- const unavailable = (detail) => ({
1675
- payment: null,
1676
- allowance: null,
1677
- warnings: [
1678
- {
1679
- code: AgentPaymentWarningCode.AllowanceCheckUnavailable,
1680
- message: `Could not read the post-purchase allowance/budget for payment ${paymentId} (${detail}). The payment itself succeeded \u2014 the on-chain policy remains the actual spend gate; this only affects the remaining-budget figure reported here.`
1681
- }
1682
- ]
1683
- });
1684
- const [statusResult, agentResult, allowanceResult] = await Promise.allSettled([
1685
- this.getPaymentStatus(paymentId),
1686
- this.getAgent(),
1687
- this.getAllowances()
1688
- ]);
1689
- if (statusResult.status === "rejected") {
1690
- return unavailable(statusResult.reason instanceof Error ? statusResult.reason.message : String(statusResult.reason));
1691
- }
1692
- const status = statusResult.value;
1693
- if (agentResult.status === "rejected") {
1694
- return { ...unavailable(agentResult.reason instanceof Error ? agentResult.reason.message : String(agentResult.reason)), payment: status };
1695
- }
1696
- if (allowanceResult.status === "rejected") {
1697
- return { ...unavailable(allowanceResult.reason instanceof Error ? allowanceResult.reason.message : String(allowanceResult.reason)), payment: status };
1698
- }
1699
- try {
1700
- const tokenAddress = status.asset ?? status.x402?.asset ?? null;
1701
- if (!tokenAddress) {
1702
- return { ...unavailable("the settled payment does not carry a resolvable token address"), payment: status };
1703
- }
1704
- const rail = agentResult.value.executionRail;
1705
- const source = rail === "delegation" ? "active_delegations" : "allowance_module";
1706
- const match = allowanceResult.value.allowances.find(
1707
- (a) => a.tokenAddress.toLowerCase() === tokenAddress.toLowerCase()
1708
- );
1709
- if (!match) {
1710
- return { ...unavailable("no allowance/budget row matches the settled token"), payment: status };
1711
- }
1712
- const token = resolveTokenFromAddress(match.tokenAddress);
1713
- const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(match.onchain.remaining), token.decimals)} ${match.tokenSymbol}` : void 0;
1714
- return {
1715
- payment: status,
1716
- allowance: {
1717
- rail,
1718
- remaining_atomic: match.onchain.remaining,
1719
- ...remainingDisplay ? { remaining_display: remainingDisplay } : {},
1720
- token_symbol: match.tokenSymbol,
1721
- token_address: match.tokenAddress,
1722
- reset_period: match.resetPeriodMin,
1723
- source
1724
- },
1725
- warnings: []
1726
- };
1727
- } catch (err) {
1728
- return unavailable(err instanceof Error ? err.message : String(err));
1729
- }
3097
+ return this.accountReads.getPostPurchaseAllowanceSummary(paymentId);
1730
3098
  }
1731
3099
  /**
1732
3100
  * `haven_get_payment_status` convenience: fetch status and, for a
@@ -1783,9 +3151,7 @@ var HavenClient = class {
1783
3151
  * List recent machine-payment receipts/evidence for bookkeeping.
1784
3152
  */
1785
3153
  async listReceipts(options = {}) {
1786
- const query = options.limit ? `?limit=${encodeURIComponent(String(options.limit))}` : "";
1787
- const raw = await this.get(`/machine-payments/receipts${query}`);
1788
- return raw.receipts.map((receipt) => this.mapPaymentReceipt(receipt));
3154
+ return this.accountReads.listReceipts(options);
1789
3155
  }
1790
3156
  /**
1791
3157
  * Fetch the verifiable receipt bundle for a settled payment and verify it
@@ -1794,10 +3160,7 @@ var HavenClient = class {
1794
3160
  * authorisation, so the result is trustworthy even if the backend lied.
1795
3161
  */
1796
3162
  async getReceipt(paymentId) {
1797
- const { receipt } = await this.get(
1798
- `/payments/${paymentId}/receipt`
1799
- );
1800
- return { receipt, verification: verifyPaymentReceipt(receipt) };
3163
+ return this.accountReads.getReceipt(paymentId);
1801
3164
  }
1802
3165
  /**
1803
3166
  * Rehydrate the x402 resume-state bundle for a payment id (#1328: the MPP
@@ -1850,17 +3213,16 @@ var HavenClient = class {
1850
3213
  );
1851
3214
  }
1852
3215
  const idempotencyKey = options.idempotencyKey ?? buildX402IdempotencyKey(paymentRequired, option);
1853
- const cached = this.x402ReceiptCache.get(idempotencyKey);
1854
- if (cached && cached.expiresAt > Date.now()) return cached.receipt;
3216
+ const cached = this.fundingLeg.cachedReceipt(idempotencyKey);
3217
+ if (cached) return cached;
1855
3218
  const inFlight = this.inFlightX402.get(idempotencyKey);
1856
3219
  if (inFlight) return inFlight;
1857
- const promise = this.authorizeStandardX402(paymentRequired, option, idempotencyKey);
3220
+ const promise = this.fundingLeg.authorize(paymentRequired, option, idempotencyKey);
1858
3221
  this.inFlightX402.set(idempotencyKey, promise);
1859
3222
  try {
1860
3223
  return await promise;
1861
3224
  } catch (err) {
1862
- this.attachResumeState(err, {
1863
- rail: "x402",
3225
+ attachResumeState(err, {
1864
3226
  paymentRequired,
1865
3227
  accepted: option,
1866
3228
  idempotencyKey
@@ -1875,9 +3237,9 @@ var HavenClient = class {
1875
3237
  * payment or approval request.
1876
3238
  */
1877
3239
  async quoteX402(url, init, options = {}) {
1878
- const initialInit = this.withX402Wallet(init, this.x402PayerAddress());
1879
- const request = this.snapshotX402Request(url, initialInit);
1880
- const response = await this.merchantFetch(url, initialInit);
3240
+ const initialInit = withX402Wallet(init, x402PayerAddress(this.delegateAddress, this.x402Wallet));
3241
+ const request = snapshotX402Request(url, initialInit);
3242
+ const response = await this.merchantTransport.fetch(url, initialInit);
1881
3243
  if (response.status !== 402) {
1882
3244
  throw new X402UnexpectedStatusError(
1883
3245
  `Expected an x402 quote response with HTTP 402, got HTTP ${response.status}.`,
@@ -1888,8 +3250,8 @@ var HavenClient = class {
1888
3250
  throw new HavenApiError("quoteX402 only supports standard x402 Payment Required responses.", 400);
1889
3251
  }
1890
3252
  const paymentRequired = await parsePaymentRequiredResponse(response);
1891
- const mcpTransport = await this.detectX402McpTransport(url, paymentRequired, response);
1892
- return this.buildX402Quote(paymentRequired, request, options.idempotencyKey, mcpTransport);
3253
+ const mcpTransport = await this.merchantTransport.detect(url, paymentRequired, response);
3254
+ return buildX402Quote(paymentRequired, request, options.idempotencyKey, mcpTransport);
1893
3255
  }
1894
3256
  /**
1895
3257
  * Probe an MCP tool for its x402 quote without creating a payment.
@@ -1902,8 +3264,8 @@ var HavenClient = class {
1902
3264
  * callers that need a plain x402 endpoint must use {@link quoteX402}.
1903
3265
  */
1904
3266
  async quoteMcpX402(url, init, options = {}) {
1905
- const wallet = await this.resolveX402WalletForMerchantCall();
1906
- const sessionId = await this.mcpInitialize(url, init, wallet);
3267
+ const wallet = await this.merchantCompletion.resolveWalletForMerchantCall();
3268
+ const sessionId = await this.merchantTransport.initialize(url, init, wallet);
1907
3269
  if (!sessionId) {
1908
3270
  throw new HavenApiError(
1909
3271
  "The merchant did not establish an MCP session before the x402 quote. No payment was created.",
@@ -1911,238 +3273,71 @@ var HavenClient = class {
1911
3273
  { mcpSessionNotEstablished: true }
1912
3274
  );
1913
3275
  }
1914
- let requestInit = this.withX402Wallet(init, wallet);
1915
- requestInit = this.withMcpHeaders(requestInit, sessionId);
3276
+ let requestInit = withX402Wallet(init, wallet);
3277
+ requestInit = this.merchantTransport.withSessionHeaders(requestInit, sessionId);
1916
3278
  const quote = await this.quoteX402(url, requestInit, options);
1917
3279
  return {
1918
3280
  ...quote,
1919
3281
  mcpTransport: quote.mcpTransport ?? { handshakeRequired: true, source: "path" }
1920
3282
  };
1921
3283
  }
1922
- /**
1923
- * Pay a previously inspected x402 quote and retry the exact captured request.
1924
- */
1925
- async payX402Quote(quote, options = {}) {
1926
- const idempotencyKey = options.idempotencyKey ?? quote.idempotencyKey;
1927
- try {
1928
- const receipt = await this.authorizeX402(quote.paymentRequired, { idempotencyKey });
1929
- return this.retryX402Request(
1930
- quote.request.url,
1931
- this.requestInitFromSnapshot(quote.request),
1932
- quote.paymentRequired,
1933
- receipt
1934
- );
1935
- } catch (err) {
1936
- this.attachResumeState(err, {
1937
- rail: "x402",
1938
- paymentRequired: quote.paymentRequired,
1939
- accepted: quote.accepted,
1940
- idempotencyKey,
1941
- request: quote.request
1942
- });
1943
- throw err;
1944
- }
1945
- }
1946
- async authorizeStandardX402(paymentRequired, option, idempotencyKey) {
1947
- const raw = await this.post("/x402", {
1948
- url: paymentRequired.resource.url,
1949
- payTo: this.delegateAddress,
1950
- merchantPayTo: option.payTo,
1951
- amount: x402AuthorizationAmount(option),
1952
- asset: option.asset,
1953
- network: option.network,
1954
- description: paymentRequired.resource.description,
1955
- idempotencyKey,
1956
- // #1360: same explicit funding-leg declaration as createX402Intent —
1957
- // this local-key path derives payTo from the key (never stale), but the
1958
- // declaration keeps both writers of the 3009 shape loud-by-default.
1959
- settlementScheme: "eip3009"
1960
- });
1961
- const state = this.paymentStateFromRaw("x402 payment", raw);
1962
- const executedReplay = raw.success && raw.tx_hash ? "idempotency-collision" : state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request ? "approval-resume" : null;
1963
- if (executedReplay) {
1964
- const canFund = await this.delegateCanFund(
1965
- raw.chain_id ?? state?.chainId ?? chainIdFromNetwork(option.network),
1966
- option.asset,
1967
- x402AuthorizationAmount(option)
1968
- );
1969
- const refuse = executedReplay === "idempotency-collision" ? canFund !== true : canFund === false;
1970
- if (refuse) {
1971
- const settledReceipt = state && executedReplay === "approval-resume" ? this.mapX402ReceiptFromStatus(paymentRequired, option, void 0, state) : this.mapX402ReceiptFromAuthorization(paymentRequired, option, void 0, raw);
1972
- throw new X402AlreadySettledError(
1973
- canFund === false ? "This x402 payment already settled \u2014 the delegate no longer holds the funds to authorize it again. To buy the same item a second time, pass a distinct `idempotencyKey`; the synthesised key intentionally collapses repeat calls for the same product within a 5-minute window so a retried request cannot pay twice." : "This x402 payment already settled, and whether the delegate can still fund a new authorization could not be verified (no `chainRpcs` entry for this chain). Refusing rather than issue an authorization that may be unfundable. To buy the same item a second time, pass a distinct `idempotencyKey`; to finish an interrupted payment, resume it by `paymentId`.",
1974
- settledReceipt,
1975
- canFund === false ? "settled" : "unverifiable"
1976
- );
1977
- }
1978
- }
1979
- const paymentHeader = await this.createStandardX402Header(paymentRequired, option);
1980
- if (raw.success && raw.tx_hash) {
1981
- const receipt2 = this.mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw);
1982
- this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
1983
- return receipt2;
1984
- }
1985
- if (state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request) {
1986
- const receipt2 = this.mapX402ReceiptFromStatus(paymentRequired, option, paymentHeader, state);
1987
- this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
1988
- return receipt2;
1989
- }
1990
- this.throwIfNonSignableAuthorizationState("x402 payment", raw);
1991
- if (!raw.sign_data?.hash) {
1992
- throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
1993
- }
1994
- const sig = await this.signForData(raw.sign_data);
1995
- const execResult = await this.post(
1996
- `/payments/${raw.payment_id}/sign`,
1997
- { signature: sig }
1998
- );
1999
- if (execResult.status !== "confirmed") {
2000
- this.throwPaymentStateError("x402 payment", execResult);
2001
- }
2002
- await this.waitForFundingTx(
2003
- execResult.tx_hash,
2004
- execResult.chain_id ?? chainIdFromNetwork(option.network)
2005
- );
2006
- const receipt = this.mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult);
2007
- this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt);
2008
- return receipt;
2009
- }
2010
- /**
2011
- * Pay a merchant through **erc7710 direct settlement** (#1454, epic #1450).
2012
- *
2013
- * The whole point of this path is what it does NOT do. There is no funding
2014
- * leg: the merchant redeems a delegation chain and pulls from the treasury
2015
- * directly, so the delegate EOA never holds the money, no sweep can strand
2016
- * it, and the #713 reconciliation class does not apply. It is also why this
2017
- * method is SMALLER than the 3009 path — the backend assembles the merchant
2018
- * `X-PAYMENT` header in `assembleSettlementPayload`, so the SDK builds no
2019
- * header locally.
2020
- *
2021
- * authorize (payTo = the MERCHANT) → sign the child → settle → header
2022
- *
2023
- * The caller then retries the merchant with that header. **Nothing has
2024
- * settled when this returns** — that is why it does not return an
2025
- * `X402Receipt`.
2026
- *
2027
- * Requires a delegation-rail account. The backend enforces that
2028
- * (`validateGenericSchemeRail`), and so does this method, before building a
2029
- * request the backend would only reject: an error a client can explain is
2030
- * worth more than a 400 it has to decode.
2031
- *
2032
- * **MCP callers must pass `options.resourceUrl`.** An in-band MCP 402
2033
- * challenge frequently carries no `resource` object at all, so
2034
- * `paymentRequired.resource?.url` is undefined and the backend answers
2035
- * "Valid url is required". The QA scenario this path was ported from falls
2036
- * back to the request URL for exactly that reason — the SDK cannot, because
2037
- * it never saw the request. Pass it.
2038
- */
2039
- async settleX402Erc7710(paymentRequired, options = {}) {
2040
- if (!this.delegateKey) {
2041
- throw new HavenSigningError(
2042
- "delegateKey is required for x402 payments. Pass it in the HavenClient config."
2043
- );
2044
- }
2045
- const prepared = await this.prepareX402Erc7710(paymentRequired, options);
2046
- const signature = await this.signForData(prepared.signData);
2047
- const paymentHeader = await this.submitX402Erc7710(prepared.paymentId, signature);
2048
- return { ...prepared.settlement, paymentHeader };
2049
- }
2050
- /**
2051
- * The AUTHORIZE half of erc7710 settlement (#1456): select the scheme, build
2052
- * the request, and return the child to be signed — without signing it.
2053
- *
2054
- * Split out because the hosted topology cannot use `settleX402Erc7710()`:
2055
- * that method signs in-process with `delegateKey`, and hosted Haven does not
2056
- * have one and must not. The hosted MCP server drives these two halves with
2057
- * the LOCAL signer in between, so the key stays where it belongs and the
2058
- * request shaping stays in one place rather than being reimplemented.
2059
- */
2060
- async prepareX402Erc7710(paymentRequired, options = {}) {
2061
- const delegationRail = options.delegationRail ?? (await this.getAgent()).executionRail === "delegation";
2062
- if (!delegationRail) {
2063
- throw new HavenApiError(
2064
- "erc7710 settlement requires a delegation-rail account; this one is not on it. Use authorizeX402() for the standard EIP-3009 path.",
2065
- 400
2066
- );
2067
- }
2068
- const selection = selectX402SettlementScheme(paymentRequired.accepts, { delegationRail });
2069
- if (!selection || selection.scheme !== "erc7710") {
2070
- throw new HavenApiError(
2071
- "This merchant does not advertise an erc7710 settlement option (no accepts[] entry carries extra.assetTransferMethod: 'erc7710'). Use authorizeX402() for the standard EIP-3009 path.",
2072
- 400
2073
- );
2074
- }
2075
- const option = selection.option;
2076
- const merchantPayTo = option.payTo;
2077
- const amountAtomic = x402AuthorizationAmount(option);
2078
- const raw = await this.post("/x402", {
2079
- url: options.resourceUrl ?? paymentRequired.resource?.url,
2080
- // payTo = the MERCHANT is what selects direct settlement server-side.
2081
- // The explicit settlementScheme must AGREE with that shape (#1360) —
2082
- // disagreement is a 400 by design, so that a stale delegate address
2083
- // becomes a loud mismatch instead of a silent reroute to the 3009 leg.
2084
- payTo: merchantPayTo,
2085
- settlementScheme: "erc7710",
2086
- amount: amountAtomic,
2087
- asset: option.asset,
2088
- network: option.network,
2089
- // The v2 header echoes the accepted entry field-for-field, so the quoted
2090
- // timeout must round-trip or the merchant rejects the echo (#1064).
2091
- maxTimeoutSeconds: option.maxTimeoutSeconds,
2092
- // #1058: forward the advertised facilitators verbatim — the child becomes
2093
- // redeemable ONLY by them. `null` here means the merchant advertised none
2094
- // (or an empty array, which the backend 400s on), so the field is OMITTED
2095
- // rather than sent empty. See x402FacilitatorAddresses.
2096
- ...selection.facilitatorAddresses ? { facilitatorAddresses: selection.facilitatorAddresses } : {},
2097
- // #1307/#1547: persisted so the settle leg can rehydrate the merchant
2098
- // call by payment_id on this scheme too, not only on the 3009 bridge.
2099
- ...options.mcpCallContext ? { mcpCallContext: options.mcpCallContext } : {}
2100
- });
2101
- if (!raw.payment_id) {
2102
- throw new HavenApiError("No payment_id returned from x402/authorize", 500, raw);
2103
- }
2104
- const signData = raw.sign_data;
2105
- if (signData?.signature_scheme !== "eip712_delegation" || !signData.typed_data) {
2106
- throw new HavenApiError(
2107
- `x402/authorize did not return an erc7710 settlement child (signature_scheme was ${JSON.stringify(signData?.signature_scheme)}). Refusing to sign a payload this path did not ask for.`,
2108
- 500,
2109
- raw
3284
+ /**
3285
+ * Pay a previously inspected x402 quote and retry the exact captured request.
3286
+ */
3287
+ async payX402Quote(quote, options = {}) {
3288
+ const idempotencyKey = options.idempotencyKey ?? quote.idempotencyKey;
3289
+ try {
3290
+ const receipt = await this.authorizeX402(quote.paymentRequired, { idempotencyKey });
3291
+ return this.merchantCompletion.retryRequest(
3292
+ quote.request.url,
3293
+ requestInitFromSnapshot(quote.request),
3294
+ quote.paymentRequired,
3295
+ receipt
2110
3296
  );
3297
+ } catch (err) {
3298
+ attachResumeState(err, {
3299
+ paymentRequired: quote.paymentRequired,
3300
+ accepted: quote.accepted,
3301
+ idempotencyKey,
3302
+ request: quote.request
3303
+ });
3304
+ throw err;
2111
3305
  }
2112
- return {
2113
- paymentId: raw.payment_id,
2114
- signData,
2115
- settlement: {
2116
- paymentId: raw.payment_id,
2117
- merchantPayTo,
2118
- amountAtomic,
2119
- asset: option.asset,
2120
- network: option.network,
2121
- facilitatorAddresses: selection.facilitatorAddresses
2122
- }
2123
- };
3306
+ }
3307
+ /**
3308
+ * Pay a merchant through **erc7710 direct settlement** (#1454, epic #1450).
3309
+ *
3310
+ * **Nothing has settled when this returns** — that is why it does not return
3311
+ * an `X402Receipt`; the caller still has to retry the merchant with the
3312
+ * header. **MCP callers must pass `options.resourceUrl`**, because an in-band
3313
+ * MCP 402 challenge frequently carries no `resource` object at all.
3314
+ *
3315
+ * Both caveats, and why this scheme has no funding leg, are explained where
3316
+ * the lifecycle lives: `x402-erc7710.ts` (#1619).
3317
+ */
3318
+ async settleX402Erc7710(paymentRequired, options = {}) {
3319
+ return this.erc7710.settle(paymentRequired, options);
3320
+ }
3321
+ /**
3322
+ * The AUTHORIZE half of erc7710 settlement (#1456): select the scheme, build
3323
+ * the request, and return the child to be signed — without signing it.
3324
+ *
3325
+ * Split out because the hosted topology cannot use `settleX402Erc7710()`:
3326
+ * that method signs in-process with `delegateKey`, and hosted Haven does not
3327
+ * have one and must not.
3328
+ */
3329
+ async prepareX402Erc7710(paymentRequired, options = {}) {
3330
+ return this.erc7710.prepare(paymentRequired, options);
2124
3331
  }
2125
3332
  /**
2126
3333
  * The SETTLE half (#1456): exchange the signed child for the merchant header.
2127
3334
  *
2128
3335
  * The SDK builds no header on this path — the backend assembles the MetaMask
2129
- * erc7710 payload in `assembleSettlementPayload`. Whoever produced the
2130
- * signature (an in-process delegate key, or the local edge signer over the
2131
- * hosted boundary) is irrelevant here.
3336
+ * erc7710 payload. Whoever produced the signature (an in-process delegate
3337
+ * key, or the local edge signer over the hosted boundary) is irrelevant.
2132
3338
  */
2133
3339
  async submitX402Erc7710(paymentId, signature) {
2134
- const settled = await this.post(
2135
- `/x402/${paymentId}/settle`,
2136
- { signature }
2137
- );
2138
- if (!settled.payment_header) {
2139
- throw new HavenApiError(
2140
- "x402 settle returned no payment_header \u2014 the merchant cannot be retried.",
2141
- 500,
2142
- settled
2143
- );
2144
- }
2145
- return settled.payment_header;
3340
+ return this.erc7710.submit(paymentId, signature);
2146
3341
  }
2147
3342
  async resumeAuthorizedX402(input) {
2148
3343
  if (!this.delegateKey) {
@@ -2161,11 +3356,11 @@ var HavenClient = class {
2161
3356
  );
2162
3357
  }
2163
3358
  const idempotencyKey = input.idempotencyKey ?? buildX402IdempotencyKey(input.paymentRequired, option);
2164
- const cached = this.x402ReceiptCache.get(idempotencyKey);
2165
- if (cached && cached.expiresAt > Date.now()) return cached.receipt;
3359
+ const cached = this.fundingLeg.cachedReceipt(idempotencyKey);
3360
+ if (cached) return cached;
2166
3361
  const status = await this.getPaymentStatus(input.paymentId);
2167
- this.assertCanResumeX402(status, input.paymentRequired, option);
2168
- const canFund = await this.delegateCanFund(
3362
+ assertCanResumeX402(status, input.paymentRequired, option);
3363
+ const canFund = await this.fundingLeg.delegateCanFund(
2169
3364
  status.chainId ?? chainIdFromNetwork(option.network),
2170
3365
  option.asset,
2171
3366
  x402AuthorizationAmount(option)
@@ -2173,20 +3368,20 @@ var HavenClient = class {
2173
3368
  if (canFund === false) {
2174
3369
  throw new X402AlreadySettledError(
2175
3370
  `x402 payment ${status.paymentId} has already settled \u2014 the delegate no longer holds the funds to authorize it again, so there is nothing left to resume.`,
2176
- this.mapX402ReceiptFromStatus(input.paymentRequired, option, void 0, status),
3371
+ this.fundingLeg.receiptFromStatus(input.paymentRequired, option, void 0, status),
2177
3372
  "settled"
2178
3373
  );
2179
3374
  }
2180
- const paymentHeader = await this.createStandardX402Header(input.paymentRequired, option);
2181
- const receipt = this.mapX402ReceiptFromStatus(input.paymentRequired, option, paymentHeader, status);
2182
- this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt);
3375
+ const paymentHeader = await this.fundingLeg.createPaymentHeader(input.paymentRequired, option);
3376
+ const receipt = this.fundingLeg.receiptFromStatus(input.paymentRequired, option, paymentHeader, status);
3377
+ this.fundingLeg.cacheReceipt(idempotencyKey, paymentHeader, receipt);
2183
3378
  return receipt;
2184
3379
  }
2185
3380
  async resumeX402Payment(input) {
2186
3381
  const inputInit = "init" in input ? input.init : void 0;
2187
- const initialInit = this.withX402Wallet(
2188
- inputInit ?? (input.request ? this.requestInitFromSnapshot(input.request) : void 0),
2189
- this.x402PayerAddress()
3382
+ const initialInit = withX402Wallet(
3383
+ inputInit ?? (input.request ? requestInitFromSnapshot(input.request) : void 0),
3384
+ x402PayerAddress(this.delegateAddress, this.x402Wallet)
2190
3385
  );
2191
3386
  let paymentRequired = input.paymentRequired;
2192
3387
  const url = input.url ?? input.request?.url;
@@ -2194,7 +3389,7 @@ var HavenClient = class {
2194
3389
  if (!url) {
2195
3390
  throw new HavenApiError("x402 resume requires the original URL or a captured request snapshot.", 400);
2196
3391
  }
2197
- const response = await this.merchantFetch(url, initialInit);
3392
+ const response = await this.merchantTransport.fetch(url, initialInit);
2198
3393
  if (response.status !== 402) {
2199
3394
  throw new HavenApiError("Expected the original x402 request to return HTTP 402 before resuming.", 400);
2200
3395
  }
@@ -2205,7 +3400,7 @@ var HavenClient = class {
2205
3400
  paymentRequired,
2206
3401
  idempotencyKey: input.idempotencyKey
2207
3402
  });
2208
- return this.retryX402Request(url ?? paymentRequired.resource.url, initialInit, paymentRequired, receipt);
3403
+ return this.merchantCompletion.retryRequest(url ?? paymentRequired.resource.url, initialInit, paymentRequired, receipt);
2209
3404
  }
2210
3405
  /**
2211
3406
  * Fetch wrapper that automatically handles HTTP 402 responses.
@@ -2219,268 +3414,57 @@ var HavenClient = class {
2219
3414
  * ```
2220
3415
  *
2221
3416
  * **MCP-over-x402 auto-handshake (issue #315):** when the endpoint is
2222
- * MCP-shaped — the URL path ends in `/mcp`, or the 402 body carries a
2223
- * Coinbase Bazaar `extensions.bazaar` block — the SDK runs the MCP
2224
- * `initialize` handshake, threads the resulting `mcp-session-id`,
2225
- * `Accept: application/json, text/event-stream`, and `x402-wallet` headers
2226
- * through every request, and collapses SSE responses to the JSON-RPC
2227
- * `result`. The caller just passes `(url, { body })` and never sees the
2228
- * protocol plumbing. A non-MCP server (handshake error / no session id)
2229
- * falls back to standard x402 behaviour.
2230
- *
2231
- * Requires `delegateKey` to be set in the client config.
2232
- */
2233
- async fetch(url, init, options = {}) {
2234
- let mcpSessionId;
2235
- if (isMcpUrl(url)) {
2236
- mcpSessionId = await this.mcpInitialize(url, init);
2237
- }
2238
- let requestInit = this.withX402Wallet(init, this.x402PayerAddress());
2239
- if (mcpSessionId) requestInit = this.withMcpHeaders(requestInit, mcpSessionId);
2240
- const response = await this.merchantFetch(url, requestInit);
2241
- if (response.status !== 402) {
2242
- return mcpSessionId ? this.surfaceMcpResult(response) : response;
2243
- }
2244
- let paymentRequired;
2245
- try {
2246
- paymentRequired = await parsePaymentRequiredResponse(response);
2247
- } catch {
2248
- return response;
2249
- }
2250
- if (!mcpSessionId && await responseHasBazaarExtension(response)) {
2251
- mcpSessionId = await this.mcpInitialize(url, init);
2252
- if (mcpSessionId) requestInit = this.withMcpHeaders(requestInit, mcpSessionId);
2253
- }
2254
- const request = this.snapshotX402Request(url, requestInit);
2255
- const option = selectStandardPaymentOption(paymentRequired.accepts);
2256
- const idempotencyKey = options.idempotencyKey ?? (option ? buildX402IdempotencyKey(paymentRequired, option) : void 0);
2257
- let receipt;
2258
- try {
2259
- receipt = await this.authorizeX402(paymentRequired, options);
2260
- } catch (err) {
2261
- if (option && idempotencyKey) {
2262
- this.attachResumeState(err, {
2263
- rail: "x402",
2264
- paymentRequired,
2265
- accepted: option,
2266
- idempotencyKey,
2267
- request
2268
- });
2269
- }
2270
- throw err;
2271
- }
2272
- const retryResponse = await this.retryX402Request(url, requestInit, paymentRequired, receipt);
2273
- return mcpSessionId ? this.surfaceMcpResult(retryResponse) : retryResponse;
2274
- }
2275
- // ── MCP-over-x402 transport helpers (issue #315) ─────────────────
2276
- /**
2277
- * Run the MCP `initialize` handshake against a Streamable-HTTP endpoint and
2278
- * return the `mcp-session-id` the server assigns.
2279
- *
2280
- * Returns `undefined` whenever the endpoint is not actually an MCP server —
2281
- * a transport/HTTP error, a missing session id, or a JSON-RPC error in the
2282
- * handshake response — so the caller can fall back to plain x402.
2283
- */
2284
- async mcpInitialize(url, init, wallet = this.x402PayerAddress()) {
2285
- try {
2286
- const headers = new Headers(init?.headers);
2287
- headers.set("Content-Type", "application/json");
2288
- headers.set("Accept", MCP_ACCEPT);
2289
- if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
2290
- const response = await this.merchantFetch(url, {
2291
- method: "POST",
2292
- headers,
2293
- body: JSON.stringify({
2294
- jsonrpc: "2.0",
2295
- id: ++this.mcpRequestId,
2296
- method: "initialize",
2297
- params: {
2298
- protocolVersion: MCP_PROTOCOL_VERSION,
2299
- capabilities: {},
2300
- clientInfo: MCP_CLIENT_INFO
2301
- }
2302
- })
2303
- });
2304
- if (!response.ok) return void 0;
2305
- const sessionId = response.headers.get("mcp-session-id");
2306
- if (!sessionId) return void 0;
2307
- const message = await this.readMcpMessage(response);
2308
- if (message && "error" in message) return void 0;
2309
- await this.mcpNotifyInitialized(url, init, sessionId, wallet);
2310
- return sessionId;
2311
- } catch {
2312
- return void 0;
2313
- }
2314
- }
2315
- /**
2316
- * Send the MCP `notifications/initialized` notification that completes the
2317
- * lifecycle handshake. Best-effort: the session is already established, so a
2318
- * failed notification must not abort the payment.
2319
- */
2320
- async mcpNotifyInitialized(url, init, sessionId, wallet = this.x402PayerAddress()) {
2321
- try {
2322
- const headers = new Headers(init?.headers);
2323
- headers.set("Content-Type", "application/json");
2324
- headers.set("Accept", MCP_ACCEPT);
2325
- headers.set("mcp-session-id", sessionId);
2326
- if (wallet && !headers.has("x402-wallet")) headers.set("x402-wallet", wallet);
2327
- await this.merchantFetch(
2328
- url,
2329
- {
2330
- method: "POST",
2331
- headers,
2332
- body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
2333
- },
2334
- NOTIFY_TIMEOUT
2335
- );
2336
- } catch {
2337
- }
2338
- }
2339
- /** Read a single JSON-RPC message from an MCP response (JSON or SSE body). */
2340
- async readMcpMessage(response) {
2341
- let text;
2342
- try {
2343
- text = await response.clone().text();
2344
- } catch {
2345
- return void 0;
2346
- }
2347
- if ((response.headers.get("content-type") ?? "").includes("text/event-stream")) {
2348
- return selectJsonRpcResult(parseSseJsonRpcMessages(text));
2349
- }
2350
- try {
2351
- return JSON.parse(text);
2352
- } catch {
2353
- return void 0;
2354
- }
2355
- }
2356
- /** Add the MCP transport headers (session id + SSE Accept) to a request. */
2357
- withMcpHeaders(init, sessionId) {
2358
- const headers = new Headers(init?.headers);
2359
- headers.set("mcp-session-id", sessionId);
2360
- headers.set("Accept", MCP_ACCEPT);
2361
- return { ...init, headers };
2362
- }
2363
- /**
2364
- * Collapse an MCP SSE response into a plain JSON response carrying the
2365
- * JSON-RPC `result`, so callers of `fetch()` never see raw SSE framing.
2366
- * Non-SSE responses pass through untouched.
2367
- */
2368
- async surfaceMcpResult(response) {
2369
- if (!(response.headers.get("content-type") ?? "").includes("text/event-stream")) {
2370
- return response;
2371
- }
2372
- let text;
2373
- try {
2374
- text = await response.clone().text();
2375
- } catch {
2376
- return response;
2377
- }
2378
- const message = selectJsonRpcResult(parseSseJsonRpcMessages(text));
2379
- if (!message) return response;
2380
- const body = "result" in message ? message.result : message;
2381
- const headers = new Headers(response.headers);
2382
- headers.set("content-type", "application/json");
2383
- headers.delete("content-length");
2384
- headers.delete("mcp-session-id");
2385
- return new Response(JSON.stringify(body), {
2386
- status: response.status,
2387
- statusText: response.statusText,
2388
- headers
2389
- });
2390
- }
2391
- async retryX402Request(url, initialInit, paymentRequired, receipt) {
2392
- if (!receipt.accepted) {
2393
- throw new HavenApiError("No accepted x402 option was recorded for payment retry", 500);
2394
- }
2395
- if (!receipt.paymentHeader) {
2396
- throw new HavenApiError("No x402 payment header was returned for payment retry", 500);
2397
- }
2398
- const retryHeaders = new Headers(initialInit?.headers);
2399
- retryHeaders.set("X-PAYMENT", receipt.paymentHeader);
2400
- const retryResponse = await this.merchantFetch(url, {
2401
- ...initialInit,
2402
- headers: retryHeaders
2403
- });
2404
- if (!retryResponse.ok) {
2405
- const merchant = await captureMerchantResponse(retryResponse);
2406
- await this.recordMerchantRetryRejected({
2407
- rail: "x402",
2408
- paymentId: receipt.paymentId,
2409
- txHash: receipt.txHash,
2410
- resourceUrl: receipt.resourceUrl,
2411
- merchant,
2412
- details: {
2413
- merchant_to: receipt.merchantTo,
2414
- delegate_to: receipt.to
2415
- }
2416
- });
2417
- throw new HavenApiError(
2418
- "x402 retry failed after Haven funded the delegate wallet; reconciliation may be required.",
2419
- merchant.merchant_status,
2420
- {
2421
- marker: "x402_retry_rejected_after_funding",
2422
- payment_id: receipt.paymentId,
2423
- tx_hash: receipt.txHash,
2424
- resource_url: receipt.resourceUrl,
2425
- merchant_to: receipt.merchantTo,
2426
- delegate_to: receipt.to,
2427
- ...merchant
2428
- }
2429
- );
2430
- }
2431
- const merchantSettlement = parseMerchantSettlement(retryResponse.headers.get("PAYMENT-RESPONSE"));
2432
- if (receipt.merchant && merchantSettlement.settlementTxHash) {
2433
- receipt.merchant.settlementTxHash = merchantSettlement.settlementTxHash;
2434
- receipt.merchant.settlementExplorerUrl = buildExplorerUrl(
2435
- receipt.chainId,
2436
- merchantSettlement.settlementTxHash
2437
- );
2438
- }
2439
- await this.reportMachinePaymentEvidence({
2440
- paymentId: receipt.paymentId,
2441
- rail: "x402",
2442
- txHash: receipt.txHash,
2443
- resourceUrl: receipt.resourceUrl,
2444
- merchantStatus: retryResponse.status,
2445
- challengePayload: paymentRequired,
2446
- selectedPayment: receipt.accepted,
2447
- paymentProofHeaderName: "X-PAYMENT",
2448
- paymentProofHeader: receipt.paymentHeader,
2449
- protocolReceiptHeaderName: "PAYMENT-RESPONSE",
2450
- protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
2451
- });
2452
- await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
2453
- return retryResponse;
2454
- }
2455
- /**
2456
- * #956: capture the merchant's OWN receipt when the paid response carries
2457
- * one, and report it to Haven so the reporting feed can attach it next to
2458
- * the Haven-generated payment evidence (#498). Two supported signals on the
2459
- * paid response:
2460
- *
2461
- * x-receipt-json: base64-encoded JSON receipt document (inline)
2462
- * x-receipt-url: https URL to the receipt document (reference)
3417
+ * MCP-shaped — the URL path ends in `/mcp`, or the 402 body carries a
3418
+ * Coinbase Bazaar `extensions.bazaar` block — the SDK runs the MCP
3419
+ * `initialize` handshake, threads the resulting `mcp-session-id`,
3420
+ * `Accept: application/json, text/event-stream`, and `x402-wallet` headers
3421
+ * through every request, and collapses SSE responses to the JSON-RPC
3422
+ * `result`. The caller just passes `(url, { body })` and never sees the
3423
+ * protocol plumbing. A non-MCP server (handshake error / no session id)
3424
+ * falls back to standard x402 behaviour.
2463
3425
  *
2464
- * Strictly best-effort: absence is the normal case, and no failure here may
2465
- * ever affect the completed payment — the response is already paid for.
3426
+ * Requires `delegateKey` to be set in the client config.
2466
3427
  */
2467
- async reportMerchantReceipt(paymentId, response) {
3428
+ async fetch(url, init, options = {}) {
3429
+ let mcpSessionId;
3430
+ if (this.merchantTransport.isMcpUrl(url)) {
3431
+ mcpSessionId = await this.merchantTransport.initialize(url, init);
3432
+ }
3433
+ let requestInit = withX402Wallet(init, x402PayerAddress(this.delegateAddress, this.x402Wallet));
3434
+ if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
3435
+ const response = await this.merchantTransport.fetch(url, requestInit);
3436
+ if (response.status !== 402) {
3437
+ return mcpSessionId ? this.merchantTransport.surfaceResult(response) : response;
3438
+ }
3439
+ let paymentRequired;
2468
3440
  try {
2469
- const inlineB64 = response.headers.get("x-receipt-json");
2470
- const url = response.headers.get("x-receipt-url");
2471
- if (!inlineB64 && !url) return;
2472
- let body = null;
2473
- if (inlineB64) {
2474
- if (inlineB64.length > Math.ceil(64 * 1024 * 4 / 3)) return;
2475
- const decoded = JSON.parse(Buffer.from(inlineB64, "base64").toString("utf8"));
2476
- if (decoded && typeof decoded === "object") body = { json: decoded };
2477
- } else if (url && url.startsWith("https://") && url.length <= 2048) {
2478
- body = { url };
2479
- }
2480
- if (!body) return;
2481
- await this.post(`/machine-payments/${paymentId}/merchant-receipt`, body);
3441
+ paymentRequired = await parsePaymentRequiredResponse(response);
2482
3442
  } catch {
3443
+ return response;
3444
+ }
3445
+ if (!mcpSessionId && await this.merchantTransport.hasBazaarExtension(response)) {
3446
+ mcpSessionId = await this.merchantTransport.initialize(url, init);
3447
+ if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
3448
+ }
3449
+ const request = snapshotX402Request(url, requestInit);
3450
+ const option = selectStandardPaymentOption(paymentRequired.accepts);
3451
+ const idempotencyKey = options.idempotencyKey ?? (option ? buildX402IdempotencyKey(paymentRequired, option) : void 0);
3452
+ let receipt;
3453
+ try {
3454
+ receipt = await this.authorizeX402(paymentRequired, options);
3455
+ } catch (err) {
3456
+ if (option && idempotencyKey) {
3457
+ attachResumeState(err, {
3458
+ paymentRequired,
3459
+ accepted: option,
3460
+ idempotencyKey,
3461
+ request
3462
+ });
3463
+ }
3464
+ throw err;
2483
3465
  }
3466
+ const retryResponse = await this.merchantCompletion.retryRequest(url, requestInit, paymentRequired, receipt);
3467
+ return mcpSessionId ? this.merchantTransport.surfaceResult(retryResponse) : retryResponse;
2484
3468
  }
2485
3469
  /**
2486
3470
  * Deliver an already-signed x402 payment header to the merchant and return
@@ -2505,7 +3489,7 @@ var HavenClient = class {
2505
3489
  * and before delivering the X-PAYMENT header, so the merchant's
2506
3490
  * balanceOf(delegate) / transferWithAuthorization verification sees the funded
2507
3491
  * balance — otherwise it rejects with "Payment verification failed". The
2508
- * SDK's local path already does this (see authorizeStandardX402); the hosted
3492
+ * SDK's local path already does this (see `X402FundingLeg.authorize`); the hosted
2509
3493
  * split flow regressed when the 5→3 collapse removed the incidental
2510
3494
  * inter-call latency that used to mask it.
2511
3495
  *
@@ -2522,28 +3506,25 @@ var HavenClient = class {
2522
3506
  */
2523
3507
  async ensureFundingConfirmed(paymentId, fundingTxHash) {
2524
3508
  const status = await this.getPaymentStatus(paymentId);
2525
- await this.waitForFundingTx(fundingTxHash ?? status.txHash ?? void 0, status.chainId);
3509
+ await this.fundingLeg.waitForFundingTx(fundingTxHash ?? status.txHash ?? void 0, status.chainId);
2526
3510
  }
2527
3511
  async completeX402MerchantCall(input) {
2528
- const evidenceContext = await this.resolveX402MerchantCompletionContext({
3512
+ const evidenceContext = await this.merchantCompletion.resolveCompletionContext({
2529
3513
  paymentId: input.paymentId,
2530
3514
  url: input.url,
2531
3515
  noFundingLeg: input.noFundingLeg === true
2532
3516
  });
2533
3517
  const fundingTxHash = evidenceContext.txHash;
2534
- const shouldHandshakeMcp = isMcpUrl(input.url) || input.mcpTransport?.handshakeRequired === true;
2535
- const x402Wallet = shouldHandshakeMcp ? await this.resolveX402WalletForMerchantCall() : this.x402PayerAddress();
3518
+ const shouldHandshakeMcp = this.merchantTransport.isMcpUrl(input.url) || input.mcpTransport?.handshakeRequired === true;
3519
+ const x402Wallet = shouldHandshakeMcp ? await this.merchantCompletion.resolveWalletForMerchantCall() : x402PayerAddress(this.delegateAddress, this.x402Wallet);
2536
3520
  let mcpSessionId;
2537
3521
  if (shouldHandshakeMcp) {
2538
- mcpSessionId = await this.mcpInitialize(input.url, input.init, x402Wallet);
3522
+ mcpSessionId = await this.merchantTransport.initialize(input.url, input.init, x402Wallet);
2539
3523
  }
2540
- let requestInit = this.withX402Wallet(input.init, x402Wallet) ?? {};
2541
- if (mcpSessionId) requestInit = this.withMcpHeaders(requestInit, mcpSessionId);
2542
- const headers = new Headers(requestInit.headers);
2543
- headers.set("X-PAYMENT", input.paymentHeader);
2544
- requestInit = { ...requestInit, headers };
2545
- const response = await this.merchantFetch(input.url, requestInit);
2546
- const surfaced = mcpSessionId ? await this.surfaceMcpResult(response) : response;
3524
+ let requestInit = withX402Wallet(input.init, x402Wallet) ?? {};
3525
+ if (mcpSessionId) requestInit = this.merchantTransport.withSessionHeaders(requestInit, mcpSessionId);
3526
+ const response = await this.merchantTransport.deliverPayment(input.url, requestInit, input.paymentHeader);
3527
+ const surfaced = mcpSessionId ? await this.merchantTransport.surfaceResult(response) : response;
2547
3528
  const protocolReceiptHeader = surfaced.headers.get("PAYMENT-RESPONSE") ?? void 0;
2548
3529
  const settlement = parseMerchantSettlement(protocolReceiptHeader ?? null);
2549
3530
  const text = await surfaced.text();
@@ -2555,7 +3536,7 @@ var HavenClient = class {
2555
3536
  }
2556
3537
  if (!surfaced.ok) {
2557
3538
  if (!input.noFundingLeg && fundingTxHash) {
2558
- await this.recordMerchantRetryRejected({
3539
+ await this.merchantCompletion.recordRetryRejected({
2559
3540
  rail: "x402",
2560
3541
  paymentId: evidenceContext.paymentId,
2561
3542
  txHash: fundingTxHash,
@@ -2573,7 +3554,7 @@ var HavenClient = class {
2573
3554
  }
2574
3555
  } else {
2575
3556
  if (!input.noFundingLeg && fundingTxHash) {
2576
- await this.reportMachinePaymentEvidence({
3557
+ await this.merchantCompletion.reportEvidence({
2577
3558
  paymentId: evidenceContext.paymentId,
2578
3559
  rail: "x402",
2579
3560
  txHash: fundingTxHash,
@@ -2585,7 +3566,7 @@ var HavenClient = class {
2585
3566
  protocolReceiptHeader
2586
3567
  });
2587
3568
  }
2588
- await this.reportMerchantReceipt(evidenceContext.paymentId, surfaced);
3569
+ await this.merchantCompletion.reportMerchantReceipt(evidenceContext.paymentId, surfaced);
2589
3570
  }
2590
3571
  return {
2591
3572
  status: surfaced.status,
@@ -2615,271 +3596,11 @@ var HavenClient = class {
2615
3596
  arguments: raw.arguments ?? {},
2616
3597
  ...raw.mcp_transport ? {
2617
3598
  mcpTransport: {
2618
- handshakeRequired: raw.mcp_transport.handshake_required,
2619
- source: raw.mcp_transport.source
2620
- }
2621
- } : {}
2622
- };
2623
- }
2624
- async resolveX402MerchantCompletionContext(input) {
2625
- const status = await this.getPaymentStatus(input.paymentId);
2626
- if (status.rail !== "x402") {
2627
- throw new HavenPaymentStateError(
2628
- `Payment ${status.paymentId} is ${status.rail}, not x402.`,
2629
- 409,
2630
- status
2631
- );
2632
- }
2633
- const readyForMerchantCompletion = input.noFundingLeg ? status.kind === "payment_intent" && status.status === "submitted" : status.nextAction === AgentPaymentNextAction.RetryOriginalX402Request || status.kind === "payment_intent" && status.status === "confirmed" && status.phase === AgentPaymentPhase.PaymentConfirmed && status.nextAction === AgentPaymentNextAction.None;
2634
- if (!readyForMerchantCompletion) {
2635
- throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
2636
- }
2637
- if (!input.noFundingLeg && !status.txHash) {
2638
- throw new HavenApiError(
2639
- `x402 payment ${status.paymentId} is ready for merchant completion but has no Haven transaction hash.`,
2640
- 502,
2641
- status,
2642
- status.paymentId
2643
- );
2644
- }
2645
- const approvedResourceUrl = status.resourceUrl ?? status.x402?.resourceUrl ?? null;
2646
- if (approvedResourceUrl && approvedResourceUrl !== input.url) {
2647
- throw new HavenApiError(
2648
- "x402 merchant completion does not match the approved resource URL.",
2649
- 409,
2650
- { status, url: input.url },
2651
- status.paymentId
2652
- );
2653
- }
2654
- return {
2655
- paymentId: status.paymentId,
2656
- txHash: status.txHash,
2657
- resourceUrl: approvedResourceUrl ?? input.url,
2658
- merchantAddress: status.merchantAddress ?? status.x402?.merchantAddress ?? null
2659
- };
2660
- }
2661
- async resolveX402WalletForMerchantCall() {
2662
- const localWallet = this.x402PayerAddress();
2663
- if (localWallet) return localWallet;
2664
- try {
2665
- const agent = await this.getAgent();
2666
- return agent.delegateAddress ?? void 0;
2667
- } catch {
2668
- return void 0;
2669
- }
2670
- }
2671
- // #1328: authorizeMachinePayment / authorizeMppDemoPayment / resumeAuthorizedMpp
2672
- // / resumeMppPayment / fetchWithMachinePayment / retryMppRequest (the
2673
- // MACHINE-PAYMENT-CHALLENGE / mpp_demo client surface) are retired — the
2674
- // backend's POST /machine-payments/authorize refuses unconditionally now,
2675
- // and MACHINE-PAYMENT-CHALLENGE was never produced by any other Haven
2676
- // surface. Use the x402 flow (authorizeX402 / fetch / quoteX402 / payX402Quote)
2677
- // for agent-to-merchant payments.
2678
- assertCanResumeX402(status, paymentRequired, option) {
2679
- if (status.rail !== "x402") {
2680
- throw new HavenPaymentStateError(
2681
- `Payment ${status.paymentId} is ${status.rail}, not x402.`,
2682
- 409,
2683
- status
2684
- );
2685
- }
2686
- if (status.nextAction !== AgentPaymentNextAction.RetryOriginalX402Request) {
2687
- throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
2688
- }
2689
- if (!status.txHash) {
2690
- throw new HavenApiError(
2691
- `x402 payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
2692
- 502,
2693
- status,
2694
- status.paymentId
2695
- );
2696
- }
2697
- if (status.resourceUrl && status.resourceUrl !== paymentRequired.resource.url) {
2698
- throw new HavenApiError(
2699
- "x402 resume request does not match the approved resource URL.",
2700
- 409,
2701
- { status, paymentRequired },
2702
- status.paymentId
2703
- );
2704
- }
2705
- if (status.merchantAddress && !sameAddress3(status.merchantAddress, option.payTo)) {
2706
- throw new HavenApiError(
2707
- "x402 resume request does not match the approved merchant.",
2708
- 409,
2709
- { status, selectedPayment: option },
2710
- status.paymentId
2711
- );
2712
- }
2713
- const optionChainId = chainIdFromNetwork(option.network);
2714
- if (status.chainId && optionChainId && status.chainId !== optionChainId) {
2715
- throw new HavenApiError(
2716
- "x402 resume request does not match the approved network.",
2717
- 409,
2718
- { status, selectedPayment: option },
2719
- status.paymentId
2720
- );
2721
- }
2722
- if (status.token && status.token !== "USDC") {
2723
- throw new HavenApiError(
2724
- "x402 resume request does not match the approved token.",
2725
- 409,
2726
- { status, selectedPayment: option },
2727
- status.paymentId
2728
- );
2729
- }
2730
- const approvedAmount = status.amount ? normalizeDecimal(status.amount) : "";
2731
- const requestedAmount = normalizeDecimal(decimalFromUsdcAtomic(x402AuthorizationAmount(option)));
2732
- if (approvedAmount && approvedAmount !== requestedAmount) {
2733
- throw new HavenApiError(
2734
- "x402 resume request does not match the approved amount.",
2735
- 409,
2736
- { status, selectedPayment: option },
2737
- status.paymentId
2738
- );
2739
- }
2740
- }
2741
- mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult) {
2742
- const txHash = execResult?.tx_hash ?? raw.tx_hash ?? "";
2743
- const chainId = execResult?.chain_id ?? raw.chain_id ?? chainIdFromNetwork(option.network);
2744
- const token = execResult?.token ?? raw.token ?? "USDC";
2745
- const amount = execResult?.amount ?? raw.amount ?? decimalFromUsdcAtomic(x402AuthorizationAmount(option));
2746
- const to = execResult?.to ?? raw.to ?? this.delegateAddress ?? "";
2747
- const explorerUrl = execResult?.explorer_url ?? raw.explorer_url ?? explorerUrlOrEmpty(chainId, txHash);
2748
- const merchantTo = execResult?.merchant_to ?? raw.merchant_to ?? option.payTo;
2749
- const payer = raw.payer ?? raw.safe_address ?? raw.sign_data?.components.safe;
2750
- return this.buildX402Receipt({
2751
- paymentId: raw.payment_id,
2752
- txHash,
2753
- token,
2754
- amount,
2755
- to,
2756
- resourceUrl: paymentRequired.resource.url,
2757
- explorerUrl,
2758
- accepted: option,
2759
- paymentHeader,
2760
- merchantTo,
2761
- payer,
2762
- chainId
2763
- });
2764
- }
2765
- mapX402ReceiptFromStatus(paymentRequired, option, paymentHeader, status) {
2766
- if (!status.txHash) {
2767
- throw new HavenApiError(
2768
- `x402 payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
2769
- 502,
2770
- status,
2771
- status.paymentId
2772
- );
2773
- }
2774
- return this.buildX402Receipt({
2775
- paymentId: status.paymentId,
2776
- txHash: status.txHash,
2777
- token: status.token || "USDC",
2778
- amount: status.amount || decimalFromUsdcAtomic(x402AuthorizationAmount(option)),
2779
- to: this.delegateAddress ?? "",
2780
- resourceUrl: paymentRequired.resource.url,
2781
- explorerUrl: explorerUrlOrEmpty(status.chainId, status.txHash),
2782
- accepted: option,
2783
- paymentHeader,
2784
- merchantTo: status.merchantAddress ?? option.payTo,
2785
- payer: this.x402Wallet,
2786
- chainId: status.chainId || chainIdFromNetwork(option.network)
2787
- });
2788
- }
2789
- buildX402Receipt(input) {
2790
- const fundingExplorerUrl = input.explorerUrl || explorerUrlOrEmpty(input.chainId, input.txHash);
2791
- return {
2792
- success: true,
2793
- paymentId: input.paymentId,
2794
- txHash: input.txHash,
2795
- token: input.token,
2796
- amount: input.amount,
2797
- to: input.to,
2798
- resourceUrl: input.resourceUrl,
2799
- explorerUrl: input.explorerUrl,
2800
- accepted: input.accepted,
2801
- paymentHeader: input.paymentHeader,
2802
- merchantTo: input.merchantTo ?? input.accepted.payTo,
2803
- payer: input.payer,
2804
- chainId: input.chainId,
2805
- haven: {
2806
- paymentId: input.paymentId,
2807
- fundingTxHash: input.txHash,
2808
- fundingExplorerUrl
2809
- },
2810
- merchant: {
2811
- payTo: input.merchantTo ?? input.accepted.payTo
2812
- },
2813
- x402: {
2814
- amount: x402AuthorizationAmount(input.accepted),
2815
- token: input.token,
2816
- network: input.accepted.network,
2817
- asset: input.accepted.asset,
2818
- resource: input.accepted.resource ?? input.resourceUrl
2819
- }
2820
- };
2821
- }
2822
- async createStandardX402Header(paymentRequired, option) {
2823
- if (!this.delegateKey) {
2824
- throw new HavenSigningError("delegateKey is required to sign x402 payment headers.");
2825
- }
2826
- const account = privateKeyToAccount(this.delegateKey);
2827
- const requirements = toStandardPaymentRequirements(paymentRequired, option);
2828
- const header = await exact.evm.createPaymentHeader(
2829
- account,
2830
- paymentRequired.x402Version,
2831
- requirements
2832
- );
2833
- if (paymentRequired.x402Version < 2) return header;
2834
- const payment = decodeBase64Json(header);
2835
- return encodeBase64Json({
2836
- x402Version: paymentRequired.x402Version,
2837
- accepted: option,
2838
- payload: payment.payload
2839
- });
2840
- }
2841
- cacheX402Receipt(idempotencyKey, paymentHeader, receipt) {
2842
- const expiresAt = getPaymentHeaderValidBefore(paymentHeader);
2843
- if (expiresAt > Date.now()) {
2844
- this.x402ReceiptCache.set(idempotencyKey, { expiresAt, receipt });
2845
- }
2846
- }
2847
- async recordMerchantRetryRejected(input) {
2848
- try {
2849
- await this.post("/machine-payments/reconciliation-events", {
2850
- paymentId: input.paymentId,
2851
- rail: input.rail,
2852
- eventType: "merchant_retry_rejected_after_payment",
2853
- txHash: input.txHash,
2854
- reason: `Merchant returned HTTP ${input.merchant.merchant_status} after Haven payment confirmation`,
2855
- details: {
2856
- resource_url: input.resourceUrl,
2857
- retry_status: input.merchant.merchant_status,
2858
- retry_body: input.merchant.merchant_body.slice(0, MERCHANT_BODY_SNIPPET_LIMIT) || null,
2859
- ...input.details
2860
- }
2861
- });
2862
- } catch {
2863
- }
2864
- }
2865
- async reportMachinePaymentEvidence(input) {
2866
- try {
2867
- await this.post("/machine-payments/evidence", {
2868
- paymentId: input.paymentId,
2869
- rail: input.rail,
2870
- txHash: input.txHash,
2871
- resourceUrl: input.resourceUrl,
2872
- merchantStatus: input.merchantStatus,
2873
- challengePayload: input.challengePayload,
2874
- selectedPayment: input.selectedPayment,
2875
- paymentProofHeaderName: input.paymentProofHeaderName,
2876
- paymentProofHeader: input.paymentProofHeader,
2877
- protocolReceiptHeaderName: input.protocolReceiptHeaderName,
2878
- protocolReceiptHeader: input.protocolReceiptHeader,
2879
- protocolReceiptPayload: input.protocolReceiptHeader ? parseProtocolReceiptHeader(input.protocolReceiptHeader) : void 0
2880
- });
2881
- } catch {
2882
- }
3599
+ handshakeRequired: raw.mcp_transport.handshake_required,
3600
+ source: raw.mcp_transport.source
3601
+ }
3602
+ } : {}
3603
+ };
2883
3604
  }
2884
3605
  /**
2885
3606
  * Wait for a funding tx to be mined with ≥1 confirmation before the
@@ -2890,264 +3611,9 @@ var HavenClient = class {
2890
3611
  * backend has already confirmed on-chain submission and callers accept the
2891
3612
  * small propagation window as a trade-off for not configuring an RPC URL.
2892
3613
  */
2893
- async waitForFundingTx(txHash, chainId, timeoutMs = 3e4) {
2894
- if (!txHash || !chainId) return;
2895
- const rpcUrl = this.chainRpcs[chainId];
2896
- if (!rpcUrl) return;
2897
- const provider = createJsonRpcProvider(rpcUrl);
2898
- const onChainReceipt = await provider.waitForTransaction(txHash, 1, timeoutMs);
2899
- if (!onChainReceipt || onChainReceipt.status !== 1) {
2900
- throw new HavenApiError(
2901
- "Funding tx did not confirm on-chain within the timeout window.",
2902
- 500,
2903
- { txHash, chainId }
2904
- );
2905
- }
2906
- }
2907
- /**
2908
- * Can the delegate EOA still fund an authorization for `amountAtomic`?
2909
- *
2910
- * #1521: the only question that separates a legitimate resume (funding
2911
- * confirmed, merchant never paid — the delegate still holds the money) from
2912
- * a replayed settled payment (funding confirmed, merchant paid, delegate
2913
- * spent). The intent's own `status: 'confirmed'` is identical in both.
2914
- *
2915
- * The balance is asked of the CHAIN rather than of Haven's bookkeeping on
2916
- * purpose: the merchant-settlement evidence record is written by this SDK
2917
- * *after* the merchant call, so a client that dies between the two leaves
2918
- * the backend believing the merchant was never paid — the exact case the
2919
- * discriminator has to get right. The chain cannot be behind in that way.
2920
- *
2921
- * Returns `null` — never a guess — when `chainRpcs` has no entry for the
2922
- * chain or the read fails. Callers must treat that as "unverifiable", not
2923
- * as "funded".
2924
- */
2925
- async delegateCanFund(chainId, tokenAddress, amountAtomic, timeoutMs = 1e4) {
2926
- if (!chainId || !this.delegateAddress) return null;
2927
- const rpcUrl = this.chainRpcs[chainId];
2928
- if (!rpcUrl) return null;
2929
- try {
2930
- const provider = createJsonRpcProvider(rpcUrl);
2931
- const token = createErc20Contract(
2932
- tokenAddress,
2933
- ["function balanceOf(address) view returns (uint256)"],
2934
- provider
2935
- );
2936
- const balance = await Promise.race([
2937
- token.balanceOf(this.delegateAddress),
2938
- new Promise((resolve) => setTimeout(() => resolve(null), timeoutMs).unref?.())
2939
- ]);
2940
- if (balance === null) return null;
2941
- return balance >= BigInt(amountAtomic);
2942
- } catch {
2943
- return null;
2944
- }
2945
- }
2946
3614
  throwIfNonSignableAuthorizationState(label, raw) {
2947
3615
  if (raw.status === "pending_signature") return;
2948
- this.throwPaymentStateError(label, raw);
2949
- }
2950
- throwPaymentStateError(label, raw) {
2951
- const statusCode = PAYMENT_STATE_STATUS_CODES[raw.status] ?? 502;
2952
- const state = this.paymentStateFromRaw(label, raw);
2953
- if (state) {
2954
- throw new HavenPaymentStateError(state.message, statusCode, state, raw);
2955
- }
2956
- if (raw.status === "pending_approval") {
2957
- throw new HavenApiError(
2958
- `${label} exceeds the on-chain allowance and was queued for owner approval (payment_id: ${raw.payment_id}).`,
2959
- statusCode,
2960
- raw
2961
- );
2962
- }
2963
- if (raw.status === "expired") {
2964
- throw new HavenApiError(
2965
- `${label} expired before it could be completed (payment_id: ${raw.payment_id}).`,
2966
- statusCode,
2967
- raw
2968
- );
2969
- }
2970
- const paymentId = raw.payment_id ? ` (payment_id: ${raw.payment_id})` : "";
2971
- const message = raw.error ?? `${label} ${raw.status}${paymentId}`;
2972
- throw new HavenApiError(message, statusCode, raw);
2973
- }
2974
- paymentStateFromRaw(label, raw) {
2975
- if (!raw.payment_id || !raw.status) return null;
2976
- const phase = raw.phase ?? phaseForStatus(raw.status);
2977
- const nextAction = raw.next_action ?? nextActionForStatus(raw.status);
2978
- if (!phase || !nextAction) return null;
2979
- const amount = raw.amount ?? raw.requested ?? "";
2980
- const token = raw.token ?? "";
2981
- const message = raw.message ?? raw.error ?? messageForState(label, raw.status, raw.payment_id, nextAction);
2982
- return {
2983
- paymentId: raw.payment_id,
2984
- kind: raw.kind === "payment_intent" ? "payment_intent" : "approval_request",
2985
- rail: raw.rail ?? "direct",
2986
- status: raw.status === "pending" ? "pending_approval" : raw.status,
2987
- phase,
2988
- nextAction,
2989
- amount,
2990
- token,
2991
- resourceUrl: raw.resource_url ?? null,
2992
- merchantAddress: raw.merchant_address ?? raw.merchant_to ?? null,
2993
- txHash: raw.tx_hash ?? null,
2994
- expiresAt: raw.expires_at ?? "",
2995
- chainId: raw.chain_id ?? 0,
2996
- message,
2997
- amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? raw.mpp?.amount_atomic ?? null,
2998
- asset: raw.asset ?? raw.x402?.asset ?? raw.mpp?.asset ?? null,
2999
- network: raw.network ?? raw.x402?.network ?? raw.mpp?.network ?? null,
3000
- description: raw.description ?? raw.x402?.description ?? raw.mpp?.description ?? null,
3001
- idempotencyKey: raw.idempotency_key ?? raw.x402?.idempotency_key ?? raw.mpp?.idempotency_key ?? null,
3002
- x402: raw.x402 ? {
3003
- amountAtomic: raw.x402.amount_atomic ?? raw.amount_atomic ?? null,
3004
- asset: raw.x402.asset ?? raw.asset ?? null,
3005
- network: raw.x402.network ?? raw.network ?? null,
3006
- resourceUrl: raw.x402.resource_url ?? raw.resource_url ?? null,
3007
- merchantAddress: raw.x402.merchant_address ?? raw.merchant_address ?? raw.merchant_to ?? null,
3008
- description: raw.x402.description ?? raw.description ?? null,
3009
- idempotencyKey: raw.x402.idempotency_key ?? raw.idempotency_key ?? null
3010
- } : void 0,
3011
- mpp: raw.mpp ? {
3012
- amountAtomic: raw.mpp.amount_atomic ?? raw.amount_atomic ?? null,
3013
- asset: raw.mpp.asset ?? raw.asset ?? null,
3014
- network: raw.mpp.network ?? raw.network ?? null,
3015
- resourceUrl: raw.mpp.resource_url ?? raw.resource_url ?? null,
3016
- merchantAddress: raw.mpp.merchant_address ?? raw.merchant_address ?? raw.merchant_to ?? null,
3017
- description: raw.mpp.description ?? raw.description ?? null,
3018
- idempotencyKey: raw.mpp.idempotency_key ?? raw.idempotency_key ?? null,
3019
- challengeId: raw.mpp.challenge_id ?? raw.challenge_id ?? null
3020
- } : void 0
3021
- };
3022
- }
3023
- x402PayerAddress() {
3024
- return this.delegateAddress ?? this.x402Wallet;
3025
- }
3026
- snapshotX402Request(url, init) {
3027
- return {
3028
- url,
3029
- method: init?.method ?? "GET",
3030
- headers: Array.from(new Headers(init?.headers).entries()),
3031
- body: this.snapshotRequestBody(init?.body)
3032
- };
3033
- }
3034
- snapshotRequestBody(body) {
3035
- if (body == null) return void 0;
3036
- if (typeof body === "string") return body;
3037
- if (body instanceof URLSearchParams) return body.toString();
3038
- throw new HavenApiError(
3039
- "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.",
3040
- 400
3041
- );
3042
- }
3043
- requestInitFromSnapshot(request) {
3044
- return {
3045
- method: request.method,
3046
- headers: request.headers,
3047
- body: request.body
3048
- };
3049
- }
3050
- withX402Wallet(init, wallet = this.x402PayerAddress()) {
3051
- if (!wallet) return init;
3052
- const headers = new Headers(init?.headers);
3053
- if (!headers.has("x402-wallet")) {
3054
- headers.set("x402-wallet", wallet);
3055
- }
3056
- return {
3057
- ...init,
3058
- headers
3059
- };
3060
- }
3061
- buildX402Quote(paymentRequired, request, idempotencyKey, mcpTransport) {
3062
- const option = selectStandardPaymentOption(paymentRequired.accepts);
3063
- if (!option) {
3064
- throw new HavenApiError(
3065
- "No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC.",
3066
- 400
3067
- );
3068
- }
3069
- const token = resolveTokenFromAddress(option.asset, option.network);
3070
- return {
3071
- rail: "x402",
3072
- idempotencyKey: idempotencyKey ?? buildX402IdempotencyKey(paymentRequired, option),
3073
- paymentRequired,
3074
- accepted: option,
3075
- request,
3076
- ...mcpTransport ? { mcpTransport } : {},
3077
- resourceUrl: paymentRequired.resource.url,
3078
- description: paymentRequired.resource.description ?? option.description ?? null,
3079
- mimeType: paymentRequired.resource.mimeType ?? option.mimeType ?? null,
3080
- amountAtomic: x402AuthorizationAmount(option),
3081
- amount: decimalFromUsdcAtomic(x402AuthorizationAmount(option)),
3082
- token: token?.symbol ?? "USDC",
3083
- // #1351: null when the asset is unrecognised on this network — the
3084
- // `token` fallback above is a LABEL, not evidence of 6 decimals, and a
3085
- // human-denominated cap must fail closed rather than convert against a
3086
- // guess. Same resolution as `token`, so the two never disagree.
3087
- decimals: token?.decimals ?? null,
3088
- asset: option.asset,
3089
- network: option.network,
3090
- chainId: chainIdOrNull(option.network),
3091
- merchantAddress: option.payTo,
3092
- maxTimeoutSeconds: option.maxTimeoutSeconds
3093
- };
3094
- }
3095
- async detectX402McpTransport(url, paymentRequired, response) {
3096
- if (isMcpUrl(url)) {
3097
- return { handshakeRequired: true, source: "path" };
3098
- }
3099
- if (paymentRequired.extensions?.bazaar != null) {
3100
- return { handshakeRequired: true, source: "bazaar" };
3101
- }
3102
- if (await responseHasBazaarExtension(response)) {
3103
- return { handshakeRequired: true, source: "bazaar" };
3104
- }
3105
- return void 0;
3106
- }
3107
- buildX402ResumeState(input) {
3108
- const token = resolveTokenFromAddress(input.accepted.asset, input.accepted.network);
3109
- return {
3110
- rail: "x402",
3111
- paymentId: input.paymentId,
3112
- idempotencyKey: input.idempotencyKey,
3113
- paymentRequired: input.paymentRequired,
3114
- accepted: input.accepted,
3115
- url: input.request?.url ?? input.paymentRequired.resource.url,
3116
- request: input.request,
3117
- resourceUrl: input.paymentRequired.resource.url,
3118
- description: input.paymentRequired.resource.description ?? input.accepted.description ?? null,
3119
- amountAtomic: x402AuthorizationAmount(input.accepted),
3120
- amount: decimalFromUsdcAtomic(x402AuthorizationAmount(input.accepted)),
3121
- token: token?.symbol ?? "USDC",
3122
- asset: input.accepted.asset,
3123
- network: input.accepted.network,
3124
- chainId: chainIdOrNull(input.accepted.network),
3125
- merchantAddress: input.accepted.payTo
3126
- };
3127
- }
3128
- // #1328: attachResumeState's 'mpp' branch (buildMppQuote / buildMppResumeState
3129
- // / attachMppResumeState) is retired along with the rest of the MPP-demo
3130
- // client surface — every remaining caller passes rail: 'x402' only, so this
3131
- // is now a direct alias for attachX402ResumeState rather than a dispatcher.
3132
- attachResumeState(err, input) {
3133
- this.attachX402ResumeState(
3134
- err,
3135
- input.paymentRequired,
3136
- input.accepted,
3137
- input.idempotencyKey,
3138
- input.request
3139
- );
3140
- }
3141
- attachX402ResumeState(err, paymentRequired, accepted, idempotencyKey, request) {
3142
- if (!(err instanceof HavenPaymentStateError)) return;
3143
- if (err.state.rail !== "x402") return;
3144
- err.resumeState = this.buildX402ResumeState({
3145
- paymentId: err.state.paymentId,
3146
- paymentRequired,
3147
- accepted,
3148
- idempotencyKey,
3149
- request
3150
- });
3616
+ throwPaymentStateError(label, raw);
3151
3617
  }
3152
3618
  // ── Tool Execution (for agent frameworks) ────────────────────────
3153
3619
  /**
@@ -3179,19 +3645,19 @@ var HavenClient = class {
3179
3645
  error: result.errorMessage
3180
3646
  };
3181
3647
  } catch (err) {
3182
- return this.toolError(err);
3648
+ return toolError(err);
3183
3649
  }
3184
3650
  }
3185
3651
  if (toolName === "authorize_x402_payment") {
3186
3652
  const { url, payTo, amount, asset, network, description, idempotencyKey } = input;
3187
3653
  try {
3188
3654
  const receipt = await this.authorizeX402(
3189
- this.toolX402PaymentRequired({ url, payTo, amount, asset, network, description }),
3655
+ toolX402PaymentRequired({ url, payTo, amount, asset, network, description }),
3190
3656
  { idempotencyKey }
3191
3657
  );
3192
- return this.x402ToolReceipt(receipt);
3658
+ return x402ToolReceipt(receipt);
3193
3659
  } catch (err) {
3194
- return this.toolError(err);
3660
+ return toolError(err);
3195
3661
  }
3196
3662
  }
3197
3663
  if (toolName === "resume_x402_payment") {
@@ -3199,12 +3665,12 @@ var HavenClient = class {
3199
3665
  try {
3200
3666
  const receipt = await this.resumeAuthorizedX402({
3201
3667
  paymentId: payment_id,
3202
- paymentRequired: this.toolX402PaymentRequired({ url, payTo, amount, asset, network, description }),
3668
+ paymentRequired: toolX402PaymentRequired({ url, payTo, amount, asset, network, description }),
3203
3669
  idempotencyKey
3204
3670
  });
3205
- return this.x402ToolReceipt(receipt);
3671
+ return x402ToolReceipt(receipt);
3206
3672
  } catch (err) {
3207
- return this.toolError(err);
3673
+ return toolError(err);
3208
3674
  }
3209
3675
  }
3210
3676
  if (toolName === "get_payment_status") {
@@ -3239,302 +3705,17 @@ var HavenClient = class {
3239
3705
  }
3240
3706
  throw new Error(`Unknown tool: ${toolName}`);
3241
3707
  }
3242
- toolX402PaymentRequired(input) {
3243
- return {
3244
- x402Version: 2,
3245
- resource: { url: input.url, description: input.description },
3246
- accepts: [
3247
- {
3248
- scheme: "exact",
3249
- network: input.network,
3250
- amount: input.amount,
3251
- asset: input.asset,
3252
- payTo: input.payTo,
3253
- maxTimeoutSeconds: 30
3254
- }
3255
- ]
3256
- };
3257
- }
3258
- x402ToolReceipt(receipt) {
3259
- return {
3260
- success: true,
3261
- payment_id: receipt.paymentId,
3262
- tx_hash: receipt.txHash,
3263
- token: receipt.token,
3264
- amount: receipt.amount,
3265
- to: receipt.to,
3266
- resource_url: receipt.resourceUrl,
3267
- explorer_url: receipt.explorerUrl,
3268
- payment_header: receipt.paymentHeader,
3269
- merchant_to: receipt.merchantTo,
3270
- payer: receipt.payer,
3271
- chain_id: receipt.chainId,
3272
- haven: receipt.haven,
3273
- merchant: receipt.merchant,
3274
- x402: receipt.x402
3275
- };
3276
- }
3277
- toolError(err) {
3278
- if (err instanceof HavenPaymentStateError) {
3279
- return {
3280
- success: false,
3281
- payment_id: err.state.paymentId,
3282
- kind: err.state.kind,
3283
- rail: err.state.rail,
3284
- status: err.state.status,
3285
- phase: err.state.phase,
3286
- next_action: err.state.nextAction,
3287
- tx_hash: err.state.txHash,
3288
- token: err.state.token,
3289
- amount: err.state.amount,
3290
- resource_url: err.state.resourceUrl,
3291
- merchant_address: err.state.merchantAddress,
3292
- amount_atomic: err.state.amountAtomic,
3293
- asset: err.state.asset,
3294
- network: err.state.network,
3295
- description: err.state.description,
3296
- idempotency_key: err.state.idempotencyKey,
3297
- x402: err.state.x402 ? {
3298
- amount_atomic: err.state.x402.amountAtomic,
3299
- asset: err.state.x402.asset,
3300
- network: err.state.x402.network,
3301
- resource_url: err.state.x402.resourceUrl,
3302
- merchant_address: err.state.x402.merchantAddress,
3303
- description: err.state.x402.description,
3304
- idempotency_key: err.state.x402.idempotencyKey
3305
- } : void 0,
3306
- mpp: err.state.mpp ? {
3307
- amount_atomic: err.state.mpp.amountAtomic,
3308
- asset: err.state.mpp.asset,
3309
- network: err.state.mpp.network,
3310
- resource_url: err.state.mpp.resourceUrl,
3311
- merchant_address: err.state.mpp.merchantAddress,
3312
- description: err.state.mpp.description,
3313
- idempotency_key: err.state.mpp.idempotencyKey,
3314
- challenge_id: err.state.mpp.challengeId
3315
- } : void 0,
3316
- resume_state: err.resumeState,
3317
- expires_at: err.state.expiresAt,
3318
- chain_id: err.state.chainId,
3319
- message: err.state.message,
3320
- error: err.message
3321
- };
3322
- }
3323
- if (err instanceof HavenApiError) {
3324
- return {
3325
- success: false,
3326
- status_code: err.statusCode,
3327
- error: err.message,
3328
- body: err.body
3329
- };
3330
- }
3331
- return {
3332
- success: false,
3333
- error: err instanceof Error ? err.message : String(err)
3334
- };
3335
- }
3336
3708
  // ── HTTP Helpers ─────────────────────────────────────────────────
3337
3709
  async post(path, body) {
3338
- return this.request("POST", path, body);
3710
+ return this.havenApi.post(path, body);
3339
3711
  }
3340
3712
  async get(path) {
3341
- return this.request("GET", path);
3342
- }
3343
- /**
3344
- * #1300: every MERCHANT-facing fetch goes through here. Haven API calls
3345
- * have always been bounded (request() below); the merchant probes/retries
3346
- * called globalThis.fetch bare, so a slow-loris merchant could hold a tool
3347
- * call open forever. A caller-supplied signal still applies (combined via
3348
- * AbortSignal.any); a timeout abort surfaces as a clear HavenApiError 504
3349
- * naming the URL rather than a bare AbortError.
3350
- */
3351
- async merchantFetch(url, init = {}, timeoutMs = this.merchantTimeout) {
3352
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
3353
- const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
3354
- try {
3355
- return await globalThis.fetch(url, { ...init, signal });
3356
- } catch (err) {
3357
- if (timeoutSignal.aborted) {
3358
- throw new MerchantTimeoutError(`Merchant request timed out after ${timeoutMs}ms: ${url}`);
3359
- }
3360
- throw err;
3361
- }
3362
- }
3363
- async request(method, path, body) {
3364
- const url = `${this.baseUrl}${path}`;
3365
- const controller = new AbortController();
3366
- const timeout = setTimeout(() => controller.abort(), this.requestTimeout);
3367
- try {
3368
- const contextHeaders = this.requestContext.getStore()?.headers ?? {};
3369
- const res = await fetch(url, {
3370
- method,
3371
- headers: {
3372
- "Content-Type": "application/json",
3373
- "Authorization": `Bearer ${this.apiKey}`,
3374
- ...this.defaultHeaders,
3375
- ...contextHeaders
3376
- },
3377
- body: body ? JSON.stringify(body) : void 0,
3378
- signal: controller.signal
3379
- });
3380
- const data = await res.json();
3381
- if (!res.ok) {
3382
- const record = data;
3383
- const errorText = typeof record.error === "string" ? record.error : void 0;
3384
- const rawDetails = record.details ?? record.detail;
3385
- const detailsText = typeof rawDetails === "string" ? rawDetails : rawDetails != null ? JSON.stringify(rawDetails) : void 0;
3386
- const message = errorText && detailsText ? `${errorText}: ${detailsText}` : errorText ?? detailsText ?? `API request failed`;
3387
- throw new HavenApiError(message, res.status, data);
3388
- }
3389
- return data;
3390
- } catch (err) {
3391
- if (err instanceof HavenApiError) throw err;
3392
- if (err instanceof Error && err.name === "AbortError") {
3393
- throw new HavenApiError(`Request to ${path} timed out`, 408);
3394
- }
3395
- throw new HavenApiError(
3396
- `Request to ${path} failed: ${err instanceof Error ? err.message : String(err)}`,
3397
- 0
3398
- );
3399
- } finally {
3400
- clearTimeout(timeout);
3401
- }
3402
- }
3403
- // ── Mapping Helpers ──────────────────────────────────────────────
3404
- mapPaymentResult(raw) {
3405
- return {
3406
- paymentId: raw.payment_id,
3407
- status: raw.status,
3408
- token: raw.token,
3409
- amount: raw.amount,
3410
- to: raw.to,
3411
- txHash: raw.tx_hash,
3412
- errorMessage: raw.error_message,
3413
- explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : null),
3414
- fee: raw.fee ? {
3415
- amount: raw.fee.amount,
3416
- token: raw.fee.token,
3417
- basisPoints: raw.fee.basis_points,
3418
- applied: raw.fee.applied
3419
- } : null,
3420
- createdAt: raw.created_at,
3421
- signedAt: raw.signed_at,
3422
- submittedAt: raw.submitted_at,
3423
- confirmedAt: raw.confirmed_at,
3424
- expiresAt: raw.expires_at
3425
- };
3426
- }
3427
- mapPaymentStatusResult(raw) {
3428
- return {
3429
- paymentId: raw.payment_id,
3430
- kind: raw.kind,
3431
- rail: raw.rail,
3432
- status: raw.status,
3433
- phase: raw.phase,
3434
- nextAction: raw.next_action,
3435
- amount: raw.amount,
3436
- token: raw.token,
3437
- resourceUrl: raw.resource_url,
3438
- merchantAddress: raw.merchant_address,
3439
- payerAddress: raw.payer_address ?? null,
3440
- txHash: raw.tx_hash,
3441
- expiresAt: raw.expires_at,
3442
- chainId: raw.chain_id,
3443
- message: raw.message,
3444
- fee: raw.fee ? {
3445
- amount: raw.fee.amount,
3446
- token: raw.fee.token,
3447
- basisPoints: raw.fee.basis_points,
3448
- applied: raw.fee.applied
3449
- } : null,
3450
- amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? null,
3451
- asset: raw.asset ?? raw.x402?.asset ?? null,
3452
- network: raw.network ?? raw.x402?.network ?? null,
3453
- description: raw.description ?? raw.x402?.description ?? null,
3454
- idempotencyKey: raw.idempotency_key ?? raw.x402?.idempotency_key ?? null,
3455
- x402: raw.x402 ? {
3456
- amountAtomic: raw.x402.amount_atomic ?? raw.amount_atomic ?? null,
3457
- asset: raw.x402.asset ?? raw.asset ?? null,
3458
- network: raw.x402.network ?? raw.network ?? null,
3459
- resourceUrl: raw.x402.resource_url ?? raw.resource_url,
3460
- merchantAddress: raw.x402.merchant_address ?? raw.merchant_address,
3461
- description: raw.x402.description ?? raw.description ?? null,
3462
- idempotencyKey: raw.x402.idempotency_key ?? raw.idempotency_key ?? null
3463
- } : void 0
3464
- };
3465
- }
3466
- mapPaymentReceipt(raw) {
3467
- const receipt = {
3468
- id: raw.id,
3469
- paymentId: raw.payment_id,
3470
- rail: raw.rail,
3471
- proofStatus: raw.proof_status,
3472
- txHash: raw.tx_hash,
3473
- chainId: raw.chain_id,
3474
- resourceUrl: raw.resource_url,
3475
- merchantAddress: raw.merchant_address,
3476
- payerAddress: raw.payer_address,
3477
- settlementAddress: raw.settlement_address,
3478
- tokenSymbol: raw.token_symbol,
3479
- tokenAddress: raw.token_address,
3480
- amountRaw: raw.amount_raw,
3481
- amount: raw.amount_human,
3482
- challengeId: raw.challenge_id,
3483
- idempotencyKey: raw.idempotency_key,
3484
- challengePayload: raw.challenge_payload,
3485
- selectedPayment: raw.selected_payment,
3486
- paymentProofHeaderName: raw.payment_proof_header_name,
3487
- protocolReceiptHeaderName: raw.protocol_receipt_header_name,
3488
- protocolReceiptPayload: raw.protocol_receipt_payload,
3489
- merchantStatus: raw.merchant_status,
3490
- confirmedAt: raw.confirmed_at,
3491
- createdAt: raw.created_at,
3492
- updatedAt: raw.updated_at
3493
- };
3494
- if ("payment_intent_id" in raw) {
3495
- receipt.paymentIntentId = raw.payment_intent_id ?? null;
3496
- }
3497
- if ("approval_request_id" in raw) {
3498
- receipt.approvalRequestId = raw.approval_request_id ?? null;
3499
- }
3500
- return receipt;
3713
+ return this.havenApi.get(path);
3501
3714
  }
3502
3715
  };
3503
3716
  function sleep(ms) {
3504
3717
  return new Promise((resolve) => setTimeout(resolve, ms));
3505
3718
  }
3506
- function getPaymentHeaderValidBefore(paymentHeader) {
3507
- try {
3508
- const payment = decodeBase64Json(
3509
- paymentHeader
3510
- );
3511
- const payload = payment.payload;
3512
- const validBeforeSeconds = Number(payload.authorization?.validBefore);
3513
- if (Number.isFinite(validBeforeSeconds)) return validBeforeSeconds * 1e3;
3514
- } catch {
3515
- }
3516
- return 0;
3517
- }
3518
- function parseProtocolReceiptHeader(value) {
3519
- try {
3520
- return decodeBase64Json(value);
3521
- } catch {
3522
- try {
3523
- return JSON.parse(value);
3524
- } catch {
3525
- return void 0;
3526
- }
3527
- }
3528
- }
3529
- async function captureMerchantResponse(response) {
3530
- const merchant_body = await response.text().catch(() => "");
3531
- return {
3532
- merchant_status: response.status,
3533
- merchant_status_text: response.statusText,
3534
- merchant_headers: Object.fromEntries(response.headers.entries()),
3535
- merchant_body
3536
- };
3537
- }
3538
3719
 
3539
3720
  // src/tool-descriptions.ts
3540
3721
  function composeDescription(d) {
@@ -3616,8 +3797,8 @@ var toolDescriptions = {
3616
3797
  sweep_delegate: {
3617
3798
  summary: "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating Safe.",
3618
3799
  selectionGuidance: "Use this when the user instructs you to recover stranded funds on the delegate wallet, or when a payment status returns nextAction=sweep_stranded_funds. Do NOT use for normal payments \u2014 use haven_pay_x402. Do NOT use to read balances only \u2014 use haven_get_allowances.",
3619
- behavior: "Reads the delegate EOA's on-chain USDC and ETH balances. For each non-zero balance, signs and submits a transfer from the delegate EOA to the originating Safe (hardcoded destination). The delegate key signs locally \u2014 Haven never sees it and the backend never constructs signed transactions (CASP/MiCA Red Line #2). Returns tx hashes and recovered amounts. Returns an empty transfers list when nothing is stranded.",
3620
- nextActionGuidance: "If transfers is non-empty, confirm the amounts with the user. No further action required \u2014 funds are on their way back to the Safe."
3800
+ behavior: `Reads the delegate EOA's on-chain USDC and ETH balances. For each non-zero balance, signs and submits a transfer from the delegate EOA to the originating Safe (hardcoded destination). The delegate key signs locally \u2014 Haven never sees it and the backend never constructs signed transactions (CASP/MiCA Red Line #2). Returns tx hashes and recovered amounts. Returns an empty transfers list when nothing is stranded. Each transfer carries confirmation: "confirmed" (a receipt was seen \u2014 the funds are in the Safe) or "unconfirmed" (broadcast but not confirmed within 90 seconds \u2014 still in the mempool, may still land). The top-level unconfirmed flag is true when any transfer is unconfirmed.`,
3801
+ nextActionGuidance: 'If transfers is non-empty, confirm the amounts with the user. Report a transfer as recovered ONLY when its confirmation is "confirmed". For an "unconfirmed" transfer, tell the user it was submitted but not yet confirmed, give them its txHash and explorerUrl to check, and do not re-run the sweep immediately \u2014 a re-run after it lands will simply find nothing stranded.'
3621
3802
  },
3622
3803
  send: {
3623
3804
  summary: "Send ETH or USDC directly from the agent's Haven wallet to a recipient address.",
@@ -4129,6 +4310,6 @@ function sameUrl(a, b) {
4129
4310
  }
4130
4311
  }
4131
4312
 
4132
- 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, AgentPaymentWarningCode, DISCOVERY_MAX_BYTES, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_MINIMUM_NODE_VERSION, HAVEN_SKILL_BODY_MD, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, MERCHANT_DISCOVERY_PATHS, MerchantTimeoutError, RECEIPT_VERSION, SIGNER_UPDATE_FALLBACK, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, SignerRefusalCode, TRANSFER_WITH_AUTHORIZATION_TYPES, X402AlreadySettledError, X402PaymentHeaderValidationError, X402UnexpectedStatusError, X402_MAX_AUTHORIZATION_WINDOW_SECONDS, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, addressFromKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, composeDescription, decodeBase64Json, decodeBase64Utf8, discoverMerchantMcpUrl, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, havenTools, isErc7710Option, isSupportedNodeVersion, isSweepableChain, normalizePaymentRequired, parsePaymentRequired, parsePaymentRequiredResponse, resolveTokenFromAddress, sameUrl, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, signHash, signUserOpTypedDataForDelegation, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, verifyPaymentReceipt, verifySignature, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses };
4313
+ 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, AgentPaymentWarningCode, DEFAULT_CONFIRMATION_TIMEOUT_MS, DISCOVERY_MAX_BYTES, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_MINIMUM_NODE_VERSION, HAVEN_SKILL_BODY_MD, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, MERCHANT_DISCOVERY_PATHS, MerchantTimeoutError, RECEIPT_VERSION, SIGNER_UPDATE_FALLBACK, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, SignerRefusalCode, TRANSFER_WITH_AUTHORIZATION_TYPES, X402AlreadySettledError, X402PaymentHeaderValidationError, X402UnexpectedStatusError, X402_MAX_AUTHORIZATION_WINDOW_SECONDS, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, addressFromKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, composeDescription, decodeBase64Json, decodeBase64Utf8, discoverMerchantMcpUrl, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, havenTools, isErc7710Option, isSupportedNodeVersion, isSweepableChain, normalizePaymentRequired, parsePaymentRequired, parsePaymentRequiredResponse, resolveTokenFromAddress, sameUrl, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, signHash, signUserOpTypedDataForDelegation, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, verifyPaymentReceipt, verifySignature, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses };
4133
4314
  //# sourceMappingURL=index.js.map
4134
4315
  //# sourceMappingURL=index.js.map