@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.
- package/dist/{chunk-JH3SX3LY.js → chunk-6XF3RVVX.js} +1 -1
- package/dist/{chunk-RLTSCKRI.js → chunk-BPYMEMQX.js} +50 -3
- package/dist/{chunk-IWQHUSMO.js → chunk-CXCQLOOK.js} +143 -2
- package/dist/{chunk-LE74UEV7.js → chunk-EZXAJ6EI.js} +64 -53
- package/dist/{chunk-FI4CST2D.js → chunk-FWGWLPXF.js} +4 -6
- package/dist/{chunk-HHBDBMEO.js → chunk-GY66THYI.js} +21 -23
- package/dist/{chunk-UOCZXO2Z.js → chunk-JEVLNAO3.js} +10 -5
- package/dist/{chunk-OQA7IHI4.js → chunk-KMED3H3A.js} +10 -6
- package/dist/{chunk-PAIM66YA.js → chunk-NFMOPYBA.js} +47 -37
- package/dist/{chunk-B2IROPQF.js → chunk-O2OTZ5GP.js} +40 -15
- package/dist/{chunk-FYRR2UEZ.js → chunk-OML32F6I.js} +1 -1
- package/dist/{chunk-7ZUKL4IV.js → chunk-RA2WIX7G.js} +5 -5
- package/dist/{chunk-PLG4XER6.js → chunk-S3OY533W.js} +1 -1
- package/dist/{chunk-GFY5FRX3.js → chunk-SZVXOQ75.js} +5 -12
- package/dist/{chunk-MZRJR2CW.js → chunk-WBOKPC37.js} +22 -3
- package/dist/{content-retention-BTGSQRfP.d.ts → content-retention-DHyLdA6b.d.ts} +1 -1
- package/dist/{credit-ledger-MI2H7NEC.js → credit-ledger-MCUXYKSJ.js} +2 -3
- package/dist/{credit-menu-BAsrRu3z.d.ts → credit-menu-DjnkeEUU.d.ts} +4 -4
- package/dist/{fx-BgDmyTdd.d.ts → fx-zMWFzfrU.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/internal/caller.d.ts +1359 -851
- package/dist/internal/caller.js +1000 -914
- package/dist/internal/index.js +1 -1
- package/dist/internal/server.d.ts +7 -7
- package/dist/internal/server.js +9 -11
- package/dist/{job-store-BVeF5_Jq.d.ts → job-store-DdLXBpol.d.ts} +1 -1
- package/dist/{lightning-backend-40fIzz1W.d.ts → lightning-backend-Bw-qtAbt.d.ts} +4 -2
- package/dist/{memory-credit-ledger-W2BAUYJP.js → memory-credit-ledger-SBSMGMDX.js} +3 -4
- package/dist/server/index.d.ts +10 -10
- package/dist/server/index.js +14 -17
- package/dist/{step-cache-CPdH1hei.d.ts → step-cache-2MkvenJn.d.ts} +1 -1
- package/dist/tempo-lifecycle-GJ77YKVY.js +15 -0
- package/dist/{tempo-wallet-B5XXV3YH.js → tempo-wallet-X2GGAZ6X.js} +4 -4
- package/dist/testing/index.d.ts +3 -3
- package/dist/{usd-AWlrwlM5.d.ts → usd-DI3d3gTK.d.ts} +1 -1
- package/dist/{x402-R6HPBY5D.js → x402-FT4S4JS4.js} +8 -10
- package/package.json +3 -2
- package/dist/chunk-CZULM42S.js +0 -143
- package/dist/chunk-LIRAHP7B.js +0 -53
- package/dist/tempo-lifecycle-ZBBLFBMH.js +0 -15
package/dist/internal/caller.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
185
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
441
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
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
|
-
|
|
3311
|
-
|
|
3333
|
+
var STABLECOIN_FUND_RAILS = ["x402", "tempo"];
|
|
3334
|
+
function isStablecoinFundRail(rail) {
|
|
3335
|
+
return STABLECOIN_FUND_RAILS.includes(rail);
|
|
3312
3336
|
}
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
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
|
|
3319
|
-
return
|
|
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
|
|
3325
|
-
return flavour === X402_BATCH_SETTLEMENT_SCHEME ? "
|
|
3344
|
+
function fundingModeForX402Flavour(flavour) {
|
|
3345
|
+
return flavour === X402_BATCH_SETTLEMENT_SCHEME ? "reusable" : "one-shot";
|
|
3326
3346
|
}
|
|
3327
|
-
function
|
|
3328
|
-
if (
|
|
3329
|
-
|
|
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
|
|
3333
|
-
|
|
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
|
|
3336
|
-
return
|
|
3360
|
+
function usableMicro(value) {
|
|
3361
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
3337
3362
|
}
|
|
3338
|
-
|
|
3339
|
-
|
|
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
|
|
3342
|
-
|
|
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
|
|
3345
|
-
|
|
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/
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
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
|
|
3364
|
-
|
|
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
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
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
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
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
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
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
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
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
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
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
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
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
|
|
3432
|
-
|
|
3433
|
-
|
|
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
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
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 ?? (() =>
|
|
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 (!
|
|
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(
|
|
5986
|
-
const signedDeadline =
|
|
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
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
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/
|
|
6430
|
-
|
|
6431
|
-
|
|
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
|
-
|
|
6436
|
-
|
|
6437
|
-
return STABLECOIN_FUND_RAILS.includes(rail);
|
|
6040
|
+
function offersX402CreditChannel(menu, network) {
|
|
6041
|
+
return matchesNetwork(x402CreditChannelNetworks(menu), network);
|
|
6438
6042
|
}
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
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
|
|
6444
|
-
return
|
|
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
|
|
6447
|
-
return flavour === X402_BATCH_SETTLEMENT_SCHEME ? "
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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(
|
|
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
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
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
|
|
8893
|
-
|
|
8894
|
-
const
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
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
|
|
8901
|
-
|
|
8902
|
-
if (
|
|
8903
|
-
|
|
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
|
|
8906
|
-
if (
|
|
8907
|
-
|
|
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
|
|
8911
|
-
|
|
8912
|
-
|
|
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
|
|
8915
|
-
|
|
8916
|
-
|
|
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
|
|
8919
|
-
|
|
8920
|
-
|
|
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
|
|
8923
|
-
|
|
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
|
|
8926
|
-
const
|
|
8927
|
-
const
|
|
8928
|
-
if (
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
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 (
|
|
8938
|
-
|
|
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
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8950
|
-
|
|
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
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
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
|
|
8979
|
-
const
|
|
8980
|
-
const
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
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
|
|
8966
|
+
return {
|
|
8967
|
+
mode: "draw",
|
|
8968
|
+
creditId: implicit.creditId,
|
|
8969
|
+
drawId: randomUUID3(),
|
|
8970
|
+
source: "implicit"
|
|
8971
|
+
};
|
|
9001
8972
|
}
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
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
|
-
|
|
8983
|
+
});
|
|
8984
|
+
if (effectiveCreditPosture(args.config, stored.endpoint, args.identity).posture === "off") {
|
|
8985
|
+
return skip("posture_off");
|
|
9011
8986
|
}
|
|
9012
|
-
if (
|
|
9013
|
-
|
|
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
|
|
9001
|
+
return { mode: "draw", creditId: stored.creditId, drawId: randomUUID3(), source: "prepaid" };
|
|
9016
9002
|
}
|
|
9017
|
-
function
|
|
9018
|
-
|
|
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
|
|
9039
|
-
return
|
|
9006
|
+
function pocketTargetMicro(config) {
|
|
9007
|
+
return parseCapMicro(config?.pocketTarget) ?? POCKET_TARGET_MICRO;
|
|
9040
9008
|
}
|
|
9041
|
-
function
|
|
9042
|
-
|
|
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
|
|
9046
|
-
|
|
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
|
-
|
|
9049
|
-
|
|
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
|
|
9052
|
-
|
|
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
|
|
9055
|
-
|
|
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-
|
|
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-
|
|
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
|
};
|