@dvmkit/sdk 0.6.0-rc.1 → 0.7.0-rc.1

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.
Files changed (40) hide show
  1. package/dist/{chunk-JH3SX3LY.js → chunk-6XF3RVVX.js} +1 -1
  2. package/dist/{chunk-RLTSCKRI.js → chunk-BPYMEMQX.js} +50 -3
  3. package/dist/{chunk-IWQHUSMO.js → chunk-CXCQLOOK.js} +143 -2
  4. package/dist/{chunk-LE74UEV7.js → chunk-EZXAJ6EI.js} +64 -53
  5. package/dist/{chunk-FI4CST2D.js → chunk-FWGWLPXF.js} +4 -6
  6. package/dist/{chunk-HHBDBMEO.js → chunk-GY66THYI.js} +21 -23
  7. package/dist/{chunk-UOCZXO2Z.js → chunk-JEVLNAO3.js} +10 -5
  8. package/dist/{chunk-OQA7IHI4.js → chunk-KMED3H3A.js} +10 -6
  9. package/dist/{chunk-PAIM66YA.js → chunk-NFMOPYBA.js} +47 -37
  10. package/dist/{chunk-B2IROPQF.js → chunk-O2OTZ5GP.js} +40 -15
  11. package/dist/{chunk-FYRR2UEZ.js → chunk-OML32F6I.js} +1 -1
  12. package/dist/{chunk-7ZUKL4IV.js → chunk-RA2WIX7G.js} +5 -5
  13. package/dist/{chunk-PLG4XER6.js → chunk-S3OY533W.js} +1 -1
  14. package/dist/{chunk-GFY5FRX3.js → chunk-SZVXOQ75.js} +5 -12
  15. package/dist/{chunk-MZRJR2CW.js → chunk-WBOKPC37.js} +22 -3
  16. package/dist/{content-retention-BTGSQRfP.d.ts → content-retention-DHyLdA6b.d.ts} +1 -1
  17. package/dist/{credit-ledger-MI2H7NEC.js → credit-ledger-MCUXYKSJ.js} +2 -3
  18. package/dist/{credit-menu-BAsrRu3z.d.ts → credit-menu-DjnkeEUU.d.ts} +4 -4
  19. package/dist/{fx-BgDmyTdd.d.ts → fx-zMWFzfrU.d.ts} +1 -1
  20. package/dist/index.d.ts +5 -5
  21. package/dist/internal/caller.d.ts +1359 -851
  22. package/dist/internal/caller.js +1000 -914
  23. package/dist/internal/index.js +1 -1
  24. package/dist/internal/server.d.ts +7 -7
  25. package/dist/internal/server.js +9 -11
  26. package/dist/{job-store-BVeF5_Jq.d.ts → job-store-DdLXBpol.d.ts} +1 -1
  27. package/dist/{lightning-backend-40fIzz1W.d.ts → lightning-backend-Bw-qtAbt.d.ts} +4 -2
  28. package/dist/{memory-credit-ledger-W2BAUYJP.js → memory-credit-ledger-SBSMGMDX.js} +3 -4
  29. package/dist/server/index.d.ts +10 -10
  30. package/dist/server/index.js +14 -17
  31. package/dist/{step-cache-CPdH1hei.d.ts → step-cache-2MkvenJn.d.ts} +1 -1
  32. package/dist/tempo-lifecycle-GJ77YKVY.js +15 -0
  33. package/dist/{tempo-wallet-B5XXV3YH.js → tempo-wallet-X2GGAZ6X.js} +4 -4
  34. package/dist/testing/index.d.ts +3 -3
  35. package/dist/{usd-AWlrwlM5.d.ts → usd-DI3d3gTK.d.ts} +1 -1
  36. package/dist/{x402-R6HPBY5D.js → x402-FT4S4JS4.js} +8 -10
  37. package/package.json +3 -2
  38. package/dist/chunk-CZULM42S.js +0 -143
  39. package/dist/chunk-LIRAHP7B.js +0 -53
  40. package/dist/tempo-lifecycle-ZBBLFBMH.js +0 -15
@@ -8,6 +8,7 @@ import {
8
8
  assertTempoSessionVoucherWithinAsk,
9
9
  assertTempoWalletIdle,
10
10
  clearPendingTempoChargeFunding,
11
+ createTempoChannelStore,
11
12
  createTempoCloseCredential,
12
13
  createTempoCreditSession,
13
14
  createTempoSessionPaymentFetch,
@@ -22,6 +23,7 @@ import {
22
23
  loadIdentities,
23
24
  loadResumableTempoChargeFunding,
24
25
  loadTempoChannelAssociation,
26
+ loadTempoChannelAssociationByKey,
25
27
  loadTempoChannelController,
26
28
  loadTempoChannelEntry,
27
29
  nwcRejectionDisplay,
@@ -35,12 +37,15 @@ import {
35
37
  reconcileTempoChannelOnChain,
36
38
  recordPendingTempoChargeFunding,
37
39
  resolveSigningIdentity,
40
+ retireTempoChannel,
41
+ retireTempoChannelScope,
38
42
  rpcEndpointLine,
39
43
  rpcEndpointSuffix,
40
44
  saveIdentities,
41
45
  shellQuoteArg,
42
46
  tempoChannelControlledBy,
43
47
  tempoChargeCredentialSettleable,
48
+ tempoDescriptorController,
44
49
  tempoKeyImportRoutes,
45
50
  tempoSessionAssociationByKey,
46
51
  tempoSessionChallenge,
@@ -55,7 +60,7 @@ import {
55
60
  withIdentitiesLock,
56
61
  withTempoChannelLock,
57
62
  withholdsTempoCreditSession
58
- } from "../chunk-UOCZXO2Z.js";
63
+ } from "../chunk-JEVLNAO3.js";
59
64
  import {
60
65
  TEMPO_CHAIN_ID,
61
66
  TEMPO_MODERATO_CHAIN_ID,
@@ -71,13 +76,14 @@ import {
71
76
  tempoTokenReference,
72
77
  tempoWrongAssetHint,
73
78
  tryResolveTempoAsset
74
- } from "../chunk-GFY5FRX3.js";
79
+ } from "../chunk-SZVXOQ75.js";
75
80
  import {
76
81
  DvmX402ChannelStorage,
77
82
  X402_EXACT_AUTHORIZATION_TTL_SECONDS,
78
83
  X402_PENDING_STALE_MS,
79
84
  X402_REFUSAL_TRANSACTION_DISCOVERY_WINDOW_MS,
80
85
  acknowledgeX402ChannelOwner,
86
+ assertNoRivalX402Voucher,
81
87
  clearPendingX402ExactFunding,
82
88
  clearPendingX402Funding,
83
89
  connectedX402Payer,
@@ -121,8 +127,9 @@ import {
121
127
  x402PendingRetired,
122
128
  x402PendingStale,
123
129
  x402PendingUnsettled,
130
+ x402VoucherPendingState,
124
131
  x402WrongAssetHint
125
- } from "../chunk-LE74UEV7.js";
132
+ } from "../chunk-EZXAJ6EI.js";
126
133
  import {
127
134
  RAIL_FLAGS,
128
135
  SUPPORTED_PROTOCOL_VERSION,
@@ -164,7 +171,7 @@ import {
164
171
  recordRefusedDrawBalance,
165
172
  settleDrainEvidence,
166
173
  withinAdvertisedTolerance
167
- } from "../chunk-B2IROPQF.js";
174
+ } from "../chunk-O2OTZ5GP.js";
168
175
  import {
169
176
  CREDIT_POSTURES,
170
177
  FEEDBACK_POSTURES,
@@ -181,8 +188,10 @@ import {
181
188
  previewConfigRollback,
182
189
  retireConfigBackup,
183
190
  rollbackConfig,
184
- updateConfig
185
- } from "../chunk-MZRJR2CW.js";
191
+ tempoAccountFromConfig,
192
+ updateConfig,
193
+ x402WalletFromConfig
194
+ } from "../chunk-WBOKPC37.js";
186
195
  import {
187
196
  CAPABILITY_NAME_RE
188
197
  } from "../chunk-HCLVYZUZ.js";
@@ -210,7 +219,7 @@ import {
210
219
  sha256hex,
211
220
  validateX402Env,
212
221
  withNwcPool
213
- } from "../chunk-FI4CST2D.js";
222
+ } from "../chunk-FWGWLPXF.js";
214
223
  import {
215
224
  MemoryKVStore
216
225
  } from "../chunk-SDK6KDJN.js";
@@ -256,6 +265,7 @@ import {
256
265
  isX402SettlementReconciliationReason,
257
266
  loadAgentWallet,
258
267
  loadIdentitySecret,
268
+ loadWalletConfiguredMints,
259
269
  looksLikeMnemonic,
260
270
  meltAmountBounds,
261
271
  mnemonicFingerprint,
@@ -280,31 +290,7 @@ import {
280
290
  withWalletLock,
281
291
  writeAgentMnemonic,
282
292
  writeIdentity
283
- } from "../chunk-OQA7IHI4.js";
284
- import {
285
- X402_BATCH_SETTLEMENT_SCHEME,
286
- X402_DEFAULT_FACILITATOR,
287
- X402_DEFAULT_NETWORK,
288
- X402_EXACT_SCHEME,
289
- X402_V1_VERSION,
290
- X402_VERSION,
291
- buildPaymentRequiredV2,
292
- buildPaymentRequirements,
293
- caip2ToX402Network,
294
- chainIdFromCaip2,
295
- decodePayment,
296
- decodePaymentRequiredHeader,
297
- encodePayment,
298
- encodePaymentRequiredHeader,
299
- encodeSettleResponseHeader,
300
- exactEvmAuthorization,
301
- paymentRequiredV2FromV1,
302
- usdcContractByCaip2,
303
- usdcContractFor,
304
- usdcDomainNameFor,
305
- usdcDomainVersionFor,
306
- x402NetworkToCaip2
307
- } from "../chunk-IWQHUSMO.js";
293
+ } from "../chunk-KMED3H3A.js";
308
294
  import {
309
295
  COINGECKO_SIMPLE_PRICE_URL,
310
296
  assertValidBudget,
@@ -324,7 +310,9 @@ import {
324
310
  isDrainReceipt,
325
311
  isFundingReceipt,
326
312
  isSignedJobReceipt,
313
+ microToSats,
327
314
  monetaryJson,
315
+ msatsToMicro,
328
316
  msatsToUsd,
329
317
  msatsToUsdCents,
330
318
  msatsToUsdc,
@@ -337,7 +325,7 @@ import {
337
325
  verifyDrainReceipt,
338
326
  verifyFundingReceipt,
339
327
  verifyReceipt
340
- } from "../chunk-PAIM66YA.js";
328
+ } from "../chunk-NFMOPYBA.js";
341
329
  import {
342
330
  AGENT_MNEMONIC_FILE,
343
331
  BTC_RATE_FILE,
@@ -359,13 +347,35 @@ import {
359
347
  REQUEST_INTENTS_DIR,
360
348
  TEMPO_CHANNELS_FILE,
361
349
  WALLET_FILE,
350
+ X402_BATCH_SETTLEMENT_SCHEME,
362
351
  X402_CHANNELS_FILE,
352
+ X402_DEFAULT_FACILITATOR,
353
+ X402_DEFAULT_NETWORK,
354
+ X402_EXACT_SCHEME,
355
+ X402_V1_VERSION,
356
+ X402_VERSION,
357
+ buildPaymentRequiredV2,
358
+ buildPaymentRequirements,
359
+ caip2ToX402Network,
360
+ chainIdFromCaip2,
361
+ decodePayment,
362
+ decodePaymentRequiredHeader,
363
+ encodePayment,
364
+ encodePaymentRequiredHeader,
365
+ encodeSettleResponseHeader,
363
366
  ensureConfigDir,
364
367
  ensureConfigSubdir,
368
+ exactEvmAuthorization,
369
+ paymentRequiredV2FromV1,
365
370
  repairConfigDirMode,
366
371
  resolveRpcHttpTransportOptions,
367
- resolveRpcOverride
368
- } from "../chunk-CZULM42S.js";
372
+ resolveRpcOverride,
373
+ usdcContractByCaip2,
374
+ usdcContractFor,
375
+ usdcDomainNameFor,
376
+ usdcDomainVersionFor,
377
+ x402NetworkToCaip2
378
+ } from "../chunk-CXCQLOOK.js";
369
379
  import {
370
380
  RAIL_REFUNDABLE,
371
381
  SIGNED_REQUEST_AUTH_ID,
@@ -412,20 +422,18 @@ import {
412
422
  withInitRetry
413
423
  } from "../chunk-P6QON7ER.js";
414
424
  import {
425
+ DvmError,
415
426
  X402_NETWORKS,
416
427
  assertSupportedX402Network,
428
+ isAbortError,
429
+ providerProseSources,
430
+ transportErrorCode,
417
431
  x402NetworkByCaip2,
418
432
  x402NetworkBySlug,
419
433
  x402NetworkLabel,
420
434
  x402NetworkListForCopy,
421
435
  x402SupportedNetworksHint
422
- } from "../chunk-RLTSCKRI.js";
423
- import {
424
- DvmError,
425
- isAbortError,
426
- providerProseSources,
427
- transportErrorCode
428
- } from "../chunk-LIRAHP7B.js";
436
+ } from "../chunk-BPYMEMQX.js";
429
437
  import {
430
438
  DIAGNOSTIC_CATALOGUE_VERSION,
431
439
  bindDiagnosticCapabilityAuthority,
@@ -437,40 +445,9 @@ import {
437
445
  projectSpanRow
438
446
  } from "../chunk-ETXDTUJO.js";
439
447
 
440
- // src/lib/budget-ledger.ts
441
- import { existsSync, readFileSync, renameSync, writeFileSync } from "fs";
442
- function loadLedger() {
443
- if (!existsSync(BUDGET_FILE)) return { caps: {}, entries: [] };
444
- try {
445
- const data = JSON.parse(readFileSync(BUDGET_FILE, "utf-8"));
446
- return { caps: data.caps ?? {}, entries: data.entries ?? [] };
447
- } catch {
448
- return { caps: {}, entries: [] };
449
- }
450
- }
451
- function saveLedger(ledger, _now = /* @__PURE__ */ new Date()) {
452
- ensureConfigDir();
453
- const tmp = `${BUDGET_FILE}.tmp`;
454
- writeFileSync(tmp, JSON.stringify(ledger, null, 2) + "\n");
455
- renameSync(tmp, BUDGET_FILE);
456
- }
457
- function recordSpend(opts) {
458
- appendSpend(loadLedger(), opts);
459
- }
460
- function recordSpendOnce(opts) {
461
- const ledger = loadLedger();
462
- const kind = opts.kind ?? "job";
463
- if (ledger.entries.some(
464
- (entry) => entryKind(entry) === kind && entry.jobId === opts.jobId && entry.providerEndpoint === opts.providerEndpoint
465
- )) {
466
- return false;
467
- }
468
- appendSpend(ledger, opts);
469
- return true;
470
- }
471
- function checkBudget(opts) {
448
+ // src/lib/budget-core.ts
449
+ function checkBudgetForLedger(ledger, opts) {
472
450
  const now = opts.now ?? /* @__PURE__ */ new Date();
473
- const ledger = loadLedger();
474
451
  const plannedUsd = msatsToUsd(opts.plannedMsats, opts.btcRate);
475
452
  const windows = ["daily", "weekly", "monthly"];
476
453
  for (const window of windows) {
@@ -491,62 +468,6 @@ function checkBudget(opts) {
491
468
  }
492
469
  return { ok: true };
493
470
  }
494
- async function enforceGlobalBudget(opts) {
495
- const ledger = loadLedger();
496
- const hasCaps = ledger.caps.dailyUsd !== void 0 || ledger.caps.weeklyUsd !== void 0 || ledger.caps.monthlyUsd !== void 0;
497
- if (!hasCaps) {
498
- if (opts.btcRate !== void 0 && opts.btcRate > 0) return opts.btcRate;
499
- try {
500
- return (await resolveBtcUsdRate("payment")).rate;
501
- } catch {
502
- return 0;
503
- }
504
- }
505
- const rate = opts.btcRate && opts.btcRate > 0 ? opts.btcRate : (await resolveBtcUsdRate("payment")).rate;
506
- const check = checkBudget({ plannedMsats: opts.plannedMsats, btcRate: rate });
507
- if (!check.ok) {
508
- const windowName = check.window.charAt(0).toUpperCase() + check.window.slice(1);
509
- const suffix = check.window === "daily" ? "day" : check.window === "weekly" ? "week" : "month";
510
- const suggestedCap = Math.max(1, Math.ceil(check.limitUsd * 2));
511
- throw new DvmError(
512
- "budget_exceeded",
513
- `${windowName} budget would be exceeded by this payment.`,
514
- `${windowName} budget exhausted. The window resets at ${new Date(check.resetsAt).toISOString()}. Ask the user if they'd like to raise the cap (e.g. 'dvm budget set --${check.window} "$${suggestedCap}"' \u2014 substitute any USD amount).`,
515
- {
516
- window: check.window,
517
- spent: {
518
- display: `$${formatUsd(check.spentUsd)}`,
519
- // the cap counts both what the agent paid DVMs and what it
520
- // moved out of the Lightning float, so name the split rather than
521
- // leaving the user to wonder why the number is higher than their jobs.
522
- jobs: `$${formatUsd(check.breakdown.jobsUsd)}`,
523
- float_fundings: `$${formatUsd(check.breakdown.floatFundingsUsd)}`,
524
- credit_fundings: `$${formatUsd(check.breakdown.creditFundingsUsd)}`
525
- },
526
- limit: { display: `$${formatUsd(check.limitUsd)}/${suffix}` },
527
- resets_at: new Date(check.resetsAt).toISOString()
528
- }
529
- );
530
- }
531
- return rate;
532
- }
533
- function statusSnapshot(now = /* @__PURE__ */ new Date()) {
534
- const ledger = loadLedger();
535
- const windows = ["daily", "weekly", "monthly"];
536
- return {
537
- caps: ledger.caps,
538
- windows: windows.map((window) => {
539
- const breakdown = breakdownSince(ledger.entries, windowStart(window, now).getTime());
540
- return {
541
- window,
542
- spentUsd: breakdown.totalUsd,
543
- limitUsd: capFor(ledger.caps, window),
544
- resetsAt: windowEnd(window, now).getTime(),
545
- breakdown
546
- };
547
- })
548
- };
549
- }
550
471
  function entryKind(entry) {
551
472
  return entry.kind ?? "job";
552
473
  }
@@ -571,7 +492,7 @@ function windowEnd(window, now) {
571
492
  }
572
493
  return new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 1));
573
494
  }
574
- function appendSpend(ledger, opts) {
495
+ function spendEntry(opts) {
575
496
  const usd = msatsToUsd(opts.msats, opts.btcRate);
576
497
  const entry = {
577
498
  timestamp: (opts.now ?? /* @__PURE__ */ new Date()).getTime(),
@@ -591,8 +512,7 @@ function appendSpend(ledger, opts) {
591
512
  if (opts.walletDebitMsats !== void 0) entry.walletDebitMsats = opts.walletDebitMsats;
592
513
  if (opts.label !== void 0) entry.label = opts.label;
593
514
  if (opts.labelSource !== void 0) entry.labelSource = opts.labelSource;
594
- ledger.entries.push(entry);
595
- saveLedger(ledger);
515
+ return entry;
596
516
  }
597
517
  function capFor(caps, window) {
598
518
  if (window === "daily") return caps.dailyUsd;
@@ -617,6 +537,110 @@ function breakdownSince(entries, sinceMs) {
617
537
  return out;
618
538
  }
619
539
 
540
+ // src/lib/budget-store.ts
541
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
542
+ import { dirname } from "path";
543
+ function loadBudgetLedger(file) {
544
+ if (!existsSync(file)) return { caps: {}, entries: [] };
545
+ try {
546
+ const data = JSON.parse(readFileSync(file, "utf-8"));
547
+ return { caps: data.caps ?? {}, entries: data.entries ?? [] };
548
+ } catch {
549
+ return { caps: {}, entries: [] };
550
+ }
551
+ }
552
+ function saveBudgetLedger(file, ledger) {
553
+ mkdirSync(dirname(file), { recursive: true, mode: 448 });
554
+ const tmp = `${file}.tmp`;
555
+ writeFileSync(tmp, JSON.stringify(ledger, null, 2) + "\n");
556
+ renameSync(tmp, file);
557
+ }
558
+ function appendBudgetSpend(file, opts, once = false) {
559
+ const ledger = loadBudgetLedger(file);
560
+ const kind = opts.kind ?? "job";
561
+ if (once && ledger.entries.some(
562
+ (entry) => entryKind(entry) === kind && entry.jobId === opts.jobId && entry.providerEndpoint === opts.providerEndpoint
563
+ )) {
564
+ return false;
565
+ }
566
+ ledger.entries.push(spendEntry(opts));
567
+ saveBudgetLedger(file, ledger);
568
+ return true;
569
+ }
570
+
571
+ // src/lib/budget-ledger.ts
572
+ function loadLedger() {
573
+ return loadBudgetLedger(BUDGET_FILE);
574
+ }
575
+ function saveLedger(ledger, _now = /* @__PURE__ */ new Date()) {
576
+ saveBudgetLedger(BUDGET_FILE, ledger);
577
+ }
578
+ function recordSpend(opts) {
579
+ appendBudgetSpend(BUDGET_FILE, opts);
580
+ }
581
+ function recordSpendOnce(opts) {
582
+ return appendBudgetSpend(BUDGET_FILE, opts, true);
583
+ }
584
+ function checkBudget(opts) {
585
+ return checkBudgetForLedger(loadLedger(), opts);
586
+ }
587
+ async function enforceGlobalBudget(opts) {
588
+ const ledger = loadLedger();
589
+ const hasCaps = ledger.caps.dailyUsd !== void 0 || ledger.caps.weeklyUsd !== void 0 || ledger.caps.monthlyUsd !== void 0;
590
+ if (!hasCaps) {
591
+ if (opts.btcRate !== void 0 && opts.btcRate > 0) return opts.btcRate;
592
+ try {
593
+ return (await resolveBtcUsdRate("payment")).rate;
594
+ } catch {
595
+ return 0;
596
+ }
597
+ }
598
+ const rate = opts.btcRate && opts.btcRate > 0 ? opts.btcRate : (await resolveBtcUsdRate("payment")).rate;
599
+ const check = checkBudget({ plannedMsats: opts.plannedMsats, btcRate: rate });
600
+ if (!check.ok) {
601
+ const windowName = check.window.charAt(0).toUpperCase() + check.window.slice(1);
602
+ const suffix = check.window === "daily" ? "day" : check.window === "weekly" ? "week" : "month";
603
+ const suggestedCap = Math.max(1, Math.ceil(check.limitUsd * 2));
604
+ throw new DvmError(
605
+ "budget_exceeded",
606
+ `${windowName} budget would be exceeded by this payment.`,
607
+ `${windowName} budget exhausted. The window resets at ${new Date(check.resetsAt).toISOString()}. Ask the user if they'd like to raise the cap (e.g. 'dvm budget set --${check.window} "$${suggestedCap}"' \u2014 substitute any USD amount).`,
608
+ {
609
+ window: check.window,
610
+ spent: {
611
+ display: `$${formatUsd(check.spentUsd)}`,
612
+ // the cap counts both what the agent paid DVMs and what it
613
+ // moved out of the Lightning float, so name the split rather than
614
+ // leaving the user to wonder why the number is higher than their jobs.
615
+ jobs: `$${formatUsd(check.breakdown.jobsUsd)}`,
616
+ float_fundings: `$${formatUsd(check.breakdown.floatFundingsUsd)}`,
617
+ credit_fundings: `$${formatUsd(check.breakdown.creditFundingsUsd)}`
618
+ },
619
+ limit: { display: `$${formatUsd(check.limitUsd)}/${suffix}` },
620
+ resets_at: new Date(check.resetsAt).toISOString()
621
+ }
622
+ );
623
+ }
624
+ return rate;
625
+ }
626
+ function statusSnapshot(now = /* @__PURE__ */ new Date()) {
627
+ const ledger = loadLedger();
628
+ const windows = ["daily", "weekly", "monthly"];
629
+ return {
630
+ caps: ledger.caps,
631
+ windows: windows.map((window) => {
632
+ const breakdown = breakdownSince(ledger.entries, windowStart(window, now).getTime());
633
+ return {
634
+ window,
635
+ spentUsd: breakdown.totalUsd,
636
+ limitUsd: capFor(ledger.caps, window),
637
+ resetsAt: windowEnd(window, now).getTime(),
638
+ breakdown
639
+ };
640
+ })
641
+ };
642
+ }
643
+
620
644
  // src/lib/cashu/builder-subkey.ts
621
645
  import { createHash } from "crypto";
622
646
  import { HDKey } from "@scure/bip32";
@@ -781,7 +805,7 @@ function randomNonce() {
781
805
  }
782
806
 
783
807
  // src/lib/credit-fund.ts
784
- import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
808
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
785
809
  import { Challenge, Credential } from "mppx";
786
810
  import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
787
811
 
@@ -3300,631 +3324,208 @@ function deriveAgentRefundSubkeyPubkey(mnemonic, canonicalDvmId) {
3300
3324
  return deriveAgentRefundSubkey(mnemonic, canonicalDvmId).pubkey;
3301
3325
  }
3302
3326
 
3303
- // src/lib/credit-policy.ts
3304
- import { randomUUID } from "crypto";
3305
-
3306
- // src/lib/x402/menu.ts
3307
- function offersX402CreditFunding(menu, network) {
3308
- return matchesNetwork(x402CreditFundingNetworks(menu), network);
3327
+ // src/lib/credit-funding-contracts.ts
3328
+ var CREDIT_FUND_RAILS = ["cashu", "lightning", "x402", "tempo"];
3329
+ var CREDIT_DRAIN_RAILS = ["cashu", "x402", "tempo"];
3330
+ function isCreditFundRail(value) {
3331
+ return typeof value === "string" && CREDIT_FUND_RAILS.includes(value);
3309
3332
  }
3310
- function offersX402CreditChannel(menu, network) {
3311
- return matchesNetwork(x402CreditChannelNetworks(menu), network);
3333
+ var STABLECOIN_FUND_RAILS = ["x402", "tempo"];
3334
+ function isStablecoinFundRail(rail) {
3335
+ return STABLECOIN_FUND_RAILS.includes(rail);
3312
3336
  }
3313
- function x402CreditFlavour(menu, network) {
3314
- if (offersX402CreditChannel(menu, network)) return X402_BATCH_SETTLEMENT_SCHEME;
3315
- if (matchesNetwork(x402CreditExactNetworks(menu), network)) return X402_EXACT_SCHEME;
3316
- return void 0;
3337
+ var CREDIT_FUNDING_MODES = ["one-shot", "reusable"];
3338
+ function isCreditFundingMode(value) {
3339
+ return typeof value === "string" && CREDIT_FUNDING_MODES.includes(value);
3317
3340
  }
3318
- function offersX402CreditFlavour(menu, network, flavour) {
3319
- return matchesNetwork(
3320
- flavour === X402_BATCH_SETTLEMENT_SCHEME ? x402CreditChannelNetworks(menu) : x402CreditExactNetworks(menu),
3321
- network
3322
- );
3341
+ function fundingModeForTempoIntent(intent) {
3342
+ return intent === "session" ? "reusable" : "one-shot";
3323
3343
  }
3324
- function x402InstrumentForFlavour(flavour) {
3325
- return flavour === X402_BATCH_SETTLEMENT_SCHEME ? "x402_channel" : "x402_exact";
3344
+ function fundingModeForX402Flavour(flavour) {
3345
+ return flavour === X402_BATCH_SETTLEMENT_SCHEME ? "reusable" : "one-shot";
3326
3346
  }
3327
- function x402FlavourForInstrument(instrument) {
3328
- if (instrument === "x402_channel") return X402_BATCH_SETTLEMENT_SCHEME;
3329
- if (instrument === "x402_exact") return X402_EXACT_SCHEME;
3330
- return void 0;
3347
+ function fundWireAmountMicro(priceMicroServer, fundMsats, requiredMsats) {
3348
+ if (fundMsats === requiredMsats) return priceMicroServer;
3349
+ return Math.round(priceMicroServer * fundMsats / requiredMsats);
3331
3350
  }
3332
- function x402CreditChannelNetworks(menu) {
3333
- return schemeNetworks(menu, X402_BATCH_SETTLEMENT_SCHEME);
3351
+ function readFundValuation(fields) {
3352
+ const structured = usableMicro(fields.expectedMicro);
3353
+ if (structured !== void 0) return { kind: "figure", micro: structured };
3354
+ const match = /quoted price \((\d+)\)/.exec(fields.message ?? "");
3355
+ const stated = match ? usableMicro(Number(match[1])) : void 0;
3356
+ if (stated !== void 0) return { kind: "figure", micro: stated };
3357
+ const providerStatedSomething = fields.expectedMicro !== void 0 || match !== null;
3358
+ return providerStatedSomething ? { kind: "malformed" } : { kind: "absent" };
3334
3359
  }
3335
- function x402CreditExactNetworks(menu) {
3336
- return schemeNetworks(menu, X402_EXACT_SCHEME);
3360
+ function usableMicro(value) {
3361
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
3337
3362
  }
3338
- function x402CreditFundingNetworks(menu) {
3339
- return [.../* @__PURE__ */ new Set([...x402CreditChannelNetworks(menu), ...x402CreditExactNetworks(menu)])];
3363
+
3364
+ // src/lib/router-feedback-claim.ts
3365
+ import { createHmac, timingSafeEqual } from "crypto";
3366
+ var ROUTER_FEEDBACK_CLAIM_HEADER = "x-dvmkit-router-feedback-claim";
3367
+ var ROUTER_FEEDBACK_CLAIM_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
3368
+ function normalizeFeedbackEndpointHost(endpointOrHost) {
3369
+ try {
3370
+ const url = new URL(
3371
+ endpointOrHost.includes("://") ? endpointOrHost : `https://${endpointOrHost}`
3372
+ );
3373
+ return url.host === "" ? null : url.host.toLowerCase();
3374
+ } catch {
3375
+ return null;
3376
+ }
3340
3377
  }
3341
- function schemeNetworks(menu, scheme) {
3342
- return (menu?.x402?.schemes ?? []).filter((entry) => entry.scheme === scheme).map((entry) => entry.network);
3378
+ function issueRouterFeedbackClaim(secret, attribution, now = Date.now()) {
3379
+ const body = {
3380
+ v: 2,
3381
+ dvm_id: attribution.dvmId,
3382
+ job_id: attribution.jobId,
3383
+ endpoint_host: attribution.endpointHost,
3384
+ exp: now + ROUTER_FEEDBACK_CLAIM_TTL_MS
3385
+ };
3386
+ const encoded = Buffer.from(JSON.stringify(body), "utf8").toString("base64url");
3387
+ const signature = createHmac("sha256", secret).update(encoded).digest("base64url");
3388
+ return `${encoded}.${signature}`;
3343
3389
  }
3344
- function matchesNetwork(networks, network) {
3345
- return networks.some((candidate) => network === void 0 || candidate === network);
3390
+ function verifyRouterFeedbackClaim(secret, token, now = Date.now()) {
3391
+ const dot = token.indexOf(".");
3392
+ if (dot <= 0 || dot === token.length - 1) return null;
3393
+ const encoded = token.slice(0, dot);
3394
+ const presentedSignature = token.slice(dot + 1);
3395
+ const expectedSignature = createHmac("sha256", secret).update(encoded).digest("base64url");
3396
+ if (!safeEqual(expectedSignature, presentedSignature)) return null;
3397
+ let body;
3398
+ try {
3399
+ body = JSON.parse(
3400
+ Buffer.from(encoded, "base64url").toString("utf8")
3401
+ );
3402
+ } catch {
3403
+ return null;
3404
+ }
3405
+ if (body.v !== 2 || typeof body.dvm_id !== "string" || body.dvm_id.length === 0 || typeof body.job_id !== "string" || body.job_id.length === 0 || typeof body.endpoint_host !== "string" || body.endpoint_host.length === 0 || typeof body.exp !== "number" || !Number.isFinite(body.exp) || body.exp <= now) {
3406
+ return null;
3407
+ }
3408
+ return {
3409
+ dvmId: body.dvm_id,
3410
+ jobId: body.job_id,
3411
+ endpointHost: body.endpoint_host,
3412
+ expiresAt: body.exp
3413
+ };
3414
+ }
3415
+ function safeEqual(expected, presented) {
3416
+ const expectedBuffer = Buffer.from(expected);
3417
+ const presentedBuffer = Buffer.from(presented);
3418
+ return expectedBuffer.length === presentedBuffer.length && timingSafeEqual(expectedBuffer, presentedBuffer);
3346
3419
  }
3347
3420
 
3348
- // src/lib/credit-policy.ts
3349
- var TIER0_CAP_MICRO = 5e5;
3350
- var TIER1_CAP_MICRO = 2e6;
3351
- var TIER1_MIN_RECEIPTS = 3;
3352
- var TIER2_MIN_RECEIPTS = 15;
3353
- var TIER2_MIN_SPAN_MS = 7 * 24 * 60 * 60 * 1e3;
3354
- var OPEN_TARGET_JOBS = 20;
3355
- var CASHU_REFILL_TARGET_JOBS = 40;
3356
- var TOPUP_THRESHOLD_FRACTION = 0.25;
3357
- var DEFAULT_CREDIT_POSTURE = "suggest";
3358
- var POCKET_TARGET_MICRO = 5e6;
3359
- function trustTierFor(args) {
3360
- const ladder = trustLadderFor(args);
3361
- return { tier: ladder.tier, capMicro: ladder.capMicro ?? args.menu.max_micro };
3421
+ // src/lib/wallet-agent/spend.ts
3422
+ import {
3423
+ getEncodedToken,
3424
+ MintOperationError
3425
+ } from "@cashu/cashu-ts";
3426
+ import { randomUUID } from "crypto";
3427
+
3428
+ // src/lib/wallet-agent/select.ts
3429
+ function selectProofsForSpend(args) {
3430
+ if (args.amountSats <= 0) {
3431
+ return {
3432
+ error: {
3433
+ code: "insufficient_balance",
3434
+ message: `amountSats must be positive; got ${args.amountSats}.`,
3435
+ hint: "Pass a positive sat amount."
3436
+ }
3437
+ };
3438
+ }
3439
+ if (args.mintAllowlist.length === 0) {
3440
+ return { error: noAcceptedMintsError() };
3441
+ }
3442
+ const reserveSats = args.swapReserveSats ?? (() => 0);
3443
+ let largestEligibleBalance = 0;
3444
+ for (const mint of args.mintAllowlist) {
3445
+ const candidates = eligibleProofsAt(args.proofs, mint);
3446
+ const balance = candidates.reduce((sum, p) => sum + p.amount, 0);
3447
+ largestEligibleBalance = Math.max(largestEligibleBalance, balance);
3448
+ if (balance < args.amountSats) continue;
3449
+ const selected = [];
3450
+ let total = 0;
3451
+ for (const p of candidates) {
3452
+ selected.push(p);
3453
+ total += p.amount;
3454
+ const required = args.amountSats + reserveSats(selected);
3455
+ if (total >= required) {
3456
+ return { selected, mint, totalSats: total };
3457
+ }
3458
+ }
3459
+ }
3460
+ return { error: noCoverError(largestEligibleBalance, args.amountSats) };
3362
3461
  }
3363
- function trustLadderFor(args) {
3364
- const successes = completedReceiptsFor(
3365
- args.receipts,
3366
- args.endpoint,
3367
- args.signingPubkey,
3368
- args.identity
3369
- );
3370
- const tier0Cap = args.tier0CapMicro ?? TIER0_CAP_MICRO;
3371
- const tier1Cap = args.tier1CapMicro ?? TIER1_CAP_MICRO;
3372
- const builderMax = args.menu?.max_micro;
3373
- const issued = successes.map((r) => r.receipt.issued_at * 1e3);
3374
- const spanMs = issued.length > 1 ? Math.max(...issued) - Math.min(...issued) : 0;
3375
- const count = successes.length;
3376
- if (count >= TIER2_MIN_RECEIPTS && spanMs >= TIER2_MIN_SPAN_MS) {
3462
+ function coveringMintsForSpend(args) {
3463
+ if (args.amountSats <= 0) {
3377
3464
  return {
3378
- tier: 2,
3379
- ...builderMax !== void 0 ? { capMicro: builderMax } : {},
3380
- receipts: count,
3381
- spanMs
3465
+ error: {
3466
+ code: "insufficient_balance",
3467
+ message: `amountSats must be positive; got ${args.amountSats}.`,
3468
+ hint: "Pass a positive sat amount."
3469
+ }
3382
3470
  };
3383
3471
  }
3384
- const toTier2 = {
3385
- tier: 2,
3386
- ...builderMax !== void 0 ? { capMicro: builderMax } : {},
3387
- receiptsNeeded: Math.max(TIER2_MIN_RECEIPTS - count, 0),
3388
- spanMs: TIER2_MIN_SPAN_MS
3389
- };
3390
- if (count >= TIER1_MIN_RECEIPTS) {
3391
- return { tier: 1, capMicro: tier1Cap, receipts: count, spanMs, next: toTier2 };
3472
+ if (args.mintAllowlist.length === 0) {
3473
+ return { error: noAcceptedMintsError() };
3474
+ }
3475
+ const mints = [];
3476
+ let largestEligibleBalance = 0;
3477
+ for (const mint of args.mintAllowlist) {
3478
+ const balance = eligibleProofsAt(args.proofs, mint).reduce((sum, p) => sum + p.amount, 0);
3479
+ largestEligibleBalance = Math.max(largestEligibleBalance, balance);
3480
+ if (balance >= args.amountSats && !mints.includes(mint)) mints.push(mint);
3392
3481
  }
3482
+ if (mints.length === 0) {
3483
+ return { error: noCoverError(largestEligibleBalance, args.amountSats) };
3484
+ }
3485
+ return { mints };
3486
+ }
3487
+ function eligibleProofsAt(proofs, mint) {
3488
+ return proofs.filter((p) => p.mint === mint && p.status === "valid" && p.reserved_for === void 0).slice().sort((a, b) => b.amount - a.amount);
3489
+ }
3490
+ function noAcceptedMintsError() {
3393
3491
  return {
3394
- tier: 0,
3395
- capMicro: tier0Cap,
3396
- receipts: count,
3397
- spanMs,
3398
- next: { tier: 1, capMicro: tier1Cap, receiptsNeeded: TIER1_MIN_RECEIPTS - count }
3492
+ code: "no_eligible_mint",
3493
+ message: "DVM advertises no Cashu mints for the agent-wallet path.",
3494
+ hint: "Use a different DVM, or fall back to the legacy --auto-pay flow."
3399
3495
  };
3400
3496
  }
3401
- function effectiveCreditPosture(config, endpoint, identity) {
3402
- const postures = perDvmEntriesFor(config, endpoint, identity).map(([, per2]) => per2.posture).filter(isCreditPosture);
3403
- const per = postures.reduce(
3404
- (strictest, posture) => strictest === void 0 || POSTURE_STRICTNESS[posture] > POSTURE_STRICTNESS[strictest] ? posture : strictest,
3405
- void 0
3406
- );
3407
- if (per !== void 0) return { posture: per, source: "per_dvm" };
3408
- const global = config?.posture;
3409
- if (isCreditPosture(global)) return { posture: global, source: "global" };
3410
- return { posture: DEFAULT_CREDIT_POSTURE, source: "default" };
3411
- }
3412
- function effectiveFundingRail(config, endpoint, identity) {
3413
- let conflict;
3414
- if (endpoint !== void 0) {
3415
- const pinned = perDvmEntriesFor(config, endpoint, identity).flatMap(
3416
- ([origin, per]) => isCreditFundRail(per.fundingRail) ? [[origin, per.fundingRail]] : []
3417
- );
3418
- const rails = [...new Set(pinned.map(([, rail]) => rail))];
3419
- if (rails.length > 1) conflict = { rails, endpoints: pinned.map(([origin]) => origin) };
3420
- else if (rails.length === 1) return { rail: rails[0], source: "per_dvm" };
3497
+ function noCoverError(largestEligibleBalance, amountSats) {
3498
+ if (largestEligibleBalance === 0) {
3499
+ return {
3500
+ code: "no_eligible_mint",
3501
+ message: "Agent wallet has no proofs at any of the DVM's accepted mints.",
3502
+ hint: "Fund the wallet at one of the DVM's accepted mints with: dvm wallet fund <amount>"
3503
+ };
3421
3504
  }
3422
- const global = config?.fundingRail;
3423
- if (isCreditFundRail(global))
3424
- return { rail: global, source: "global", ...conflict ? { conflict } : {} };
3425
- return { source: "default", ...conflict ? { conflict } : {} };
3505
+ return {
3506
+ code: "insufficient_balance",
3507
+ message: `Agent wallet has ${largestEligibleBalance} sats at the DVM's mints; need ${amountSats} plus the per-call mint swap fee.`,
3508
+ hint: "Top the wallet up with: dvm wallet fund <amount>"
3509
+ };
3426
3510
  }
3427
- function automaticCreditBoundRail(stored) {
3428
- if (stored?.status !== "active" || stored.reconciliationPending) return void 0;
3429
- return isCreditFundRail(stored.rail) ? stored.rail : void 0;
3511
+
3512
+ // src/lib/wallet-agent/submission-binding.ts
3513
+ import { createHash as createHash2 } from "crypto";
3514
+ function cashuSubmissionBindingFingerprint(binding) {
3515
+ return createHash2("sha256").update(canonicaliseForSigning(binding)).digest("hex");
3430
3516
  }
3431
- function trustOverrideCapMicro(args) {
3432
- const raw2 = args.builderPubkey ? lookupOverride(args.config?.trustOverrides, args.builderPubkey) : void 0;
3433
- if (!raw2) return void 0;
3434
- if (raw2.trim().toLowerCase() === "builder-max") {
3435
- return args.menu?.max_micro ?? Number.POSITIVE_INFINITY;
3517
+ function cashuSubmissionBindingFromContext(context) {
3518
+ if (context.strict_per_call !== true || context.route === "credit" || typeof context.dvm_id !== "string" || context.dvm_id.length === 0 || typeof context.capability !== "string" || context.capability.length === 0 || typeof context.pinned_mint !== "string" || context.pinned_mint.length === 0 || !context.policy) {
3519
+ return null;
3436
3520
  }
3437
- return parseCapMicro(raw2);
3438
- }
3439
- function tierCapOverrides(config) {
3440
- const out = {};
3441
- const tier0 = parseCapMicro(config?.tier0Cap);
3442
- const tier1 = parseCapMicro(config?.tier1Cap);
3443
- if (tier0 !== void 0) out.tier0CapMicro = tier0;
3444
- if (tier1 !== void 0) out.tier1CapMicro = tier1;
3445
- return out;
3446
- }
3447
- function sizeCreditMicro(args) {
3448
- const target = (args.targetJobs ?? OPEN_TARGET_JOBS) * args.priceMicro;
3449
- let size = Math.max(args.menu.min_micro, Math.min(target, args.capMicro));
3450
- const residualCeiling = args.menu.max_micro + args.priceMicro;
3451
- size = Math.min(size, residualCeiling);
3452
- return Math.max(size, args.menu.min_micro);
3453
- }
3454
- function refillTargetMicro(args) {
3455
- const target = (args.targetJobs ?? CASHU_REFILL_TARGET_JOBS) * args.priceMicro;
3456
- return Math.max(args.menu.min_micro, Math.min(target, args.capMicro, args.menu.max_micro));
3457
- }
3458
- function planCreditForQuote(args) {
3459
- const none = { mode: "none" };
3460
- const menu = args.menu;
3461
- if (!menu || !args.signingPubkey) return none;
3462
- const posture = effectiveCreditPosture(args.config, args.endpoint, args.identity).posture;
3463
- if (posture === "off") return none;
3464
- if (menu.currency.toLowerCase() !== "usd") return none;
3465
- const canCashu = menu.funding.includes("cashu");
3466
- const canFloat = menu.funding.includes("lightning") && args.floatConnected === true;
3467
- const canX402 = menu.funding.includes("x402") && args.x402Connected === true && offersX402CreditFunding(menu, args.x402Network);
3468
- const canMpp = menu.funding.includes("tempo") && args.tempoConnected === true && offersTempoCreditFunding(menu);
3469
- if (!canCashu && !canFloat && !canX402 && !canMpp) return none;
3470
- if (!Number.isFinite(args.priceMsats) || args.priceMsats <= 0) return none;
3471
- const stored = args.stored;
3472
- if (stored?.reconciliationPending) return none;
3473
- const refilling = stored?.status === "active" && stored.currency === menu.currency;
3474
- const rateUnavailable = !Number.isFinite(args.btcRate) || args.btcRate <= 0;
3475
- if (!refilling) {
3476
- const completed = completedReceiptsFor(
3477
- args.receipts,
3478
- args.endpoint,
3479
- args.signingPubkey,
3480
- args.identity
3481
- );
3482
- if (completed.length === 0) return none;
3483
- const identity = args.identity;
3484
- if (rateUnavailable && identity && !completed.some((entry) => {
3485
- const attested = attestedDvmIdentity(entry.builder);
3486
- return attested?.dvmId === identity.dvmId && attested.builderPubkey === identity.builderPubkey;
3487
- })) {
3488
- return none;
3489
- }
3490
- }
3491
- if (rateUnavailable) {
3492
- return posture === "auto" ? {
3493
- mode: "none",
3494
- fundingSkipped: {
3495
- reason: "btc_rate_unavailable",
3496
- operation: refilling ? "refill" : "open"
3497
- }
3498
- } : none;
3499
- }
3500
- const priceMicro = msatsToMicro(args.priceMsats, args.btcRate);
3501
- if (priceMicro <= 0) return none;
3502
- const tier = trustTierFor({
3503
- receipts: args.receipts,
3504
- endpoint: args.endpoint,
3505
- signingPubkey: args.signingPubkey,
3506
- ...args.identity ? { identity: args.identity } : {},
3507
- menu,
3508
- ...tierCapOverrides(args.config)
3509
- });
3510
- const targetJobs = targetJobsFor(args.config, args.endpoint, args.identity);
3511
- const pin = effectiveFundingRail(args.config, args.endpoint, args.identity);
3512
- const eligible = [
3513
- ...canCashu ? ["cashu"] : [],
3514
- ...canFloat ? ["lightning"] : [],
3515
- ...canX402 ? ["x402"] : [],
3516
- ...canMpp ? ["tempo"] : []
3517
- ];
3518
- const routeFor = (fundMicro2, boundRail2) => {
3519
- const forced = boundRail2 ?? pin.rail;
3520
- const pick = pickCreditFundRail({
3521
- rails: eligible,
3522
- amountMicro: fundMicro2,
3523
- menu,
3524
- ...args.x402Network ? { x402Network: args.x402Network } : {},
3525
- ...forced ? { forced } : {}
3526
- });
3527
- const head = forced ?? pick.order[0];
3528
- return { route: head === "cashu" ? "cashu" : "deferred", reason: pick.reason };
3529
- };
3530
- const pinned = pin.rail && pin.source !== "default" ? { rail: pin.rail, source: pin.source } : void 0;
3531
- const defer = (reason, creditId, fundMicro2, targetMicro2, maxDepositMicro, forced = pinned) => ({
3532
- mode: "none",
3533
- deferred: {
3534
- reason,
3535
- creditId,
3536
- fundMicro: fundMicro2,
3537
- targetMicro: targetMicro2,
3538
- maxDepositMicro,
3539
- currency: menu.currency,
3540
- menu,
3541
- ...forced ? { pinnedRail: forced.rail, pinnedRailSource: forced.source } : {}
3542
- }
3543
- });
3544
- if (refilling) {
3545
- const targetMicro2 = refillTargetMicro({
3546
- menu,
3547
- priceMicro,
3548
- capMicro: tier.capMicro,
3549
- ...targetJobs !== void 0 ? { targetJobs } : {}
3550
- });
3551
- const boundRail2 = isCreditFundRail(stored.rail) ? stored.rail : void 0;
3552
- const forcedRefill = boundRail2 ? { rail: boundRail2, source: "credit_bound" } : pinned;
3553
- const railFloorMicro = forcedRefill?.rail === "lightning" && menu.lightning_min_micro !== void 0 ? Math.max(menu.min_micro, menu.lightning_min_micro) : menu.min_micro;
3554
- const fundMicro2 = Math.min(
3555
- Math.max(railFloorMicro, targetMicro2 - Math.max(stored.remainingMicro, 0) + priceMicro),
3556
- menu.max_micro
3557
- );
3558
- if (posture === "suggest") {
3559
- return {
3560
- mode: "none",
3561
- suppressed: { reason: "refill", fundMicro: fundMicro2, targetMicro: targetMicro2, currency: menu.currency }
3562
- };
3563
- }
3564
- const refillRoute = routeFor(fundMicro2, boundRail2);
3565
- if (refillRoute.route === "deferred") {
3566
- return defer("refill", stored.creditId, fundMicro2, targetMicro2, fundMicro2, forcedRefill);
3567
- }
3568
- if (!canCashu) return none;
3569
- return {
3570
- mode: "topup",
3571
- creditId: stored.creditId,
3572
- drawId: randomUUID(),
3573
- ...fundAmounts(fundMicro2, args.btcRate),
3574
- currency: menu.currency,
3575
- targetMicro: targetMicro2,
3576
- railReason: refillRoute.reason
3577
- };
3578
- }
3579
- const fundMicro = sizeCreditMicro({
3580
- menu,
3581
- priceMicro,
3582
- capMicro: tier.capMicro,
3583
- ...targetJobs !== void 0 ? { targetJobs: Math.min(targetJobs, OPEN_TARGET_JOBS) } : {}
3584
- });
3585
- const targetMicro = refillTargetMicro({
3586
- menu,
3587
- priceMicro,
3588
- capMicro: tier.capMicro,
3589
- ...targetJobs !== void 0 ? { targetJobs } : {}
3590
- });
3591
- if (posture === "suggest") {
3592
- return {
3593
- mode: "none",
3594
- suppressed: { reason: "open", fundMicro, targetMicro, currency: menu.currency }
3595
- };
3596
- }
3597
- const openRoute = routeFor(fundMicro);
3598
- if (openRoute.route === "deferred") {
3599
- return defer("open", randomUUID(), fundMicro, targetMicro, fundMicro);
3600
- }
3601
- if (!canCashu) return none;
3602
- return {
3603
- mode: "open",
3604
- creditId: randomUUID(),
3605
- drawId: randomUUID(),
3606
- ...fundAmounts(fundMicro, args.btcRate),
3607
- currency: menu.currency,
3608
- targetMicro,
3609
- railReason: openRoute.reason
3610
- };
3611
- }
3612
- function planProbeDraw(args) {
3613
- const now = args.now ?? Date.now();
3614
- const implicit = [...args.implicit ?? []].filter(
3615
- (credit) => credit.status === "active" && credit.remainingMicro > 0 && credit.expiryMs > now
3616
- ).sort((a, b) => a.expiryMs - b.expiryMs).at(0);
3617
- if (implicit) {
3618
- if (effectiveCreditPosture(args.config, implicit.endpoint, args.identity).posture === "off") {
3619
- return {
3620
- mode: "none",
3621
- drawSkipped: {
3622
- reason: "posture_off",
3623
- creditId: implicit.creditId,
3624
- remainingMicro: implicit.remainingMicro
3625
- }
3626
- };
3627
- }
3628
- return {
3629
- mode: "draw",
3630
- creditId: implicit.creditId,
3631
- drawId: randomUUID(),
3632
- source: "implicit"
3633
- };
3634
- }
3635
- const stored = args.stored;
3636
- if (!stored) return { mode: "none" };
3637
- const skip = (reason, extra) => ({
3638
- mode: "none",
3639
- drawSkipped: {
3640
- reason,
3641
- creditId: stored.creditId,
3642
- remainingMicro: stored.remainingMicro,
3643
- ...extra
3644
- }
3645
- });
3646
- if (effectiveCreditPosture(args.config, stored.endpoint, args.identity).posture === "off") {
3647
- return skip("posture_off");
3648
- }
3649
- if (stored.status !== "active") return skip("not_active");
3650
- if (stored.reconciliationPending) return skip("reconciliation_pending");
3651
- if (stored.remainingMicro <= 0 && !stored.provisional) return skip("exhausted");
3652
- if (stored.expiryMs > 0 && stored.expiryMs <= now) return skip("expired");
3653
- if (stored.lastPriceMicro !== void 0 && args.btcRate > 0) {
3654
- const ceilingMicro = msatsToMicro(args.drawCeilingMsats, args.btcRate);
3655
- if (ceilingMicro > 0 && stored.lastPriceMicro > ceilingMicro) {
3656
- return skip("price_above_ceiling", {
3657
- ceilingMicro,
3658
- lastPriceMicro: stored.lastPriceMicro,
3659
- ceilingSource: args.drawCeilingSource
3660
- });
3661
- }
3662
- }
3663
- return { mode: "draw", creditId: stored.creditId, drawId: randomUUID(), source: "prepaid" };
3664
- }
3665
- function needsTopup(balanceMicro, targetMicro) {
3666
- return targetMicro > 0 && balanceMicro < targetMicro * TOPUP_THRESHOLD_FRACTION;
3667
- }
3668
- function pocketTargetMicro(config) {
3669
- return parseCapMicro(config?.pocketTarget) ?? POCKET_TARGET_MICRO;
3670
- }
3671
- function needsPocketRefill(balanceMicro, targetMicro) {
3672
- return needsTopup(balanceMicro, targetMicro);
3673
- }
3674
- function fundWireAmountMicro(priceMicroServer, fundMsats, requiredMsats) {
3675
- if (fundMsats === requiredMsats) return priceMicroServer;
3676
- return Math.round(priceMicroServer * fundMsats / requiredMsats);
3677
- }
3678
- function readFundValuation(fields) {
3679
- const structured = usableMicro(fields.expectedMicro);
3680
- if (structured !== void 0) return { kind: "figure", micro: structured };
3681
- const match = /quoted price \((\d+)\)/.exec(fields.message ?? "");
3682
- const stated = match ? usableMicro(Number(match[1])) : void 0;
3683
- if (stated !== void 0) return { kind: "figure", micro: stated };
3684
- const providerStatedSomething = fields.expectedMicro !== void 0 || match !== null;
3685
- return providerStatedSomething ? { kind: "malformed" } : { kind: "absent" };
3686
- }
3687
- function msatsToMicro(msats, btcRate) {
3688
- return Math.round(msatsToUsd(msats, btcRate) * 1e6);
3689
- }
3690
- function microToSats(micro, btcRate) {
3691
- return Math.ceil(usdToMsats(micro / 1e6, btcRate) / 1e3);
3692
- }
3693
- function perDvmEntriesFor(config, endpoint, identity) {
3694
- const perDvm = config?.perDvm;
3695
- if (!perDvm) return [];
3696
- const key = normalizeCreditEndpoint(endpoint);
3697
- return Object.entries(perDvm).filter(
3698
- ([origin, per]) => normalizeCreditEndpoint(origin) === key || identity !== void 0 && per.dvmId === identity.dvmId && per.builderPubkey?.trim().toLowerCase() === identity.builderPubkey
3699
- );
3700
- }
3701
- var POSTURE_STRICTNESS = { auto: 0, suggest: 1, off: 2 };
3702
- function usableMicro(value) {
3703
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
3704
- }
3705
- function fundAmounts(fundMicro, btcRate) {
3706
- const fundSats = microToSats(fundMicro, btcRate);
3707
- return { fundMicro, fundSats, fundMsats: fundSats * 1e3 };
3708
- }
3709
- function completedReceiptsFor(receipts, endpoint, signingPubkey, identity) {
3710
- const key = normalizeCreditEndpoint(endpoint);
3711
- const requester = signingPubkey?.trim().toLowerCase();
3712
- if (!requester) return [];
3713
- const mine = (entry) => entry.receipt.requester_pubkey?.trim().toLowerCase() === requester && entry.receipt.outcome === "completed";
3714
- const here = [];
3715
- let anchor = identity;
3716
- for (const entry of receipts) {
3717
- if (normalizeCreditEndpoint(entry.endpoint) !== key || !mine(entry)) continue;
3718
- const verdict = verifyJobReceipt({ receipt: entry.receipt, builder: entry.builder });
3719
- if (verdict.status !== "verified" && verdict.status !== "verified_unattested") continue;
3720
- if (identity !== void 0) {
3721
- const attested = verdict.checks.attestation === "ok" ? attestedDvmIdentity(entry.builder) : void 0;
3722
- if (attested?.dvmId !== identity.dvmId || attested.builderPubkey !== identity.builderPubkey) {
3723
- continue;
3724
- }
3725
- }
3726
- here.push(entry);
3727
- if (anchor === void 0 && verdict.checks.attestation === "ok") {
3728
- anchor = attestedDvmIdentity(entry.builder);
3729
- }
3730
- }
3731
- if (!anchor) return here;
3732
- const elsewhere = receipts.filter((entry) => {
3733
- if (normalizeCreditEndpoint(entry.endpoint) === key || !mine(entry)) return false;
3734
- if (entry.receipt.dvm_id !== anchor.dvmId) return false;
3735
- const verdict = verifyJobReceipt({ receipt: entry.receipt, builder: entry.builder });
3736
- if (verdict.checks.attestation !== "ok") return false;
3737
- const attested = attestedDvmIdentity(entry.builder);
3738
- return attested?.dvmId === anchor.dvmId && attested.builderPubkey === anchor.builderPubkey;
3739
- });
3740
- return [...here, ...elsewhere];
3741
- }
3742
- function targetJobsFor(config, endpoint, identity) {
3743
- const targets = perDvmEntriesFor(config, endpoint, identity).map(([, per]) => per.targetJobs).filter((jobs) => typeof jobs === "number" && Number.isFinite(jobs));
3744
- if (targets.length > 0) return Math.min(...targets);
3745
- return config?.targetJobs;
3746
- }
3747
- function lookupOverride(overrides, builderPubkey) {
3748
- if (!overrides) return void 0;
3749
- const key = builderPubkey.trim().toLowerCase();
3750
- for (const [pubkey, cap] of Object.entries(overrides)) {
3751
- if (pubkey.trim().toLowerCase() === key) return cap;
3752
- }
3753
- return void 0;
3754
- }
3755
- function parseCapMicro(cap) {
3756
- if (!cap) return void 0;
3757
- const match = /^\$?(\d+(?:\.\d{1,6})?)$/.exec(cap.trim());
3758
- if (!match) return void 0;
3759
- const micro = Math.round(Number(match[1]) * 1e6);
3760
- return micro > 0 ? micro : void 0;
3761
- }
3762
-
3763
- // src/lib/router-feedback-claim.ts
3764
- import { createHmac, timingSafeEqual } from "crypto";
3765
- var ROUTER_FEEDBACK_CLAIM_HEADER = "x-dvmkit-router-feedback-claim";
3766
- var ROUTER_FEEDBACK_CLAIM_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
3767
- function normalizeFeedbackEndpointHost(endpointOrHost) {
3768
- try {
3769
- const url = new URL(
3770
- endpointOrHost.includes("://") ? endpointOrHost : `https://${endpointOrHost}`
3771
- );
3772
- return url.host === "" ? null : url.host.toLowerCase();
3773
- } catch {
3774
- return null;
3775
- }
3776
- }
3777
- function issueRouterFeedbackClaim(secret, attribution, now = Date.now()) {
3778
- const body = {
3779
- v: 2,
3780
- dvm_id: attribution.dvmId,
3781
- job_id: attribution.jobId,
3782
- endpoint_host: attribution.endpointHost,
3783
- exp: now + ROUTER_FEEDBACK_CLAIM_TTL_MS
3784
- };
3785
- const encoded = Buffer.from(JSON.stringify(body), "utf8").toString("base64url");
3786
- const signature = createHmac("sha256", secret).update(encoded).digest("base64url");
3787
- return `${encoded}.${signature}`;
3788
- }
3789
- function verifyRouterFeedbackClaim(secret, token, now = Date.now()) {
3790
- const dot = token.indexOf(".");
3791
- if (dot <= 0 || dot === token.length - 1) return null;
3792
- const encoded = token.slice(0, dot);
3793
- const presentedSignature = token.slice(dot + 1);
3794
- const expectedSignature = createHmac("sha256", secret).update(encoded).digest("base64url");
3795
- if (!safeEqual(expectedSignature, presentedSignature)) return null;
3796
- let body;
3797
- try {
3798
- body = JSON.parse(
3799
- Buffer.from(encoded, "base64url").toString("utf8")
3800
- );
3801
- } catch {
3802
- return null;
3803
- }
3804
- if (body.v !== 2 || typeof body.dvm_id !== "string" || body.dvm_id.length === 0 || typeof body.job_id !== "string" || body.job_id.length === 0 || typeof body.endpoint_host !== "string" || body.endpoint_host.length === 0 || typeof body.exp !== "number" || !Number.isFinite(body.exp) || body.exp <= now) {
3805
- return null;
3806
- }
3807
- return {
3808
- dvmId: body.dvm_id,
3809
- jobId: body.job_id,
3810
- endpointHost: body.endpoint_host,
3811
- expiresAt: body.exp
3812
- };
3813
- }
3814
- function safeEqual(expected, presented) {
3815
- const expectedBuffer = Buffer.from(expected);
3816
- const presentedBuffer = Buffer.from(presented);
3817
- return expectedBuffer.length === presentedBuffer.length && timingSafeEqual(expectedBuffer, presentedBuffer);
3818
- }
3819
-
3820
- // src/lib/wallet-agent/spend.ts
3821
- import {
3822
- getEncodedToken,
3823
- MintOperationError
3824
- } from "@cashu/cashu-ts";
3825
- import { randomUUID as randomUUID2 } from "crypto";
3826
-
3827
- // src/lib/wallet-agent/select.ts
3828
- function selectProofsForSpend(args) {
3829
- if (args.amountSats <= 0) {
3830
- return {
3831
- error: {
3832
- code: "insufficient_balance",
3833
- message: `amountSats must be positive; got ${args.amountSats}.`,
3834
- hint: "Pass a positive sat amount."
3835
- }
3836
- };
3837
- }
3838
- if (args.mintAllowlist.length === 0) {
3839
- return { error: noAcceptedMintsError() };
3840
- }
3841
- const reserveSats = args.swapReserveSats ?? (() => 0);
3842
- let largestEligibleBalance = 0;
3843
- for (const mint of args.mintAllowlist) {
3844
- const candidates = eligibleProofsAt(args.proofs, mint);
3845
- const balance = candidates.reduce((sum, p) => sum + p.amount, 0);
3846
- largestEligibleBalance = Math.max(largestEligibleBalance, balance);
3847
- if (balance < args.amountSats) continue;
3848
- const selected = [];
3849
- let total = 0;
3850
- for (const p of candidates) {
3851
- selected.push(p);
3852
- total += p.amount;
3853
- const required = args.amountSats + reserveSats(selected);
3854
- if (total >= required) {
3855
- return { selected, mint, totalSats: total };
3856
- }
3857
- }
3858
- }
3859
- return { error: noCoverError(largestEligibleBalance, args.amountSats) };
3860
- }
3861
- function coveringMintsForSpend(args) {
3862
- if (args.amountSats <= 0) {
3863
- return {
3864
- error: {
3865
- code: "insufficient_balance",
3866
- message: `amountSats must be positive; got ${args.amountSats}.`,
3867
- hint: "Pass a positive sat amount."
3868
- }
3869
- };
3870
- }
3871
- if (args.mintAllowlist.length === 0) {
3872
- return { error: noAcceptedMintsError() };
3873
- }
3874
- const mints = [];
3875
- let largestEligibleBalance = 0;
3876
- for (const mint of args.mintAllowlist) {
3877
- const balance = eligibleProofsAt(args.proofs, mint).reduce((sum, p) => sum + p.amount, 0);
3878
- largestEligibleBalance = Math.max(largestEligibleBalance, balance);
3879
- if (balance >= args.amountSats && !mints.includes(mint)) mints.push(mint);
3880
- }
3881
- if (mints.length === 0) {
3882
- return { error: noCoverError(largestEligibleBalance, args.amountSats) };
3883
- }
3884
- return { mints };
3885
- }
3886
- function eligibleProofsAt(proofs, mint) {
3887
- return proofs.filter((p) => p.mint === mint && p.status === "valid" && p.reserved_for === void 0).slice().sort((a, b) => b.amount - a.amount);
3888
- }
3889
- function noAcceptedMintsError() {
3890
- return {
3891
- code: "no_eligible_mint",
3892
- message: "DVM advertises no Cashu mints for the agent-wallet path.",
3893
- hint: "Use a different DVM, or fall back to the legacy --auto-pay flow."
3894
- };
3895
- }
3896
- function noCoverError(largestEligibleBalance, amountSats) {
3897
- if (largestEligibleBalance === 0) {
3898
- return {
3899
- code: "no_eligible_mint",
3900
- message: "Agent wallet has no proofs at any of the DVM's accepted mints.",
3901
- hint: "Fund the wallet at one of the DVM's accepted mints with: dvm wallet fund <amount>"
3902
- };
3903
- }
3904
- return {
3905
- code: "insufficient_balance",
3906
- message: `Agent wallet has ${largestEligibleBalance} sats at the DVM's mints; need ${amountSats} plus the per-call mint swap fee.`,
3907
- hint: "Top the wallet up with: dvm wallet fund <amount>"
3908
- };
3909
- }
3910
-
3911
- // src/lib/wallet-agent/submission-binding.ts
3912
- import { createHash as createHash2 } from "crypto";
3913
- function cashuSubmissionBindingFingerprint(binding) {
3914
- return createHash2("sha256").update(canonicaliseForSigning(binding)).digest("hex");
3915
- }
3916
- function cashuSubmissionBindingFromContext(context) {
3917
- if (context.strict_per_call !== true || context.route === "credit" || typeof context.dvm_id !== "string" || context.dvm_id.length === 0 || typeof context.capability !== "string" || context.capability.length === 0 || typeof context.pinned_mint !== "string" || context.pinned_mint.length === 0 || !context.policy) {
3918
- return null;
3919
- }
3920
- return {
3921
- dvmId: context.dvm_id,
3922
- capability: context.capability,
3923
- requestBody: context.submit_body,
3924
- mint: context.pinned_mint,
3925
- signer: context.signing_pubkey ?? null,
3926
- policy: context.policy
3927
- };
3521
+ return {
3522
+ dvmId: context.dvm_id,
3523
+ capability: context.capability,
3524
+ requestBody: context.submit_body,
3525
+ mint: context.pinned_mint,
3526
+ signer: context.signing_pubkey ?? null,
3527
+ policy: context.policy
3528
+ };
3928
3529
  }
3929
3530
 
3930
3531
  // src/lib/wallet-agent/submission-context.ts
@@ -3996,7 +3597,7 @@ async function prepareAgentSpend(args) {
3996
3597
  submitContext = { ...submitContext, binding_fingerprint: bindingFingerprint };
3997
3598
  }
3998
3599
  const now = args.now ?? (() => Date.now());
3999
- const uuid = args.uuid ?? (() => randomUUID2());
3600
+ const uuid = args.uuid ?? (() => randomUUID());
4000
3601
  const timeoutSec = args.inFlightTimeoutSec ?? inFlightTimeoutSeconds();
4001
3602
  const getWalletForMint = args.getWalletForMint ?? getWallet;
4002
3603
  const refundClause = resolveRefundClause(args.cashuP2pk, args.locktimeSeconds, now(), {
@@ -5968,13 +5569,16 @@ async function createX402BatchPayment(args) {
5968
5569
  };
5969
5570
  }
5970
5571
  function x402VoucherPendingRefusal(blocking, data = {}) {
5572
+ return formatX402VoucherPendingRefusal(x402VoucherPendingState(blocking), data);
5573
+ }
5574
+ function formatX402VoucherPendingRefusal(blocking, data = {}) {
5971
5575
  const base = {
5972
5576
  credit_id: blocking.creditId,
5973
5577
  fund_id: blocking.fundId,
5974
5578
  channel_id: blocking.channelId,
5975
5579
  ...data
5976
5580
  };
5977
- if (!x402PendingRetired(blocking)) {
5581
+ if (!blocking.retired) {
5978
5582
  return new DvmError(
5979
5583
  "x402_voucher_pending",
5980
5584
  `A signed x402 voucher for credit ${blocking.creditId} is still waiting for its acknowledgement, so this channel cannot sign another.`,
@@ -5982,8 +5586,8 @@ function x402VoucherPendingRefusal(blocking, data = {}) {
5982
5586
  base
5983
5587
  );
5984
5588
  }
5985
- const until = new Date(x402PendingBlockExpiresAt(blocking)).toISOString();
5986
- const signedDeadline = x402PendingDepositExpiresAt(blocking) !== void 0;
5589
+ const until = new Date(blocking.blockedUntil).toISOString();
5590
+ const signedDeadline = blocking.depositExpiresAt !== void 0;
5987
5591
  return new DvmError(
5988
5592
  "x402_voucher_pending",
5989
5593
  signedDeadline ? `The deposit this channel signed for credit ${blocking.creditId} was refused by the DVM but can still be submitted, so the channel cannot sign a second one yet.` : `The funding this channel signed for credit ${blocking.creditId} was refused by the DVM, and it names no deadline after which it stops being submittable, so the channel cannot sign a second one yet.`,
@@ -6385,12 +5989,15 @@ function x402RefundError(err, chain) {
6385
5989
  );
6386
5990
  }
6387
5991
  function assertNoRivalVoucher(channelId, creditId) {
6388
- const blocking = listPendingX402FundingsForChannel(channelId).filter(
6389
- (entry) => x402PendingUnsettled(entry)
6390
- );
6391
- const rival = blocking.find((entry) => entry.creditId !== creditId && !x402PendingRetired(entry)) ?? blocking.find((entry) => entry.creditId !== creditId || x402PendingRetired(entry));
6392
- if (!rival) return;
6393
- throw x402VoucherPendingRefusal(rival, { channel_id: channelId });
5992
+ const pending = listPendingX402FundingsForChannel(channelId);
5993
+ try {
5994
+ assertNoRivalX402Voucher(channelId, creditId, pending);
5995
+ } catch (state) {
5996
+ if (typeof state !== "object" || state === null || !("code" in state) || state.code !== "x402_voucher_pending" || !("creditId" in state) || typeof state.creditId !== "string" || !("fundId" in state) || typeof state.fundId !== "string" || !("channelId" in state) || typeof state.channelId !== "string" || !("endpoint" in state) || typeof state.endpoint !== "string" || !("retired" in state) || typeof state.retired !== "boolean" || !("blockedUntil" in state) || typeof state.blockedUntil !== "number") {
5997
+ throw state;
5998
+ }
5999
+ throw formatX402VoucherPendingRefusal(state);
6000
+ }
6394
6001
  }
6395
6002
  function selectBatchRequirement(required, network, recorded) {
6396
6003
  const accepted = required.accepts.find(
@@ -6426,26 +6033,49 @@ function normalizeEndpoint(endpoint) {
6426
6033
  }
6427
6034
  }
6428
6035
 
6429
- // src/lib/credit-fund.ts
6430
- var CREDIT_FUND_RAILS = ["cashu", "lightning", "x402", "tempo"];
6431
- var CREDIT_DRAIN_RAILS = ["cashu", "x402", "tempo"];
6432
- function isCreditFundRail(value) {
6433
- return typeof value === "string" && CREDIT_FUND_RAILS.includes(value);
6036
+ // src/lib/x402/menu.ts
6037
+ function offersX402CreditFunding(menu, network) {
6038
+ return matchesNetwork(x402CreditFundingNetworks(menu), network);
6434
6039
  }
6435
- var STABLECOIN_FUND_RAILS = ["x402", "tempo"];
6436
- function isStablecoinFundRail(rail) {
6437
- return STABLECOIN_FUND_RAILS.includes(rail);
6040
+ function offersX402CreditChannel(menu, network) {
6041
+ return matchesNetwork(x402CreditChannelNetworks(menu), network);
6438
6042
  }
6439
- var CREDIT_FUNDING_MODES = ["one-shot", "reusable"];
6440
- function isCreditFundingMode(value) {
6441
- return typeof value === "string" && CREDIT_FUNDING_MODES.includes(value);
6043
+ function x402CreditFlavour(menu, network) {
6044
+ if (offersX402CreditChannel(menu, network)) return X402_BATCH_SETTLEMENT_SCHEME;
6045
+ if (matchesNetwork(x402CreditExactNetworks(menu), network)) return X402_EXACT_SCHEME;
6046
+ return void 0;
6442
6047
  }
6443
- function fundingModeForTempoIntent(intent) {
6444
- return intent === "session" ? "reusable" : "one-shot";
6048
+ function offersX402CreditFlavour(menu, network, flavour) {
6049
+ return matchesNetwork(
6050
+ flavour === X402_BATCH_SETTLEMENT_SCHEME ? x402CreditChannelNetworks(menu) : x402CreditExactNetworks(menu),
6051
+ network
6052
+ );
6445
6053
  }
6446
- function fundingModeForX402Flavour(flavour) {
6447
- return flavour === X402_BATCH_SETTLEMENT_SCHEME ? "reusable" : "one-shot";
6054
+ function x402InstrumentForFlavour(flavour) {
6055
+ return flavour === X402_BATCH_SETTLEMENT_SCHEME ? "x402_channel" : "x402_exact";
6056
+ }
6057
+ function x402FlavourForInstrument(instrument) {
6058
+ if (instrument === "x402_channel") return X402_BATCH_SETTLEMENT_SCHEME;
6059
+ if (instrument === "x402_exact") return X402_EXACT_SCHEME;
6060
+ return void 0;
6061
+ }
6062
+ function x402CreditChannelNetworks(menu) {
6063
+ return schemeNetworks(menu, X402_BATCH_SETTLEMENT_SCHEME);
6064
+ }
6065
+ function x402CreditExactNetworks(menu) {
6066
+ return schemeNetworks(menu, X402_EXACT_SCHEME);
6067
+ }
6068
+ function x402CreditFundingNetworks(menu) {
6069
+ return [.../* @__PURE__ */ new Set([...x402CreditChannelNetworks(menu), ...x402CreditExactNetworks(menu)])];
6070
+ }
6071
+ function schemeNetworks(menu, scheme) {
6072
+ return (menu?.x402?.schemes ?? []).filter((entry) => entry.scheme === scheme).map((entry) => entry.network);
6073
+ }
6074
+ function matchesNetwork(networks, network) {
6075
+ return networks.some((candidate) => network === void 0 || candidate === network);
6448
6076
  }
6077
+
6078
+ // src/lib/credit-fund.ts
6449
6079
  var CREDIT_POST_TIMEOUT_MS = 15e3;
6450
6080
  var CREDIT_PERMANENT_REFUSALS = /* @__PURE__ */ new Set([
6451
6081
  "credit_over_max",
@@ -6505,7 +6135,7 @@ async function runCreditFunding(args) {
6505
6135
  }
6506
6136
  clearPending(pending);
6507
6137
  }
6508
- const fundId = args.fundId ?? randomUUID3();
6138
+ const fundId = args.fundId ?? randomUUID2();
6509
6139
  const base = { creditId: args.creditId, fundId };
6510
6140
  const eligible = args.fundingMode ? availableRails(args).filter(isStablecoinFundRail) : availableRails(args);
6511
6141
  const x402Network = loadX402Wallet()?.network;
@@ -7629,7 +7259,7 @@ async function fundOverCashu(args, fundId) {
7629
7259
  display: "The bitcoin exchange rate couldn't be read, so the funding amount can't be sized."
7630
7260
  };
7631
7261
  }
7632
- const fundSats = microToSats2(args.amountMicro, rate);
7262
+ const fundSats = microToSats(args.amountMicro, rate);
7633
7263
  if (args.maxCashuPrincipalSats !== void 0 && fundSats > args.maxCashuPrincipalSats) {
7634
7264
  return cashuFundingCapRefusal(base, {
7635
7265
  code: "cashu_principal_cap_exceeded",
@@ -7839,7 +7469,7 @@ async function fundOverMppCharge(args, fundId, resumed, locked = false) {
7839
7469
  display: "The bitcoin exchange rate couldn't be read, so the funding can't be checked against the global budget."
7840
7470
  };
7841
7471
  }
7842
- const plannedMsats = microToSats2(args.amountMicro, rate) * 1e3;
7472
+ const plannedMsats = microToSats(args.amountMicro, rate) * 1e3;
7843
7473
  try {
7844
7474
  await enforceGlobalBudget({ plannedMsats, btcRate: rate });
7845
7475
  } catch (err) {
@@ -8084,7 +7714,7 @@ async function fundOverMpp(args, fundId, resumed, locked = false) {
8084
7714
  display: "The bitcoin exchange rate couldn't be read, so the funding can't be checked against the global budget."
8085
7715
  };
8086
7716
  }
8087
- const plannedMsats = microToSats2(args.amountMicro, rate) * 1e3;
7717
+ const plannedMsats = microToSats(args.amountMicro, rate) * 1e3;
8088
7718
  let budgetBtcRate;
8089
7719
  try {
8090
7720
  budgetBtcRate = await enforceGlobalBudget({ plannedMsats, btcRate: rate });
@@ -8303,7 +7933,7 @@ async function fundOverMpp(args, fundId, resumed, locked = false) {
8303
7933
  const bookedMicro = current.authorizedMicro ?? current.amountMicro;
8304
7934
  if (current.budgetBtcRate) {
8305
7935
  recordSpendOnce({
8306
- msats: microToSats2(bookedMicro, current.budgetBtcRate) * 1e3,
7936
+ msats: microToSats(bookedMicro, current.budgetBtcRate) * 1e3,
8307
7937
  btcRate: current.budgetBtcRate,
8308
7938
  kind: "credit_fund",
8309
7939
  jobId: `fund:${current.creditId}:${fundId}`,
@@ -8444,7 +8074,7 @@ async function fundOverLightning(args, fundId, resumed) {
8444
8074
  };
8445
8075
  }
8446
8076
  try {
8447
- await assertFloatBudget(microToSats2(args.amountMicro, rate) * 1e3);
8077
+ await assertFloatBudget(microToSats(args.amountMicro, rate) * 1e3);
8448
8078
  } catch (err) {
8449
8079
  return budgetRefusal(base, err);
8450
8080
  }
@@ -8875,184 +8505,575 @@ function clearPending(pending) {
8875
8505
  function shouldClearPending(err) {
8876
8506
  return isPermanentRefusal(err) || err instanceof DvmError && err.code === "lightning_unavailable";
8877
8507
  }
8878
- function expiredFunding(base) {
8879
- const error = new DvmError(
8880
- "lightning_invoice_expired",
8881
- "The original Lightning funding invoice expired before it was confirmed.",
8882
- "Re-run the funding to start a new intent with a new funding id."
8508
+ function expiredFunding(base) {
8509
+ const error = new DvmError(
8510
+ "lightning_invoice_expired",
8511
+ "The original Lightning funding invoice expired before it was confirmed.",
8512
+ "Re-run the funding to start a new intent with a new funding id."
8513
+ );
8514
+ return {
8515
+ ...base,
8516
+ status: "refused",
8517
+ error,
8518
+ display: "The original Lightning invoice expired before it was funded. It was cleared and will not be polled again.",
8519
+ hint: error.hint
8520
+ };
8521
+ }
8522
+ function lightningOffer(err) {
8523
+ if (!isSettlementPending(err)) return void 0;
8524
+ const offer = err.data?.lightning;
8525
+ return offer;
8526
+ }
8527
+ function isSettlementPending(err) {
8528
+ return err instanceof DvmError && err.code === "lightning_settlement_pending" && isRecord(err.data?.lightning);
8529
+ }
8530
+ function isPermanentRefusal(err) {
8531
+ if (!(err instanceof DvmError)) return false;
8532
+ if (err.data?.retryable === false) return true;
8533
+ return CREDIT_PERMANENT_REFUSALS.has(err.code) || boundRail(err) !== void 0;
8534
+ }
8535
+ function boundRail(err) {
8536
+ if (!(err instanceof DvmError)) return void 0;
8537
+ const rail = err.data?.credit_rail;
8538
+ return isCreditFundRail(rail) ? rail : void 0;
8539
+ }
8540
+ function boundInstrument(err) {
8541
+ if (!(err instanceof DvmError)) return void 0;
8542
+ return typeof err.data?.credit_instrument === "string" ? err.data.credit_instrument : void 0;
8543
+ }
8544
+ function boundChannelId(err) {
8545
+ if (!(err instanceof DvmError)) return void 0;
8546
+ return typeof err.data?.credit_channel_id === "string" ? err.data.credit_channel_id : void 0;
8547
+ }
8548
+ function boundCreditId(err) {
8549
+ if (!(err instanceof DvmError)) return void 0;
8550
+ return typeof err.data?.bound_credit_id === "string" ? err.data.bound_credit_id : void 0;
8551
+ }
8552
+ function isBindingRefusal(err) {
8553
+ return boundInstrument(err) !== void 0 || boundRail(err) !== void 0;
8554
+ }
8555
+ function lightningRefusal(base, err) {
8556
+ const mapped = err instanceof DvmError ? err : void 0;
8557
+ const required = boundRail(err);
8558
+ if (required) {
8559
+ return {
8560
+ ...base,
8561
+ status: "unavailable",
8562
+ requiredRail: required,
8563
+ error: mapped,
8564
+ display: `This credit was opened over ${required}, so a Lightning top-up can't be added to it.`
8565
+ };
8566
+ }
8567
+ if (mapped?.code === "below_rail_minimum") {
8568
+ const minMicro = typeof mapped.data?.min_micro === "number" ? mapped.data.min_micro : void 0;
8569
+ return {
8570
+ ...base,
8571
+ status: "unavailable",
8572
+ error: mapped,
8573
+ display: typeof mapped.data?.display === "string" ? mapped.data.display : "This amount is below the minimum this DVM can receive over Lightning, so no invoice was issued.",
8574
+ hint: "Refill the agent's ecash pocket with 'dvm wallet fund' and retry \u2014 ecash funds any amount here." + (minMicro !== void 0 ? ` Or fund at least $${formatUsd(minMicro / 1e6)} to use Lightning.` : "")
8575
+ };
8576
+ }
8577
+ if (mapped?.code === "lightning_unavailable") {
8578
+ return {
8579
+ ...base,
8580
+ status: "unavailable",
8581
+ error: mapped,
8582
+ display: typeof mapped.data?.display === "string" ? mapped.data.display : "This DVM can't issue a Lightning invoice right now.",
8583
+ hint: mapped.hint
8584
+ };
8585
+ }
8586
+ return {
8587
+ ...base,
8588
+ status: "refused",
8589
+ error: mapped,
8590
+ // The DVM's own `display` when it wrote one; otherwise its message, so the
8591
+ // composed prose is never thinner than the error it was built from —
8592
+ // callers relay `display`, not `error.message`.
8593
+ display: typeof mapped?.data?.display === "string" ? mapped.data.display : `The credit funding was refused${mapped ? ` (${mapped.code}). ${mapped.message}` : "."}`,
8594
+ hint: mapped?.hint
8595
+ };
8596
+ }
8597
+ function budgetRefusal(base, err) {
8598
+ const mapped = err instanceof DvmError ? err : void 0;
8599
+ const display = typeof mapped?.data?.display === "string" ? mapped.data.display : mapped?.message ?? "";
8600
+ return {
8601
+ ...base,
8602
+ status: "refused",
8603
+ error: mapped,
8604
+ display: `${display} Nothing was charged, and any prepaid balance already at this DVM keeps working.`.trim(),
8605
+ hint: mapped?.hint
8606
+ };
8607
+ }
8608
+ function forcedRailHint(args, rail) {
8609
+ const release = releaseForcedRailHint(args);
8610
+ const advertised = args.menu?.funding;
8611
+ if (advertised && !advertised.includes(rail)) {
8612
+ return `This DVM advertises credit funding over ${advertised.join(", ") || "no rail at all"}, not ${rail}. ${release.text}`;
8613
+ }
8614
+ if (rail === "lightning") {
8615
+ const connect = `No Lightning wallet is connected here, so there is nothing to pay an invoice with. Connect one with 'dvm wallet connect', piping the connection string in`;
8616
+ return release.instruction ? `${connect}, or ${lowerFirst(release.text)}` : `${connect}. ${release.text}`;
8617
+ }
8618
+ if (rail === "x402") {
8619
+ const wallet = loadX402Wallet();
8620
+ if (!wallet) {
8621
+ return `No x402 wallet is connected here, so there is nothing to sign a funding payment with. Run 'dvm wallet x402-connect' to create one, or add --key-file <path> to import a key you already hold. ${release.text}`;
8622
+ }
8623
+ const networks = x402CreditFundingNetworks(args.menu);
8624
+ if (networks.length > 0) {
8625
+ return `This DVM takes x402 credit funding on ${networks.map(x402NetworkLabel).join(", ")}, and the connected wallet is pinned to ${x402NetworkLabel(wallet.network)}. Re-pin it without handling its key: 'dvm wallet x402-network ${networks[0]}'. ${release.text}`;
8626
+ }
8627
+ if (args.menu) {
8628
+ return `This DVM's funding menu names no x402 scheme, so there is no requirement to sign a payment against. ${release.text}`;
8629
+ }
8630
+ return `This DVM's funding menu hasn't been read here, so no x402 scheme is known to sign a payment against. ${release.text}`;
8631
+ }
8632
+ if (rail === "tempo") {
8633
+ if (!hasConnectedTempoWallet(args)) {
8634
+ const connect = `No Tempo wallet is connected here, so there is nothing to sign a funding payment with. Connect one with 'dvm wallet tempo-connect'`;
8635
+ return release.instruction ? `${connect}, or ${lowerFirst(release.text)}` : `${connect}. ${release.text}`;
8636
+ }
8637
+ if (args.menu) {
8638
+ return `This DVM advertises Tempo credit funding, but its funding menu names no supported Tempo charge or session intent, so this caller has no payment to build. ${release.text}`;
8639
+ }
8640
+ return `This DVM's funding menu hasn't been read here, so no Tempo charge or session intent is known to build a payment against. ${release.text}`;
8641
+ }
8642
+ if (!args.providerInfo) {
8643
+ return `This DVM's '/v1/info' hasn't been read here, so no Cashu offer is known to attach a proof to. ${release.text}`;
8644
+ }
8645
+ return `This DVM publishes no Cashu offer for a proof to be locked to, so ecash can't fund it. ${release.text}`;
8646
+ }
8647
+ function releaseForcedRailHint(args) {
8648
+ switch (args.railSource) {
8649
+ case "per_dvm":
8650
+ return {
8651
+ text: `Run 'dvm credit rail --clear --dvm ${args.ref.endpoint}' to let the global pin or amount rule decide.`,
8652
+ instruction: true
8653
+ };
8654
+ case "global":
8655
+ return {
8656
+ text: "Run 'dvm credit rail --clear' to let the amount rule decide.",
8657
+ instruction: true
8658
+ };
8659
+ case "credit_bound":
8660
+ return {
8661
+ text: "This credit is bound to that rail by the DVM, so it can only be topped up over it \u2014 drain it and open a new credit to change rails.",
8662
+ instruction: false
8663
+ };
8664
+ default:
8665
+ return { text: "Drop --rail to let the CLI pick.", instruction: true };
8666
+ }
8667
+ }
8668
+ function lowerFirst(value) {
8669
+ return value.charAt(0).toLowerCase() + value.slice(1);
8670
+ }
8671
+ function unavailableDisplay(args, rail) {
8672
+ const what = rail === "lightning" ? "over Lightning" : rail === "cashu" ? "with ecash" : rail === "x402" ? "over x402" : "";
8673
+ return `No way to fund a prepaid credit at ${args.ref.endpoint}${what ? ` ${what}` : ""} right now.`;
8674
+ }
8675
+ function sha256Hex(value) {
8676
+ return createHash4("sha256").update(value, "utf8").digest("hex");
8677
+ }
8678
+ function isRecord(value) {
8679
+ return !!value && typeof value === "object" && !Array.isArray(value);
8680
+ }
8681
+ function sleep(ms) {
8682
+ return new Promise((resolve) => setTimeout(resolve, ms));
8683
+ }
8684
+
8685
+ // src/lib/credit-policy.ts
8686
+ import { randomUUID as randomUUID3 } from "crypto";
8687
+ var TIER0_CAP_MICRO = 5e5;
8688
+ var TIER1_CAP_MICRO = 2e6;
8689
+ var TIER1_MIN_RECEIPTS = 3;
8690
+ var TIER2_MIN_RECEIPTS = 15;
8691
+ var TIER2_MIN_SPAN_MS = 7 * 24 * 60 * 60 * 1e3;
8692
+ var OPEN_TARGET_JOBS = 20;
8693
+ var CASHU_REFILL_TARGET_JOBS = 40;
8694
+ var TOPUP_THRESHOLD_FRACTION = 0.25;
8695
+ var DEFAULT_CREDIT_POSTURE = "suggest";
8696
+ var POCKET_TARGET_MICRO = 5e6;
8697
+ function trustTierFor(args) {
8698
+ const ladder = trustLadderFor(args);
8699
+ return { tier: ladder.tier, capMicro: ladder.capMicro ?? args.menu.max_micro };
8700
+ }
8701
+ function trustLadderFor(args) {
8702
+ const successes = completedReceiptsFor(
8703
+ args.receipts,
8704
+ args.endpoint,
8705
+ args.signingPubkey,
8706
+ args.identity
8883
8707
  );
8708
+ const tier0Cap = args.tier0CapMicro ?? TIER0_CAP_MICRO;
8709
+ const tier1Cap = args.tier1CapMicro ?? TIER1_CAP_MICRO;
8710
+ const builderMax = args.menu?.max_micro;
8711
+ const issued = successes.map((r) => r.receipt.issued_at * 1e3);
8712
+ const spanMs = issued.length > 1 ? Math.max(...issued) - Math.min(...issued) : 0;
8713
+ const count = successes.length;
8714
+ if (count >= TIER2_MIN_RECEIPTS && spanMs >= TIER2_MIN_SPAN_MS) {
8715
+ return {
8716
+ tier: 2,
8717
+ ...builderMax !== void 0 ? { capMicro: builderMax } : {},
8718
+ receipts: count,
8719
+ spanMs
8720
+ };
8721
+ }
8722
+ const toTier2 = {
8723
+ tier: 2,
8724
+ ...builderMax !== void 0 ? { capMicro: builderMax } : {},
8725
+ receiptsNeeded: Math.max(TIER2_MIN_RECEIPTS - count, 0),
8726
+ spanMs: TIER2_MIN_SPAN_MS
8727
+ };
8728
+ if (count >= TIER1_MIN_RECEIPTS) {
8729
+ return { tier: 1, capMicro: tier1Cap, receipts: count, spanMs, next: toTier2 };
8730
+ }
8884
8731
  return {
8885
- ...base,
8886
- status: "refused",
8887
- error,
8888
- display: "The original Lightning invoice expired before it was funded. It was cleared and will not be polled again.",
8889
- hint: error.hint
8732
+ tier: 0,
8733
+ capMicro: tier0Cap,
8734
+ receipts: count,
8735
+ spanMs,
8736
+ next: { tier: 1, capMicro: tier1Cap, receiptsNeeded: TIER1_MIN_RECEIPTS - count }
8890
8737
  };
8891
8738
  }
8892
- function lightningOffer(err) {
8893
- if (!isSettlementPending(err)) return void 0;
8894
- const offer = err.data?.lightning;
8895
- return offer;
8896
- }
8897
- function isSettlementPending(err) {
8898
- return err instanceof DvmError && err.code === "lightning_settlement_pending" && isRecord(err.data?.lightning);
8739
+ function effectiveCreditPosture(config, endpoint, identity) {
8740
+ const postures = perDvmEntriesFor(config, endpoint, identity).map(([, per2]) => per2.posture).filter(isCreditPosture);
8741
+ const per = postures.reduce(
8742
+ (strictest, posture) => strictest === void 0 || POSTURE_STRICTNESS[posture] > POSTURE_STRICTNESS[strictest] ? posture : strictest,
8743
+ void 0
8744
+ );
8745
+ if (per !== void 0) return { posture: per, source: "per_dvm" };
8746
+ const global = config?.posture;
8747
+ if (isCreditPosture(global)) return { posture: global, source: "global" };
8748
+ return { posture: DEFAULT_CREDIT_POSTURE, source: "default" };
8899
8749
  }
8900
- function isPermanentRefusal(err) {
8901
- if (!(err instanceof DvmError)) return false;
8902
- if (err.data?.retryable === false) return true;
8903
- return CREDIT_PERMANENT_REFUSALS.has(err.code) || boundRail(err) !== void 0;
8750
+ function effectiveFundingRail(config, endpoint, identity) {
8751
+ let conflict;
8752
+ if (endpoint !== void 0) {
8753
+ const pinned = perDvmEntriesFor(config, endpoint, identity).flatMap(
8754
+ ([origin, per]) => isCreditFundRail(per.fundingRail) ? [[origin, per.fundingRail]] : []
8755
+ );
8756
+ const rails = [...new Set(pinned.map(([, rail]) => rail))];
8757
+ if (rails.length > 1) conflict = { rails, endpoints: pinned.map(([origin]) => origin) };
8758
+ else if (rails.length === 1) return { rail: rails[0], source: "per_dvm" };
8759
+ }
8760
+ const global = config?.fundingRail;
8761
+ if (isCreditFundRail(global))
8762
+ return { rail: global, source: "global", ...conflict ? { conflict } : {} };
8763
+ return { source: "default", ...conflict ? { conflict } : {} };
8904
8764
  }
8905
- function boundRail(err) {
8906
- if (!(err instanceof DvmError)) return void 0;
8907
- const rail = err.data?.credit_rail;
8908
- return isCreditFundRail(rail) ? rail : void 0;
8765
+ function automaticCreditBoundRail(stored) {
8766
+ if (stored?.status !== "active" || stored.reconciliationPending) return void 0;
8767
+ return isCreditFundRail(stored.rail) ? stored.rail : void 0;
8909
8768
  }
8910
- function boundInstrument(err) {
8911
- if (!(err instanceof DvmError)) return void 0;
8912
- return typeof err.data?.credit_instrument === "string" ? err.data.credit_instrument : void 0;
8769
+ function trustOverrideCapMicro(args) {
8770
+ const raw2 = args.builderPubkey ? lookupOverride(args.config?.trustOverrides, args.builderPubkey) : void 0;
8771
+ if (!raw2) return void 0;
8772
+ if (raw2.trim().toLowerCase() === "builder-max") {
8773
+ return args.menu?.max_micro ?? Number.POSITIVE_INFINITY;
8774
+ }
8775
+ return parseCapMicro(raw2);
8913
8776
  }
8914
- function boundChannelId(err) {
8915
- if (!(err instanceof DvmError)) return void 0;
8916
- return typeof err.data?.credit_channel_id === "string" ? err.data.credit_channel_id : void 0;
8777
+ function tierCapOverrides(config) {
8778
+ const out = {};
8779
+ const tier0 = parseCapMicro(config?.tier0Cap);
8780
+ const tier1 = parseCapMicro(config?.tier1Cap);
8781
+ if (tier0 !== void 0) out.tier0CapMicro = tier0;
8782
+ if (tier1 !== void 0) out.tier1CapMicro = tier1;
8783
+ return out;
8917
8784
  }
8918
- function boundCreditId(err) {
8919
- if (!(err instanceof DvmError)) return void 0;
8920
- return typeof err.data?.bound_credit_id === "string" ? err.data.bound_credit_id : void 0;
8785
+ function sizeCreditMicro(args) {
8786
+ const target = (args.targetJobs ?? OPEN_TARGET_JOBS) * args.priceMicro;
8787
+ let size = Math.max(args.menu.min_micro, Math.min(target, args.capMicro));
8788
+ const residualCeiling = args.menu.max_micro + args.priceMicro;
8789
+ size = Math.min(size, residualCeiling);
8790
+ return Math.max(size, args.menu.min_micro);
8921
8791
  }
8922
- function isBindingRefusal(err) {
8923
- return boundInstrument(err) !== void 0 || boundRail(err) !== void 0;
8792
+ function refillTargetMicro(args) {
8793
+ const target = (args.targetJobs ?? CASHU_REFILL_TARGET_JOBS) * args.priceMicro;
8794
+ return Math.max(args.menu.min_micro, Math.min(target, args.capMicro, args.menu.max_micro));
8924
8795
  }
8925
- function lightningRefusal(base, err) {
8926
- const mapped = err instanceof DvmError ? err : void 0;
8927
- const required = boundRail(err);
8928
- if (required) {
8929
- return {
8930
- ...base,
8931
- status: "unavailable",
8932
- requiredRail: required,
8933
- error: mapped,
8934
- display: `This credit was opened over ${required}, so a Lightning top-up can't be added to it.`
8935
- };
8796
+ function planCreditForQuote(args) {
8797
+ const none = { mode: "none" };
8798
+ const menu = args.menu;
8799
+ if (!menu || !args.signingPubkey) return none;
8800
+ const posture = effectiveCreditPosture(args.config, args.endpoint, args.identity).posture;
8801
+ if (posture === "off") return none;
8802
+ if (menu.currency.toLowerCase() !== "usd") return none;
8803
+ const canCashu = menu.funding.includes("cashu");
8804
+ const canFloat = menu.funding.includes("lightning") && args.floatConnected === true;
8805
+ const canX402 = menu.funding.includes("x402") && args.x402Connected === true && offersX402CreditFunding(menu, args.x402Network);
8806
+ const canMpp = menu.funding.includes("tempo") && args.tempoConnected === true && offersTempoCreditFunding(menu);
8807
+ if (!canCashu && !canFloat && !canX402 && !canMpp) return none;
8808
+ if (!Number.isFinite(args.priceMsats) || args.priceMsats <= 0) return none;
8809
+ const stored = args.stored;
8810
+ if (stored?.reconciliationPending) return none;
8811
+ const refilling = stored?.status === "active" && stored.currency === menu.currency;
8812
+ const rateUnavailable = !Number.isFinite(args.btcRate) || args.btcRate <= 0;
8813
+ if (!refilling) {
8814
+ const completed = completedReceiptsFor(
8815
+ args.receipts,
8816
+ args.endpoint,
8817
+ args.signingPubkey,
8818
+ args.identity
8819
+ );
8820
+ if (completed.length === 0) return none;
8821
+ const identity = args.identity;
8822
+ if (rateUnavailable && identity && !completed.some((entry) => {
8823
+ const attested = attestedDvmIdentity(entry.builder);
8824
+ return attested?.dvmId === identity.dvmId && attested.builderPubkey === identity.builderPubkey;
8825
+ })) {
8826
+ return none;
8827
+ }
8936
8828
  }
8937
- if (mapped?.code === "below_rail_minimum") {
8938
- const minMicro = typeof mapped.data?.min_micro === "number" ? mapped.data.min_micro : void 0;
8829
+ if (rateUnavailable) {
8830
+ return posture === "auto" ? {
8831
+ mode: "none",
8832
+ fundingSkipped: {
8833
+ reason: "btc_rate_unavailable",
8834
+ operation: refilling ? "refill" : "open"
8835
+ }
8836
+ } : none;
8837
+ }
8838
+ const priceMicro = msatsToMicro(args.priceMsats, args.btcRate);
8839
+ if (priceMicro <= 0) return none;
8840
+ const tier = trustTierFor({
8841
+ receipts: args.receipts,
8842
+ endpoint: args.endpoint,
8843
+ signingPubkey: args.signingPubkey,
8844
+ ...args.identity ? { identity: args.identity } : {},
8845
+ menu,
8846
+ ...tierCapOverrides(args.config)
8847
+ });
8848
+ const targetJobs = targetJobsFor(args.config, args.endpoint, args.identity);
8849
+ const pin = effectiveFundingRail(args.config, args.endpoint, args.identity);
8850
+ const eligible = [
8851
+ ...canCashu ? ["cashu"] : [],
8852
+ ...canFloat ? ["lightning"] : [],
8853
+ ...canX402 ? ["x402"] : [],
8854
+ ...canMpp ? ["tempo"] : []
8855
+ ];
8856
+ const routeFor = (fundMicro2, boundRail2) => {
8857
+ const forced = boundRail2 ?? pin.rail;
8858
+ const pick = pickCreditFundRail({
8859
+ rails: eligible,
8860
+ amountMicro: fundMicro2,
8861
+ menu,
8862
+ ...args.x402Network ? { x402Network: args.x402Network } : {},
8863
+ ...forced ? { forced } : {}
8864
+ });
8865
+ const head = forced ?? pick.order[0];
8866
+ return { route: head === "cashu" ? "cashu" : "deferred", reason: pick.reason };
8867
+ };
8868
+ const pinned = pin.rail && pin.source !== "default" ? { rail: pin.rail, source: pin.source } : void 0;
8869
+ const defer = (reason, creditId, fundMicro2, targetMicro2, maxDepositMicro, forced = pinned) => ({
8870
+ mode: "none",
8871
+ deferred: {
8872
+ reason,
8873
+ creditId,
8874
+ fundMicro: fundMicro2,
8875
+ targetMicro: targetMicro2,
8876
+ maxDepositMicro,
8877
+ currency: menu.currency,
8878
+ menu,
8879
+ ...forced ? { pinnedRail: forced.rail, pinnedRailSource: forced.source } : {}
8880
+ }
8881
+ });
8882
+ if (refilling) {
8883
+ const targetMicro2 = refillTargetMicro({
8884
+ menu,
8885
+ priceMicro,
8886
+ capMicro: tier.capMicro,
8887
+ ...targetJobs !== void 0 ? { targetJobs } : {}
8888
+ });
8889
+ const boundRail2 = isCreditFundRail(stored.rail) ? stored.rail : void 0;
8890
+ const forcedRefill = boundRail2 ? { rail: boundRail2, source: "credit_bound" } : pinned;
8891
+ const railFloorMicro = forcedRefill?.rail === "lightning" && menu.lightning_min_micro !== void 0 ? Math.max(menu.min_micro, menu.lightning_min_micro) : menu.min_micro;
8892
+ const fundMicro2 = Math.min(
8893
+ Math.max(railFloorMicro, targetMicro2 - Math.max(stored.remainingMicro, 0) + priceMicro),
8894
+ menu.max_micro
8895
+ );
8896
+ if (posture === "suggest") {
8897
+ return {
8898
+ mode: "none",
8899
+ suppressed: { reason: "refill", fundMicro: fundMicro2, targetMicro: targetMicro2, currency: menu.currency }
8900
+ };
8901
+ }
8902
+ const refillRoute = routeFor(fundMicro2, boundRail2);
8903
+ if (refillRoute.route === "deferred") {
8904
+ return defer("refill", stored.creditId, fundMicro2, targetMicro2, fundMicro2, forcedRefill);
8905
+ }
8906
+ if (!canCashu) return none;
8939
8907
  return {
8940
- ...base,
8941
- status: "unavailable",
8942
- error: mapped,
8943
- display: typeof mapped.data?.display === "string" ? mapped.data.display : "This amount is below the minimum this DVM can receive over Lightning, so no invoice was issued.",
8944
- hint: "Refill the agent's ecash pocket with 'dvm wallet fund' and retry \u2014 ecash funds any amount here." + (minMicro !== void 0 ? ` Or fund at least $${formatUsd(minMicro / 1e6)} to use Lightning.` : "")
8908
+ mode: "topup",
8909
+ creditId: stored.creditId,
8910
+ drawId: randomUUID3(),
8911
+ ...fundAmounts(fundMicro2, args.btcRate),
8912
+ currency: menu.currency,
8913
+ targetMicro: targetMicro2,
8914
+ railReason: refillRoute.reason
8945
8915
  };
8946
8916
  }
8947
- if (mapped?.code === "lightning_unavailable") {
8917
+ const fundMicro = sizeCreditMicro({
8918
+ menu,
8919
+ priceMicro,
8920
+ capMicro: tier.capMicro,
8921
+ ...targetJobs !== void 0 ? { targetJobs: Math.min(targetJobs, OPEN_TARGET_JOBS) } : {}
8922
+ });
8923
+ const targetMicro = refillTargetMicro({
8924
+ menu,
8925
+ priceMicro,
8926
+ capMicro: tier.capMicro,
8927
+ ...targetJobs !== void 0 ? { targetJobs } : {}
8928
+ });
8929
+ if (posture === "suggest") {
8948
8930
  return {
8949
- ...base,
8950
- status: "unavailable",
8951
- error: mapped,
8952
- display: typeof mapped.data?.display === "string" ? mapped.data.display : "This DVM can't issue a Lightning invoice right now.",
8953
- hint: mapped.hint
8931
+ mode: "none",
8932
+ suppressed: { reason: "open", fundMicro, targetMicro, currency: menu.currency }
8954
8933
  };
8955
8934
  }
8935
+ const openRoute = routeFor(fundMicro);
8936
+ if (openRoute.route === "deferred") {
8937
+ return defer("open", randomUUID3(), fundMicro, targetMicro, fundMicro);
8938
+ }
8939
+ if (!canCashu) return none;
8956
8940
  return {
8957
- ...base,
8958
- status: "refused",
8959
- error: mapped,
8960
- // The DVM's own `display` when it wrote one; otherwise its message, so the
8961
- // composed prose is never thinner than the error it was built from —
8962
- // callers relay `display`, not `error.message`.
8963
- display: typeof mapped?.data?.display === "string" ? mapped.data.display : `The credit funding was refused${mapped ? ` (${mapped.code}). ${mapped.message}` : "."}`,
8964
- hint: mapped?.hint
8965
- };
8966
- }
8967
- function budgetRefusal(base, err) {
8968
- const mapped = err instanceof DvmError ? err : void 0;
8969
- const display = typeof mapped?.data?.display === "string" ? mapped.data.display : mapped?.message ?? "";
8970
- return {
8971
- ...base,
8972
- status: "refused",
8973
- error: mapped,
8974
- display: `${display} Nothing was charged, and any prepaid balance already at this DVM keeps working.`.trim(),
8975
- hint: mapped?.hint
8941
+ mode: "open",
8942
+ creditId: randomUUID3(),
8943
+ drawId: randomUUID3(),
8944
+ ...fundAmounts(fundMicro, args.btcRate),
8945
+ currency: menu.currency,
8946
+ targetMicro,
8947
+ railReason: openRoute.reason
8976
8948
  };
8977
8949
  }
8978
- function forcedRailHint(args, rail) {
8979
- const release = releaseForcedRailHint(args);
8980
- const advertised = args.menu?.funding;
8981
- if (advertised && !advertised.includes(rail)) {
8982
- return `This DVM advertises credit funding over ${advertised.join(", ") || "no rail at all"}, not ${rail}. ${release.text}`;
8983
- }
8984
- if (rail === "lightning") {
8985
- const connect = `No Lightning wallet is connected here, so there is nothing to pay an invoice with. Connect one with 'dvm wallet connect', piping the connection string in`;
8986
- return release.instruction ? `${connect}, or ${lowerFirst(release.text)}` : `${connect}. ${release.text}`;
8987
- }
8988
- if (rail === "x402") {
8989
- const wallet = loadX402Wallet();
8990
- if (!wallet) {
8991
- return `No x402 wallet is connected here, so there is nothing to sign a funding payment with. Run 'dvm wallet x402-connect' to create one, or add --key-file <path> to import a key you already hold. ${release.text}`;
8992
- }
8993
- const networks = x402CreditFundingNetworks(args.menu);
8994
- if (networks.length > 0) {
8995
- return `This DVM takes x402 credit funding on ${networks.map(x402NetworkLabel).join(", ")}, and the connected wallet is pinned to ${x402NetworkLabel(wallet.network)}. Re-pin it without handling its key: 'dvm wallet x402-network ${networks[0]}'. ${release.text}`;
8996
- }
8997
- if (args.menu) {
8998
- return `This DVM's funding menu names no x402 scheme, so there is no requirement to sign a payment against. ${release.text}`;
8950
+ function planProbeDraw(args) {
8951
+ const now = args.now ?? Date.now();
8952
+ const implicit = [...args.implicit ?? []].filter(
8953
+ (credit) => credit.status === "active" && credit.remainingMicro > 0 && credit.expiryMs > now
8954
+ ).sort((a, b) => a.expiryMs - b.expiryMs).at(0);
8955
+ if (implicit) {
8956
+ if (effectiveCreditPosture(args.config, implicit.endpoint, args.identity).posture === "off") {
8957
+ return {
8958
+ mode: "none",
8959
+ drawSkipped: {
8960
+ reason: "posture_off",
8961
+ creditId: implicit.creditId,
8962
+ remainingMicro: implicit.remainingMicro
8963
+ }
8964
+ };
8999
8965
  }
9000
- return `This DVM's funding menu hasn't been read here, so no x402 scheme is known to sign a payment against. ${release.text}`;
8966
+ return {
8967
+ mode: "draw",
8968
+ creditId: implicit.creditId,
8969
+ drawId: randomUUID3(),
8970
+ source: "implicit"
8971
+ };
9001
8972
  }
9002
- if (rail === "tempo") {
9003
- if (!hasConnectedTempoWallet(args)) {
9004
- const connect = `No Tempo wallet is connected here, so there is nothing to sign a funding payment with. Connect one with 'dvm wallet tempo-connect'`;
9005
- return release.instruction ? `${connect}, or ${lowerFirst(release.text)}` : `${connect}. ${release.text}`;
9006
- }
9007
- if (args.menu) {
9008
- return `This DVM advertises Tempo credit funding, but its funding menu names no supported Tempo charge or session intent, so this caller has no payment to build. ${release.text}`;
8973
+ const stored = args.stored;
8974
+ if (!stored) return { mode: "none" };
8975
+ const skip = (reason, extra) => ({
8976
+ mode: "none",
8977
+ drawSkipped: {
8978
+ reason,
8979
+ creditId: stored.creditId,
8980
+ remainingMicro: stored.remainingMicro,
8981
+ ...extra
9009
8982
  }
9010
- return `This DVM's funding menu hasn't been read here, so no Tempo charge or session intent is known to build a payment against. ${release.text}`;
8983
+ });
8984
+ if (effectiveCreditPosture(args.config, stored.endpoint, args.identity).posture === "off") {
8985
+ return skip("posture_off");
9011
8986
  }
9012
- if (!args.providerInfo) {
9013
- return `This DVM's '/v1/info' hasn't been read here, so no Cashu offer is known to attach a proof to. ${release.text}`;
8987
+ if (stored.status !== "active") return skip("not_active");
8988
+ if (stored.reconciliationPending) return skip("reconciliation_pending");
8989
+ if (stored.remainingMicro <= 0 && !stored.provisional) return skip("exhausted");
8990
+ if (stored.expiryMs > 0 && stored.expiryMs <= now) return skip("expired");
8991
+ if (stored.lastPriceMicro !== void 0 && args.btcRate > 0) {
8992
+ const ceilingMicro = msatsToMicro(args.drawCeilingMsats, args.btcRate);
8993
+ if (ceilingMicro > 0 && stored.lastPriceMicro > ceilingMicro) {
8994
+ return skip("price_above_ceiling", {
8995
+ ceilingMicro,
8996
+ lastPriceMicro: stored.lastPriceMicro,
8997
+ ceilingSource: args.drawCeilingSource
8998
+ });
8999
+ }
9014
9000
  }
9015
- return `This DVM publishes no Cashu offer for a proof to be locked to, so ecash can't fund it. ${release.text}`;
9001
+ return { mode: "draw", creditId: stored.creditId, drawId: randomUUID3(), source: "prepaid" };
9016
9002
  }
9017
- function releaseForcedRailHint(args) {
9018
- switch (args.railSource) {
9019
- case "per_dvm":
9020
- return {
9021
- text: `Run 'dvm credit rail --clear --dvm ${args.ref.endpoint}' to let the global pin or amount rule decide.`,
9022
- instruction: true
9023
- };
9024
- case "global":
9025
- return {
9026
- text: "Run 'dvm credit rail --clear' to let the amount rule decide.",
9027
- instruction: true
9028
- };
9029
- case "credit_bound":
9030
- return {
9031
- text: "This credit is bound to that rail by the DVM, so it can only be topped up over it \u2014 drain it and open a new credit to change rails.",
9032
- instruction: false
9033
- };
9034
- default:
9035
- return { text: "Drop --rail to let the CLI pick.", instruction: true };
9036
- }
9003
+ function needsTopup(balanceMicro, targetMicro) {
9004
+ return targetMicro > 0 && balanceMicro < targetMicro * TOPUP_THRESHOLD_FRACTION;
9037
9005
  }
9038
- function lowerFirst(value) {
9039
- return value.charAt(0).toLowerCase() + value.slice(1);
9006
+ function pocketTargetMicro(config) {
9007
+ return parseCapMicro(config?.pocketTarget) ?? POCKET_TARGET_MICRO;
9040
9008
  }
9041
- function unavailableDisplay(args, rail) {
9042
- const what = rail === "lightning" ? "over Lightning" : rail === "cashu" ? "with ecash" : rail === "x402" ? "over x402" : "";
9043
- return `No way to fund a prepaid credit at ${args.ref.endpoint}${what ? ` ${what}` : ""} right now.`;
9009
+ function needsPocketRefill(balanceMicro, targetMicro) {
9010
+ return needsTopup(balanceMicro, targetMicro);
9044
9011
  }
9045
- function microToSats2(micro, btcRate) {
9046
- return Math.ceil(usdToMsats(micro / 1e6, btcRate) / 1e3);
9012
+ function perDvmEntriesFor(config, endpoint, identity) {
9013
+ const perDvm = config?.perDvm;
9014
+ if (!perDvm) return [];
9015
+ const key = normalizeCreditEndpoint(endpoint);
9016
+ return Object.entries(perDvm).filter(
9017
+ ([origin, per]) => normalizeCreditEndpoint(origin) === key || identity !== void 0 && per.dvmId === identity.dvmId && per.builderPubkey?.trim().toLowerCase() === identity.builderPubkey
9018
+ );
9047
9019
  }
9048
- function sha256Hex(value) {
9049
- return createHash4("sha256").update(value, "utf8").digest("hex");
9020
+ var POSTURE_STRICTNESS = { auto: 0, suggest: 1, off: 2 };
9021
+ function fundAmounts(fundMicro, btcRate) {
9022
+ const fundSats = microToSats(fundMicro, btcRate);
9023
+ return { fundMicro, fundSats, fundMsats: fundSats * 1e3 };
9050
9024
  }
9051
- function isRecord(value) {
9052
- return !!value && typeof value === "object" && !Array.isArray(value);
9025
+ function completedReceiptsFor(receipts, endpoint, signingPubkey, identity) {
9026
+ const key = normalizeCreditEndpoint(endpoint);
9027
+ const requester = signingPubkey?.trim().toLowerCase();
9028
+ if (!requester) return [];
9029
+ const mine = (entry) => entry.receipt.requester_pubkey?.trim().toLowerCase() === requester && entry.receipt.outcome === "completed";
9030
+ const here = [];
9031
+ let anchor = identity;
9032
+ for (const entry of receipts) {
9033
+ if (normalizeCreditEndpoint(entry.endpoint) !== key || !mine(entry)) continue;
9034
+ const verdict = verifyJobReceipt({ receipt: entry.receipt, builder: entry.builder });
9035
+ if (verdict.status !== "verified" && verdict.status !== "verified_unattested") continue;
9036
+ if (identity !== void 0) {
9037
+ const attested = verdict.checks.attestation === "ok" ? attestedDvmIdentity(entry.builder) : void 0;
9038
+ if (attested?.dvmId !== identity.dvmId || attested.builderPubkey !== identity.builderPubkey) {
9039
+ continue;
9040
+ }
9041
+ }
9042
+ here.push(entry);
9043
+ if (anchor === void 0 && verdict.checks.attestation === "ok") {
9044
+ anchor = attestedDvmIdentity(entry.builder);
9045
+ }
9046
+ }
9047
+ if (!anchor) return here;
9048
+ const elsewhere = receipts.filter((entry) => {
9049
+ if (normalizeCreditEndpoint(entry.endpoint) === key || !mine(entry)) return false;
9050
+ if (entry.receipt.dvm_id !== anchor.dvmId) return false;
9051
+ const verdict = verifyJobReceipt({ receipt: entry.receipt, builder: entry.builder });
9052
+ if (verdict.checks.attestation !== "ok") return false;
9053
+ const attested = attestedDvmIdentity(entry.builder);
9054
+ return attested?.dvmId === anchor.dvmId && attested.builderPubkey === anchor.builderPubkey;
9055
+ });
9056
+ return [...here, ...elsewhere];
9053
9057
  }
9054
- function sleep(ms) {
9055
- return new Promise((resolve) => setTimeout(resolve, ms));
9058
+ function targetJobsFor(config, endpoint, identity) {
9059
+ const targets = perDvmEntriesFor(config, endpoint, identity).map(([, per]) => per.targetJobs).filter((jobs) => typeof jobs === "number" && Number.isFinite(jobs));
9060
+ if (targets.length > 0) return Math.min(...targets);
9061
+ return config?.targetJobs;
9062
+ }
9063
+ function lookupOverride(overrides, builderPubkey) {
9064
+ if (!overrides) return void 0;
9065
+ const key = builderPubkey.trim().toLowerCase();
9066
+ for (const [pubkey, cap] of Object.entries(overrides)) {
9067
+ if (pubkey.trim().toLowerCase() === key) return cap;
9068
+ }
9069
+ return void 0;
9070
+ }
9071
+ function parseCapMicro(cap) {
9072
+ if (!cap) return void 0;
9073
+ const match = /^\$?(\d+(?:\.\d{1,6})?)$/.exec(cap.trim());
9074
+ if (!match) return void 0;
9075
+ const micro = Math.round(Number(match[1]) * 1e6);
9076
+ return micro > 0 ? micro : void 0;
9056
9077
  }
9057
9078
 
9058
9079
  // src/lib/messages/types.ts
@@ -9438,7 +9459,7 @@ async function probeX402(x402, timeoutMs) {
9438
9459
  let network;
9439
9460
  let address;
9440
9461
  try {
9441
- const { loadX402Wallet: loadX402Wallet2, getX402Balance: getX402Balance2, ensureAddress: ensureAddress2, usdcContractByCaip2: usdcContractByCaip22 } = await import("../x402-R6HPBY5D.js");
9462
+ const { loadX402Wallet: loadX402Wallet2, getX402Balance: getX402Balance2, ensureAddress: ensureAddress2, usdcContractByCaip2: usdcContractByCaip22 } = await import("../x402-FT4S4JS4.js");
9442
9463
  const wallet = loadX402Wallet2();
9443
9464
  if (!wallet) {
9444
9465
  const storedKey = x402.privateKey;
@@ -9490,7 +9511,7 @@ async function probeMpp(mpp, timeoutMs) {
9490
9511
  if (!mpp) return null;
9491
9512
  if (mpp.method === "tempo") {
9492
9513
  try {
9493
- const { connectedTempoAccount: connectedTempoAccount2, getTempoUsdcBalance: getTempoUsdcBalance2, resolveTempoNetwork: resolveTempoNetwork2 } = await import("../tempo-wallet-B5XXV3YH.js");
9514
+ const { connectedTempoAccount: connectedTempoAccount2, getTempoUsdcBalance: getTempoUsdcBalance2, resolveTempoNetwork: resolveTempoNetwork2 } = await import("../tempo-wallet-X2GGAZ6X.js");
9494
9515
  const account = await connectedTempoAccount2();
9495
9516
  if (!account) {
9496
9517
  return {
@@ -9635,10 +9656,12 @@ export {
9635
9656
  StepCache,
9636
9657
  TEMPO_CHAIN_ID,
9637
9658
  TEMPO_CHANNELS_FILE,
9659
+ TEMPO_CHARGE_PENDING_STALE_MS,
9638
9660
  TEMPO_EXIT_KEY_MISSING_HINT,
9639
9661
  TEMPO_MODERATO_CHAIN_ID,
9640
9662
  TEMPO_MODERATO_RPC,
9641
9663
  TEMPO_PATH_USD_MODERATO,
9664
+ TEMPO_SESSION_UPDATED_REASON,
9642
9665
  WALLET_FILE,
9643
9666
  WALLET_VERSION,
9644
9667
  X402EnvValidationError,
@@ -9664,6 +9687,9 @@ export {
9664
9687
  addPayment,
9665
9688
  advanceTempoChannelExit,
9666
9689
  amountBoundsVerdict,
9690
+ appendBudgetSpend,
9691
+ appendFundingReceipt,
9692
+ appendReceipt,
9667
9693
  applyImplicitReceiptCredit,
9668
9694
  applyReceiptCredit,
9669
9695
  applyRetryToPool,
@@ -9671,8 +9697,12 @@ export {
9671
9697
  asProviderTextOpt,
9672
9698
  assertAgentMnemonicReplaceable,
9673
9699
  assertFloatBudget,
9700
+ assertNoRivalX402Voucher,
9674
9701
  assertNutSupport,
9675
9702
  assertSupportedX402Network,
9703
+ assertTempoSessionChallengeWithinAsk,
9704
+ assertTempoSessionDepositWithinCap,
9705
+ assertTempoSessionVoucherWithinAsk,
9676
9706
  assertTempoWalletIdle,
9677
9707
  assertValidBudget,
9678
9708
  assertX402ChannelPayer,
@@ -9685,8 +9715,10 @@ export {
9685
9715
  availableRails,
9686
9716
  backfillTrustAnchor,
9687
9717
  backupSecretFile,
9718
+ bindCashuTokenToSubmissionContext,
9688
9719
  bindDiagnosticCapabilityAuthority,
9689
9720
  bindDiagnosticIdentityAuthority,
9721
+ breakdownSince,
9690
9722
  buildAdminAuthHeader,
9691
9723
  buildAdminChallenge,
9692
9724
  buildAttestation,
@@ -9703,16 +9735,21 @@ export {
9703
9735
  canonicalRequestPath,
9704
9736
  canonicaliseForSigning,
9705
9737
  canonicalize,
9738
+ capFor,
9706
9739
  captureFundingReceipt,
9707
9740
  captureTransportReceipt,
9708
9741
  cashOutFromMint,
9709
9742
  cashuSubmissionBindingFingerprint,
9743
+ cashuSubmissionBindingFromContext,
9710
9744
  chainIdFromCaip2,
9745
+ checkBudgetForLedger,
9711
9746
  checkMeltQuoteState,
9712
9747
  checkMintHealth,
9713
9748
  checkReceiptAttestation,
9714
9749
  clearCreditSelection,
9715
9750
  clearPendingDrain,
9751
+ clearPendingFunding,
9752
+ clearPendingTempoChargeFunding,
9716
9753
  clearPendingX402ExactFunding,
9717
9754
  clearPendingX402Funding,
9718
9755
  clearRefillRailUnavailable,
@@ -9730,16 +9767,22 @@ export {
9730
9767
  costAnchor,
9731
9768
  coveringMintsForSpend,
9732
9769
  createDiagnosticCapabilityAuthority,
9770
+ createMintQuoteRetryState,
9733
9771
  createMonotonicClock,
9734
9772
  createNoopLogger,
9735
9773
  createStdoutLogger,
9774
+ createTempoChannelStore,
9736
9775
  createTempoCloseCredential,
9776
+ createTempoCreditSession,
9777
+ createTempoSessionPaymentFetch,
9737
9778
  createX402BatchPayment,
9738
9779
  currentContext,
9739
9780
  decodePayment,
9740
9781
  decodePaymentRequiredHeader,
9741
9782
  deleteAgentMnemonic,
9742
9783
  deriveAgentMasterKeypair,
9784
+ deriveAgentRefundSubkey,
9785
+ deriveAgentRefundSubkeyPubkey,
9743
9786
  deriveBuilderLockKeypair,
9744
9787
  deriveBuilderLockPubkey,
9745
9788
  deriveIdentityFromPrivkey,
@@ -9834,6 +9877,7 @@ export {
9834
9877
  isPromptMessage,
9835
9878
  isPublicRequestId,
9836
9879
  isResumable,
9880
+ isSignableExactRequirement,
9837
9881
  isSignedJobReceipt,
9838
9882
  isStablecoinFundRail,
9839
9883
  isTerminalJobStatus,
@@ -9856,9 +9900,11 @@ export {
9856
9900
  listX402Channels,
9857
9901
  loadAgentWallet,
9858
9902
  loadAllJobs,
9903
+ loadBudgetLedger,
9859
9904
  loadConfig,
9860
9905
  loadConfiguredMints,
9861
9906
  loadCredit,
9907
+ loadCreditX402Binding,
9862
9908
  loadFloat,
9863
9909
  loadIdentities,
9864
9910
  loadIdentitySecret,
@@ -9868,11 +9914,14 @@ export {
9868
9914
  loadPendingFunding,
9869
9915
  loadReceipt,
9870
9916
  loadReceipts,
9917
+ loadResumableTempoChargeFunding,
9871
9918
  loadResumableX402ExactFunding,
9872
9919
  loadResumableX402Funding,
9873
9920
  loadTempoChannelAssociation,
9921
+ loadTempoChannelAssociationByKey,
9874
9922
  loadTempoChannelController,
9875
9923
  loadTempoChannelEntry,
9924
+ loadWalletConfiguredMints,
9876
9925
  loadX402Wallet,
9877
9926
  loggers,
9878
9927
  looksLikeMnemonic,
@@ -9883,7 +9932,9 @@ export {
9883
9932
  markJobTerminal,
9884
9933
  markRefillDrainNoticed,
9885
9934
  markRefillRailUnavailable,
9935
+ maxAdvertisedMicro,
9886
9936
  maxKeysetDenomination,
9937
+ meltAmountBounds,
9887
9938
  microToSats,
9888
9939
  missingDleqRecoveryDiagnostic,
9889
9940
  mnemonicFingerprint,
@@ -9900,7 +9951,11 @@ export {
9900
9951
  nwcDedupWindowMs,
9901
9952
  nwcRejectionDisplay,
9902
9953
  nwcTimeoutMs,
9954
+ offersTempoCreditCharge,
9903
9955
  offersTempoCreditFunding,
9956
+ offersTempoCreditSession,
9957
+ offersX402CreditChannel,
9958
+ offersX402CreditFlavour,
9904
9959
  offersX402CreditFunding,
9905
9960
  parseBudget,
9906
9961
  parseCreditMenu,
@@ -9917,6 +9972,7 @@ export {
9917
9972
  paymentRequiredV2FromV1,
9918
9973
  pendingDrainById,
9919
9974
  pendingDrainFor,
9975
+ persistTempoOpenCredential,
9920
9976
  pickCreditFundRail,
9921
9977
  pinConfigSnapshot,
9922
9978
  pinDimension,
@@ -9937,6 +9993,7 @@ export {
9937
9993
  processX402BatchCorrection,
9938
9994
  processX402BatchResponse,
9939
9995
  processX402BatchResponseForWallet,
9996
+ progress,
9940
9997
  projectDiagnosticFields,
9941
9998
  projectEventName,
9942
9999
  projectSpanRow,
@@ -9945,6 +10002,7 @@ export {
9945
10002
  railLabelAtSentenceStart,
9946
10003
  randomTraceId,
9947
10004
  raw,
10005
+ readAgentMnemonic,
9948
10006
  readCompletedCreditHistory,
9949
10007
  readFundValuation,
9950
10008
  readX402ChannelOnChain,
@@ -9966,6 +10024,8 @@ export {
9966
10024
  recordFundResponse,
9967
10025
  recordFundingMenuSnapshot,
9968
10026
  recordPendingDrain,
10027
+ recordPendingFunding,
10028
+ recordPendingTempoChargeFunding,
9969
10029
  recordPendingX402ExactFunding,
9970
10030
  recordPendingX402Funding,
9971
10031
  recordRefusedDrawBalance,
@@ -9976,6 +10036,7 @@ export {
9976
10036
  redactUrlsInText,
9977
10037
  refFromJob,
9978
10038
  refundX402BatchChannel,
10039
+ rehydrateOutputData,
9979
10040
  releaseAgentReserve,
9980
10041
  removeX402Wallet,
9981
10042
  renderReceiptHuman,
@@ -9996,10 +10057,14 @@ export {
9996
10057
  restoreProofsFromOutputs,
9997
10058
  retireConfigBackup,
9998
10059
  retirePendingX402Funding,
10060
+ retireTempoChannel,
10061
+ retireTempoChannelScope,
10062
+ retryMintQuoteOperation,
9999
10063
  rollbackConfig,
10000
10064
  rpcEndpointLine,
10001
10065
  rpcEndpointSuffix,
10002
10066
  saveAgentWallet,
10067
+ saveBudgetLedger,
10003
10068
  saveIdentities,
10004
10069
  saveJob,
10005
10070
  saveLedger,
@@ -10009,6 +10074,7 @@ export {
10009
10074
  setEmitter,
10010
10075
  setOutputTraceId,
10011
10076
  settleDrainEvidence,
10077
+ sha256hex,
10012
10078
  shellQuoteArg,
10013
10079
  signAttestation,
10014
10080
  signDrainReceipt,
@@ -10017,6 +10083,7 @@ export {
10017
10083
  signReceipt,
10018
10084
  signRequestData,
10019
10085
  signedRequestDomain,
10086
+ spendEntry,
10020
10087
  spendableAt,
10021
10088
  staleRateNotice,
10022
10089
  statusSnapshot,
@@ -10026,16 +10093,27 @@ export {
10026
10093
  sweepPendingMints,
10027
10094
  sweepPendingSubmissions,
10028
10095
  sweepStaleInFlight,
10096
+ tempoAccountFromConfig,
10029
10097
  tempoBalanceCheckHint,
10030
10098
  tempoChannelControlledBy,
10099
+ tempoChargeCredentialSettleable,
10031
10100
  tempoCredentialAmountMicro,
10101
+ tempoCredentialValidBefore,
10102
+ tempoDescriptorController,
10032
10103
  tempoKeyImportRoutes,
10104
+ tempoSessionAssociationByKey,
10033
10105
  tempoSessionChallenge,
10106
+ tempoSessionChallengeChannelScope,
10034
10107
  tempoSessionChallengeMicro,
10108
+ tempoSessionCredentialChannelScope,
10109
+ tempoSessionCredentialCumulativeMicro,
10110
+ tempoSessionCumulativeCapMicro,
10111
+ tempoSessionPriorCumulativeMicro,
10035
10112
  tempoTokenReference,
10036
10113
  tempoWrongAssetHint,
10037
10114
  tierCapOverrides,
10038
10115
  toAgentProof,
10116
+ toCashuProofs,
10039
10117
  toFloatError,
10040
10118
  toLockPubkey,
10041
10119
  transportErrorCode,
@@ -10072,6 +10150,7 @@ export {
10072
10150
  walletExists,
10073
10151
  warmFxSnapshot,
10074
10152
  weakestReceiptVerdict,
10153
+ windowEnd,
10075
10154
  windowStart,
10076
10155
  withIdentitiesLock,
10077
10156
  withInitRetry,
@@ -10080,6 +10159,8 @@ export {
10080
10159
  withTraceContext,
10081
10160
  withWalletLock,
10082
10161
  withX402ChannelLock,
10162
+ withholdsTempoCreditSession,
10163
+ withinAdvertisedTolerance,
10083
10164
  writeAgentMnemonic,
10084
10165
  writeIdentity,
10085
10166
  x402ChannelBalanceUnreconciledAt,
@@ -10087,8 +10168,11 @@ export {
10087
10168
  x402ChannelReservation,
10088
10169
  x402CreditChannelNetworks,
10089
10170
  x402CreditExactNetworks,
10171
+ x402CreditFlavour,
10172
+ x402CreditFundingNetworks,
10090
10173
  x402ExactFundingDead,
10091
10174
  x402FlavourForInstrument,
10175
+ x402InstrumentForFlavour,
10092
10176
  x402NetworkByCaip2,
10093
10177
  x402NetworkBySlug,
10094
10178
  x402NetworkLabel,
@@ -10104,5 +10188,7 @@ export {
10104
10188
  x402ReservationRelease,
10105
10189
  x402SupportedNetworksHint,
10106
10190
  x402VoucherPendingRefusal,
10191
+ x402VoucherPendingState,
10192
+ x402WalletFromConfig,
10107
10193
  x402WrongAssetHint
10108
10194
  };