@haven_ai/signer 0.1.14-alpha.0 → 0.1.16-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/cli.cjs CHANGED
@@ -46,6 +46,22 @@ function stableStringify(value) {
46
46
  const object = value;
47
47
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
48
48
  }
49
+ var sweepAuthorizationSchema = v3.z.object({
50
+ from: v3.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "from must be a 0x address"),
51
+ to: v3.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "to must be a 0x address"),
52
+ value: v3.z.string().regex(/^[0-9]+$/, "value must be a decimal atomic amount"),
53
+ validAfter: v3.z.string().regex(/^[0-9]+$/, "validAfter must be a decimal unix time"),
54
+ validBefore: v3.z.string().regex(/^[0-9]+$/, "validBefore must be a decimal unix time"),
55
+ nonce: v3.z.string().regex(/^0x[0-9a-fA-F]{64}$/, "nonce must be a 0x-prefixed 32-byte hex string"),
56
+ token: v3.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "token must be a 0x address"),
57
+ chainId: v3.z.number().int().positive()
58
+ });
59
+ var sweepExpectedAuthSchema = v3.z.object({
60
+ version: v3.z.literal(1),
61
+ message: v3.z.string().min(1),
62
+ signature: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string"),
63
+ signer: v3.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "signer must be a 0x address")
64
+ });
49
65
  var x402ExpectedSchema = v3.z.object({
50
66
  payment_id: v3.z.string().min(1),
51
67
  payload_hash: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string"),
@@ -54,6 +70,11 @@ var x402ExpectedSchema = v3.z.object({
54
70
  amount: v3.z.string().min(1),
55
71
  asset: v3.z.string().min(1),
56
72
  network: v3.z.string().min(1),
73
+ // Required: expires_at is folded into the Haven-signed binding message, so the
74
+ // signer must receive the exact same value to reconstruct a matching message.
75
+ // Omitting it used to fail downstream with a cryptic "authentication message is
76
+ // invalid" — making it required surfaces a clear INVALID_INPUT at the boundary.
77
+ expires_at: v3.z.string().min(1),
57
78
  auth: v3.z.object({
58
79
  version: v3.z.literal(1),
59
80
  message: v3.z.string().min(1),
@@ -62,11 +83,18 @@ var x402ExpectedSchema = v3.z.object({
62
83
  })
63
84
  });
64
85
  var toolSchemas = {
65
- haven_sweep_delegate: {},
86
+ haven_sign_sweep_delegate: {
87
+ // The authorization fields prepared by Haven's POST /sweep/prepare. Passed
88
+ // through verbatim from the hosted haven_sweep_delegate tool — the signer
89
+ // re-derives the binding message from these exact values.
90
+ authorization: sweepAuthorizationSchema,
91
+ // Haven's signature over the authorization context (the binding).
92
+ expected_auth: sweepExpectedAuthSchema
93
+ },
66
94
  haven_sign: {
67
- // The unsigned hash from haven_pay / haven_x402_authorize (payload_hash).
95
+ // The unsigned hash from haven_pay / haven_pay_x402_quote (payload_hash).
68
96
  payload_hash: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string"),
69
- // Pass x402.expected from hosted haven_x402_authorize when this hash funds
97
+ // Pass x402.expected from hosted haven_pay_x402_quote when this hash funds
70
98
  // a standard x402 merchant retry. The signer records it locally and returns
71
99
  // an opaque x402_binding for the later header-signing step.
72
100
  x402_expected: x402ExpectedSchema.optional()
@@ -78,67 +106,79 @@ var toolSchemas = {
78
106
  payment_required: v3.z.record(v3.z.string(), v3.z.unknown()),
79
107
  // Opaque binding returned by haven_sign when x402_expected was supplied.
80
108
  x402_binding: v3.z.string().min(1)
109
+ },
110
+ haven_sign_x402: {
111
+ // One-shot x402 signing: funding hash + merchant header in one local call.
112
+ payload_hash: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string"),
113
+ x402_expected: x402ExpectedSchema,
114
+ payment_required: v3.z.record(v3.z.string(), v3.z.unknown())
81
115
  }
82
116
  };
83
117
  var SIGN_DESCRIPTION = [
84
118
  "Sign an unsigned Haven payment hash with the local delegate key. The delegate key never leaves",
85
119
  "this process. Pass the payload_hash returned by haven_pay or haven_pay_x402_quote.",
86
120
  "For x402, also pass x402_expected from haven_pay_x402_quote; the signer records it locally",
87
- "and returns { signature, x402_binding }. Hand signature to haven_submit, then pass x402_binding",
88
- "to haven_x402_sign_header. For plain SafeTransfer payments, just pass payload_hash and",
89
- "relay the returned signature via haven_submit."
121
+ "and returns { signature, x402_binding }. x402_expected includes expires_at; sign before that",
122
+ "window closes. Next: call mcp__haven__haven_submit with signature, then pass x402_binding",
123
+ "to mcp__haven-signer__haven_x402_sign_header. For plain SafeTransfer payments, just pass payload_hash and",
124
+ "relay the returned signature via mcp__haven__haven_submit."
90
125
  ].join(" ");
91
126
  var X402_SIGN_HEADER_DESCRIPTION = [
92
127
  "Build and sign the EIP-3009 X-PAYMENT header for the merchant leg of an x402 payment.",
93
128
  "The delegate key stays local \u2014 only the signed header crosses any boundary.",
94
129
  "Pass the payment_required from the original merchant 402 response and the x402_binding",
95
130
  "returned by haven_sign. The signer validates the merchant, amount, resource, asset, and",
96
- "network against the recorded funding context before signing, and rejects mismatches.",
131
+ "network against the recorded funding context before signing, checks expires_at when present,",
132
+ "and rejects mismatches or expired payment windows.",
97
133
  "Returns { payment_header, accepted }. Set X-PAYMENT: <payment_header> on your retry to the",
98
134
  "merchant. Only call after haven_submit has confirmed the funding step (nextAction=none or",
99
- "the funding tx has a confirmed status)."
135
+ "the funding tx has a confirmed status). Next for paid MCP tools: call mcp__haven__haven_complete_mcp_tool."
136
+ ].join(" ");
137
+ var SIGN_X402_DESCRIPTION = [
138
+ "One-shot x402 signing for the fast 3-call flow: sign the funding hash AND build the EIP-3009",
139
+ "X-PAYMENT header in a single local call (equivalent to haven_sign followed by",
140
+ "haven_x402_sign_header). The delegate key never leaves this process. From the haven_pay_mcp_tool",
141
+ "result pass payload_hash, x402_expected (the nested x402.expected object \u2014 passing the whole x402",
142
+ "object is also accepted and unwrapped for you), and payment_required (verbatim). Returns",
143
+ "{ signature, x402_binding, payment_header, accepted }; hand signature + payment_header to",
144
+ "mcp__haven__haven_settle_mcp_tool to fund and settle in one hosted call. The header is built now (before",
145
+ "funding confirms), so its short validity window starts here \u2014 call mcp__haven__haven_settle_mcp_tool promptly,",
146
+ "and re-run mcp__haven__haven_pay_mcp_tool with the same idempotency_key if a tool returns PAYMENT_WINDOW_EXPIRED.",
147
+ "Next: call mcp__haven__haven_settle_mcp_tool."
100
148
  ].join(" ");
101
- var SWEEP_DELEGATE_DESCRIPTION = [
102
- "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating Safe.",
103
- "Use when a payment failed or expired after funding, leaving funds on the delegate EOA.",
104
- "Signs ERC-20 transfer and native ETH transfer transactions locally with the delegate key.",
105
- "Only supported on Base (chainId 8453). Returns tx hashes and recovered amounts.",
106
- "Returns an empty transfers list when nothing is stranded."
149
+ var SIGN_SWEEP_DELEGATE_DESCRIPTION = [
150
+ "Sign a Haven-prepared gasless USDC sweep that recovers stranded funds from the delegate",
151
+ "wallet back to your Haven wallet. The delegate key never leaves this process and this tool",
152
+ "never broadcasts \u2014 it returns only an EIP-3009 signature that Haven's relayer submits and",
153
+ "pays gas for. Pass the authorization and expected_auth returned by the hosted",
154
+ "haven_sweep_delegate tool. The signer verifies Haven authored the authorization and that it",
155
+ "pays out to your own Safe before signing, then returns { signature } to hand back to",
156
+ "mcp__haven__haven_sweep_delegate to complete recovery."
107
157
  ].join(" ");
108
158
  var toolDescriptions = {
109
- haven_sweep_delegate: SWEEP_DELEGATE_DESCRIPTION,
110
159
  haven_sign: SIGN_DESCRIPTION,
111
- haven_x402_sign_header: X402_SIGN_HEADER_DESCRIPTION
160
+ haven_x402_sign_header: X402_SIGN_HEADER_DESCRIPTION,
161
+ haven_sign_x402: SIGN_X402_DESCRIPTION,
162
+ haven_sign_sweep_delegate: SIGN_SWEEP_DELEGATE_DESCRIPTION
112
163
  };
164
+ function toExpectedX402(raw) {
165
+ return {
166
+ paymentId: raw.payment_id,
167
+ payloadHash: raw.payload_hash,
168
+ resourceUrl: raw.resource_url,
169
+ merchantTo: raw.merchant_to,
170
+ amount: raw.amount,
171
+ asset: raw.asset,
172
+ network: raw.network,
173
+ expiresAt: raw.expires_at,
174
+ auth: raw.auth
175
+ };
176
+ }
113
177
  function createToolHandlers(signer, options = {}) {
114
178
  return {
115
- haven_sweep_delegate: async () => runTool(async () => {
116
- const safeAddress = options.audit?.safeAddress;
117
- const chainId = options.audit?.chainId;
118
- if (!safeAddress) {
119
- throw new Error(
120
- "Sweep requires a Safe address \u2014 ensure credentials include safe_address."
121
- );
122
- }
123
- if (typeof chainId !== "number") {
124
- throw new Error(
125
- "Sweep requires a chainId \u2014 ensure credentials include chain_id."
126
- );
127
- }
128
- return signer.sweepDelegate({ safeAddress, chainId });
129
- }),
130
179
  haven_sign: async (input) => runTool(async () => {
131
- const args = parse("haven_sign", input);
132
- const x402Expected = args.x402_expected ? {
133
- paymentId: args.x402_expected.payment_id,
134
- payloadHash: args.x402_expected.payload_hash,
135
- resourceUrl: args.x402_expected.resource_url,
136
- merchantTo: args.x402_expected.merchant_to,
137
- amount: args.x402_expected.amount,
138
- asset: args.x402_expected.asset,
139
- network: args.x402_expected.network,
140
- auth: args.x402_expected.auth
141
- } : null;
180
+ const args = parse("haven_sign", coerceX402Expected(input));
181
+ const x402Expected = args.x402_expected ? toExpectedX402(args.x402_expected) : null;
142
182
  const result = x402Expected ? signer.signX402FundingHash(args.payload_hash, x402Expected) : null;
143
183
  if (!result) {
144
184
  const signature = signer.signPaymentHash(args.payload_hash);
@@ -159,6 +199,36 @@ function createToolHandlers(signer, options = {}) {
159
199
  hashPayloadForAudit(args.payment_required)
160
200
  );
161
201
  return { payment_header: result.paymentHeader, accepted: result.accepted };
202
+ }),
203
+ haven_sign_x402: async (input) => runTool(async () => {
204
+ const args = parse("haven_sign_x402", coerceX402Expected(coercePaymentRequired(input)));
205
+ const funding = signer.signX402FundingHash(args.payload_hash, toExpectedX402(args.x402_expected));
206
+ const header = await signer.buildX402PaymentHeader(
207
+ args.payment_required,
208
+ funding.x402Binding
209
+ );
210
+ await auditSigning("haven_sign_x402", args.payload_hash);
211
+ await auditSigning("haven_sign_x402", hashPayloadForAudit(args.payment_required));
212
+ return {
213
+ signature: funding.signature,
214
+ x402_binding: funding.x402Binding,
215
+ payment_header: header.paymentHeader,
216
+ accepted: header.accepted
217
+ };
218
+ }),
219
+ haven_sign_sweep_delegate: async (input) => runTool(async () => {
220
+ const args = parse("haven_sign_sweep_delegate", input);
221
+ const result = await signer.signSweepAuthorization({
222
+ authorization: args.authorization,
223
+ expectedAuth: args.expected_auth,
224
+ // Cross-check `to` against the Safe in the local credential when present.
225
+ expectedSafe: options.audit?.safeAddress
226
+ });
227
+ await auditSigning(
228
+ "haven_sign_sweep_delegate",
229
+ hashPayloadForAudit(args.authorization)
230
+ );
231
+ return { signature: result.signature };
162
232
  })
163
233
  };
164
234
  async function auditSigning(tool, payloadHash) {
@@ -176,6 +246,25 @@ function createToolHandlers(signer, options = {}) {
176
246
  function parse(name, input) {
177
247
  return v3.z.object(toolSchemas[name]).parse(input ?? {});
178
248
  }
249
+ function coerceX402Expected(input) {
250
+ if (!input || typeof input !== "object") return input;
251
+ const record = input;
252
+ let value = record.x402_expected;
253
+ if (typeof value === "string") {
254
+ try {
255
+ value = JSON.parse(value);
256
+ } catch {
257
+ return input;
258
+ }
259
+ }
260
+ if (!value || typeof value !== "object") return input;
261
+ const obj = value;
262
+ if (obj.auth === void 0 && obj.expected && typeof obj.expected === "object") {
263
+ return { ...record, x402_expected: obj.expected };
264
+ }
265
+ if (value !== record.x402_expected) return { ...record, x402_expected: value };
266
+ return input;
267
+ }
179
268
  function coercePaymentRequired(input) {
180
269
  if (!input || typeof input !== "object") return input;
181
270
  const record = input;
@@ -209,7 +298,18 @@ function normalizeError(err) {
209
298
  return { success: false, code: err.code, message: err.message, statusCode: err.statusCode };
210
299
  }
211
300
  if (err instanceof sdk.HavenError) {
212
- return { success: false, code: err.code, message: err.message, statusCode: err.statusCode };
301
+ return {
302
+ success: false,
303
+ code: err.code,
304
+ message: err.message,
305
+ statusCode: err.statusCode,
306
+ paymentId: err.paymentId,
307
+ ...err.code === sdk.AgentPaymentFailureCode.PaymentWindowExpired ? {
308
+ next_action: sdk.AgentPaymentNextAction.PaymentWindowExpired,
309
+ retry_with_new_quote: true,
310
+ suggested_tool: "haven_pay_mcp_tool"
311
+ } : {}
312
+ };
213
313
  }
214
314
  return {
215
315
  success: false,
@@ -331,42 +431,6 @@ async function writeAckFile(path$1, hash) {
331
431
  "utf8"
332
432
  );
333
433
  }
334
- var BASE_CHAIN_ID = 8453;
335
- var BASE_RPC_URL = "https://mainnet.base.org";
336
- var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
337
- var BASE_EXPLORER_TX = "https://basescan.org/tx";
338
- var BASE_CHAIN = {
339
- id: BASE_CHAIN_ID,
340
- name: "Base",
341
- nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
342
- rpcUrls: { default: { http: [BASE_RPC_URL] } }
343
- };
344
- var ERC20_ABI = [
345
- {
346
- name: "balanceOf",
347
- type: "function",
348
- inputs: [{ name: "account", type: "address" }],
349
- outputs: [{ name: "", type: "uint256" }],
350
- stateMutability: "view"
351
- },
352
- {
353
- name: "transfer",
354
- type: "function",
355
- inputs: [
356
- { name: "to", type: "address" },
357
- { name: "amount", type: "uint256" }
358
- ],
359
- outputs: [{ name: "", type: "bool" }],
360
- stateMutability: "nonpayable"
361
- }
362
- ];
363
- function formatTokenAmount(atomic, decimals) {
364
- const divisor = 10n ** BigInt(decimals);
365
- const whole = atomic / divisor;
366
- const frac = atomic % divisor;
367
- if (frac === 0n) return whole.toString();
368
- return `${whole}.${frac.toString().padStart(decimals, "0").replace(/0+$/, "")}`;
369
- }
370
434
  function createEdgeSigner(delegateKey, options = {}) {
371
435
  let delegateAddress;
372
436
  try {
@@ -405,6 +469,12 @@ function createEdgeSigner(delegateKey, options = {}) {
405
469
  "x402 funding binding is required before signing a merchant header. Sign the hosted funding hash with x402_expected first."
406
470
  );
407
471
  }
472
+ try {
473
+ assertX402PaymentWindowOpen(expected);
474
+ } catch (err) {
475
+ x402Bindings.delete(x402Binding);
476
+ throw err;
477
+ }
408
478
  const option = sdk.selectStandardPaymentOption(paymentRequired.accepts);
409
479
  if (!option) {
410
480
  throw new sdk.HavenApiError(
@@ -436,64 +506,69 @@ function createEdgeSigner(delegateKey, options = {}) {
436
506
  x402Bindings.delete(x402Binding);
437
507
  }
438
508
  },
439
- async sweepDelegate({ safeAddress, chainId }) {
440
- if (chainId !== BASE_CHAIN_ID) {
441
- throw new sdk.HavenApiError(
442
- `Sweep is only supported on Base (chainId ${BASE_CHAIN_ID}). Got chainId ${chainId}.`,
443
- 422
509
+ async signSweepAuthorization({
510
+ authorization,
511
+ expectedAuth,
512
+ expectedSafe
513
+ }) {
514
+ assertSweepBinding(authorization, expectedAuth, options.x402BindingSigner);
515
+ if (!sameAddress(authorization.from, delegateAddress)) {
516
+ throw new sdk.HavenSigningError(
517
+ "Sweep authorization `from` does not match this delegate address."
444
518
  );
445
519
  }
446
- const account = accounts.privateKeyToAccount(delegateKey);
447
- const publicClient = viem.createPublicClient({ chain: BASE_CHAIN, transport: viem.http(BASE_RPC_URL) });
448
- const walletClient = viem.createWalletClient({ account, chain: BASE_CHAIN, transport: viem.http(BASE_RPC_URL) });
449
- const transfers = [];
450
- const usdcBalance = await publicClient.readContract({
451
- address: BASE_USDC_ADDRESS,
452
- abi: ERC20_ABI,
453
- functionName: "balanceOf",
454
- args: [account.address]
455
- });
456
- if (usdcBalance > 0n) {
457
- const hash = await walletClient.writeContract({
458
- address: BASE_USDC_ADDRESS,
459
- abi: ERC20_ABI,
460
- functionName: "transfer",
461
- args: [safeAddress, usdcBalance]
462
- });
463
- await publicClient.waitForTransactionReceipt({ hash });
464
- transfers.push({
465
- asset: "USDC",
466
- amount: formatTokenAmount(usdcBalance, 6),
467
- amountAtomic: usdcBalance.toString(),
468
- txHash: hash,
469
- explorerUrl: `${BASE_EXPLORER_TX}/${hash}`
470
- });
520
+ if (expectedSafe && !sameAddress(authorization.to, expectedSafe)) {
521
+ throw new sdk.HavenSigningError(
522
+ "Sweep authorization `to` does not match the Safe in the local credential."
523
+ );
471
524
  }
472
- const ethBalance = await publicClient.getBalance({ address: account.address });
473
- if (ethBalance > 0n) {
474
- const gasPrice = await publicClient.getGasPrice() ?? 1000000n;
475
- const gasLimit = 21000n;
476
- const gasCost = gasPrice * gasLimit;
477
- const toSend = ethBalance > gasCost ? ethBalance - gasCost : 0n;
478
- if (toSend > 0n) {
479
- const hash = await walletClient.sendTransaction({
480
- to: safeAddress,
481
- value: toSend
482
- });
483
- await publicClient.waitForTransactionReceipt({ hash });
484
- transfers.push({
485
- asset: "ETH",
486
- amount: formatTokenAmount(toSend, 18),
487
- amountAtomic: toSend.toString(),
488
- txHash: hash,
489
- explorerUrl: `${BASE_EXPLORER_TX}/${hash}`
490
- });
525
+ const typedData = sdk.buildSweepTypedData(authorization);
526
+ const viemTypedData = {
527
+ domain: {
528
+ ...typedData.domain,
529
+ verifyingContract: typedData.domain.verifyingContract
530
+ },
531
+ types: typedData.types,
532
+ primaryType: typedData.primaryType,
533
+ message: {
534
+ ...typedData.message,
535
+ from: typedData.message.from,
536
+ to: typedData.message.to,
537
+ nonce: typedData.message.nonce
491
538
  }
539
+ };
540
+ const account = accounts.privateKeyToAccount(delegateKey);
541
+ const signature = await account.signTypedData(viemTypedData);
542
+ const recovered = await viem.recoverTypedDataAddress({ ...viemTypedData, signature });
543
+ if (!sameAddress(recovered, delegateAddress)) {
544
+ throw new sdk.HavenSigningError(
545
+ "Local sweep signature verification failed \u2014 recovered address does not match the delegate key."
546
+ );
492
547
  }
493
- return { fromAddress: account.address, toAddress: safeAddress, chainId, transfers };
548
+ return { signature };
494
549
  }
495
550
  };
496
551
  }
552
+ function assertSweepBinding(authorization, expectedAuth, trustedSigner) {
553
+ if (!expectedAuth || typeof expectedAuth !== "object") {
554
+ throw new sdk.HavenSigningError("Sweep authorization binding is required before signing.");
555
+ }
556
+ if (!trustedSigner) {
557
+ throw new sdk.HavenSigningError(
558
+ "Sweep binding verifier is not configured. Set HAVEN_X402_BINDING_SIGNER before signing sweep authorizations."
559
+ );
560
+ }
561
+ const message = sdk.buildSweepAuthorizationMessage(authorization);
562
+ if (expectedAuth.version !== 1 || expectedAuth.message !== message) {
563
+ throw new sdk.HavenSigningError("Sweep authorization binding does not match the authorization being signed.");
564
+ }
565
+ if (!sameAddress(expectedAuth.signer, trustedSigner)) {
566
+ throw new sdk.HavenSigningError("Sweep authorization binding was not signed by the configured Haven signer.");
567
+ }
568
+ if (!sdk.verifySignature(viem.hashMessage(message), expectedAuth.signature, trustedSigner)) {
569
+ throw new sdk.HavenSigningError("Sweep authorization binding signature could not be verified.");
570
+ }
571
+ }
497
572
  function assertX402MatchesExpected(paymentRequired, option, expected) {
498
573
  assertExpectedShape(expected);
499
574
  const headerResource = option.resource ?? paymentRequired.resource.url;
@@ -535,7 +610,8 @@ function assertExpectedBinding(payloadHash, expected, trustedSigner) {
535
610
  merchantTo: expected.merchantTo,
536
611
  amount: expected.amount,
537
612
  asset: expected.asset,
538
- network: expected.network
613
+ network: expected.network,
614
+ expiresAt: expected.expiresAt
539
615
  });
540
616
  if (expected.auth?.version !== 1 || expected.auth.message !== message) {
541
617
  throw new sdk.HavenSigningError("x402 expected context authentication message is invalid.");
@@ -547,6 +623,21 @@ function assertExpectedBinding(payloadHash, expected, trustedSigner) {
547
623
  throw new sdk.HavenSigningError("x402 expected context signature could not be verified.");
548
624
  }
549
625
  }
626
+ function assertX402PaymentWindowOpen(expected) {
627
+ if (!expected.expiresAt) return;
628
+ const expiresAtMs = Date.parse(expected.expiresAt);
629
+ if (Number.isNaN(expiresAtMs)) {
630
+ throw new sdk.HavenSigningError("x402 expected context expiresAt is not a valid ISO timestamp.");
631
+ }
632
+ if (expiresAtMs <= Date.now()) {
633
+ throw new sdk.HavenError(
634
+ "The x402 payment window expired before the merchant header could be signed. Re-quote with haven_pay_mcp_tool using the same idempotency_key before trying again.",
635
+ sdk.AgentPaymentFailureCode.PaymentWindowExpired,
636
+ 410,
637
+ expected.paymentId
638
+ );
639
+ }
640
+ }
550
641
  function sameAddress(a, b) {
551
642
  return a.toLowerCase() === b.toLowerCase();
552
643
  }
@@ -640,7 +731,7 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
640
731
 
641
732
  // src/server.ts
642
733
  var SIGNER_NAME = "@haven_ai/signer";
643
- var SIGNER_VERSION = "0.1.14-alpha.0";
734
+ var SIGNER_VERSION = "0.1.16-alpha.0";
644
735
  async function resolveSignerRuntime(options = {}) {
645
736
  if (options.delegateKey) {
646
737
  return {