@dvmkit/sdk 0.1.1-rc.7 → 0.1.3-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +14 -0
  2. package/dist/chunk-2K7E3N2D.js +1709 -0
  3. package/dist/{chunk-Y6W5ZZTW.js → chunk-3ZHMQCYP.js} +666 -3031
  4. package/dist/{chunk-TKA6ZP4M.js → chunk-4B56DEEV.js} +50 -426
  5. package/dist/{chunk-X3IKFWJA.js → chunk-6BQM7TOW.js} +4 -1
  6. package/dist/chunk-BIFLRKMO.js +87 -0
  7. package/dist/chunk-BQ2NMWKE.js +160 -0
  8. package/dist/{chunk-LDTWX7JW.js → chunk-BTZY7VPH.js} +13 -1
  9. package/dist/{chunk-FJDCFHW5.js → chunk-C6JHBLMW.js} +3 -81
  10. package/dist/{chunk-EXHBXA4U.js → chunk-DBCLBYHP.js} +13 -1
  11. package/dist/chunk-EPNDZ5DH.js +1010 -0
  12. package/dist/{chunk-P4RUVDU7.js → chunk-H2MEFVH6.js} +12 -141
  13. package/dist/chunk-KXZUCCEY.js +142 -0
  14. package/dist/{chunk-6JZIX5WW.js → chunk-LLXV32HA.js} +82 -5
  15. package/dist/{chunk-LWUR4CGG.js → chunk-MLRCSJYX.js} +11 -3
  16. package/dist/{chunk-2ABMGUDS.js → chunk-NFRM5QYP.js} +83 -10
  17. package/dist/chunk-RW5LP57K.js +44 -0
  18. package/dist/chunk-YDIYXGYL.js +384 -0
  19. package/dist/{chunk-JGGI65I3.js → chunk-Z4BNLUZF.js} +1 -150
  20. package/dist/{credit-ledger-ED6JXKVD.js → credit-ledger-2DFQHNLB.js} +2 -2
  21. package/dist/{credit-menu-BrTfAr7l.d.ts → credit-menu-C7zAJElJ.d.ts} +1632 -1946
  22. package/dist/{fx-H6K6qcH1.d.ts → fx-BF_SG2i0.d.ts} +1 -1
  23. package/dist/index.d.ts +7 -6
  24. package/dist/index.js +8 -4
  25. package/dist/internal/caller.d.ts +10429 -0
  26. package/dist/internal/caller.js +9957 -0
  27. package/dist/internal/index.d.ts +2 -10816
  28. package/dist/internal/index.js +2 -10130
  29. package/dist/internal/server.d.ts +404 -0
  30. package/dist/internal/server.js +202 -0
  31. package/dist/job-store-Bn23V3QU.d.ts +576 -0
  32. package/dist/lightning-backend-C04nH94l.d.ts +367 -0
  33. package/dist/{memory-credit-ledger-XJ5VQEVP.js → memory-credit-ledger-OP24Z2KO.js} +3 -3
  34. package/dist/{payout-reporter-3UB5WRCV.js → payout-reporter-RG6XNGPI.js} +1 -1
  35. package/dist/{postgres-job-store-J5F4GUWU.js → postgres-job-store-TAONYLIF.js} +1 -1
  36. package/dist/{revenue-reporter-JIKUPXOK.js → revenue-reporter-XXSU5KVB.js} +1 -1
  37. package/dist/server/index.d.ts +27 -11
  38. package/dist/server/index.js +93 -69
  39. package/dist/{job-store-DOSfYnLX.d.ts → step-cache-3cT4Shk0.d.ts} +38 -585
  40. package/dist/{tempo-session-store-DALMRIWN.js → tempo-session-store-2JNOKJGX.js} +2 -2
  41. package/dist/testing/index.d.ts +16 -4
  42. package/dist/testing/index.js +7 -3
  43. package/dist/{usd-Civ738b3.d.ts → usd-BnuXoFl5.d.ts} +1 -1
  44. package/dist/wallet-CJC8lwxx.d.ts +29 -0
  45. package/dist/{x402-FTG2GRAQ.js → x402-T2C5MX3T.js} +6 -3
  46. package/package.json +7 -2
  47. package/dist/{chunk-RU7SXHLO.js → chunk-UP2F5RRT.js} +3 -3
@@ -1,3 +1,65 @@
1
+ import {
2
+ MemoryProcessedPaymentStore,
3
+ ProcessedPaymentReplayError
4
+ } from "./chunk-DMNLFNTW.js";
5
+ import {
6
+ createX402FacilitatorAuthHeaders,
7
+ createX402FacilitatorClient,
8
+ settleWithFacilitator,
9
+ verifyWithFacilitator
10
+ } from "./chunk-BQ2NMWKE.js";
11
+ import {
12
+ MemoryCreditLedger
13
+ } from "./chunk-DBCLBYHP.js";
14
+ import {
15
+ SIGNED_ENVELOPE_FIELDS,
16
+ SignedRequestError,
17
+ humanizeZodError,
18
+ humanizeZodIssues,
19
+ signedRequestInput,
20
+ zodIssuesOf
21
+ } from "./chunk-H2MEFVH6.js";
22
+ import {
23
+ JobCancelledError,
24
+ MemoryJobStore,
25
+ UNCAPPABLE_ASK_TOTAL,
26
+ abortJob,
27
+ accumulateAskedTopUp,
28
+ createInstrumentedFetch,
29
+ fromJobRecord,
30
+ isJobRetentionStore,
31
+ isReceiptIssuingStore,
32
+ isStreamableJobStore,
33
+ isTerminal,
34
+ isYieldMessage,
35
+ localMessage,
36
+ providerMessage,
37
+ toJobRecord
38
+ } from "./chunk-4B56DEEV.js";
39
+ import {
40
+ ADMIN_AUTH_CLOCK_SKEW_SECONDS,
41
+ ADMIN_AUTH_NONCE_TTL_SECONDS,
42
+ ADMIN_STATE_UNAVAILABLE,
43
+ TEMPO_SESSION_UPDATED_REASON,
44
+ X402_BATCH_CHANNEL_ABI,
45
+ attestedDvmIdentity,
46
+ buildAdminChallenge,
47
+ checkMintHealth,
48
+ computeResultHash,
49
+ createMonotonicClock,
50
+ getWallet,
51
+ isPublicRequestId,
52
+ isX402SettlementReconciliationReason,
53
+ normaliseMintUrl,
54
+ parseTraceparent,
55
+ receiptPubkeyFromSecret,
56
+ signDrainReceipt,
57
+ signFundingReceipt,
58
+ signReceipt,
59
+ toLockPubkey,
60
+ verifyCashuToken,
61
+ verifyChallenge
62
+ } from "./chunk-2K7E3N2D.js";
1
63
  import {
2
64
  X402_BATCH_SETTLEMENT_SCHEME,
3
65
  X402_DEFAULT_FACILITATOR,
@@ -6,78 +68,38 @@ import {
6
68
  X402_VERSION,
7
69
  buildPaymentRequiredV2,
8
70
  buildPaymentRequirements,
9
- createX402FacilitatorAuthHeaders,
10
- createX402FacilitatorClient,
11
71
  decodePayment,
12
72
  encodePaymentRequiredHeader,
13
73
  encodeSettleResponseHeader,
14
74
  exactEvmAuthorization,
15
75
  paymentRequiredV2FromV1,
16
- settleWithFacilitator,
17
- verifyWithFacilitator,
18
76
  x402NetworkByCaip2,
19
77
  x402NetworkToCaip2
20
- } from "./chunk-JGGI65I3.js";
78
+ } from "./chunk-Z4BNLUZF.js";
21
79
  import {
22
80
  IMPLICIT_CREDIT_TTL_MS,
23
- costAnchor,
24
81
  formatStaleness,
25
- formatUsd,
26
82
  msatsToUsd,
27
83
  msatsToUsdc,
28
84
  usdcToMsats
29
85
  } from "./chunk-5URG56JJ.js";
30
86
  import {
31
- AGENT_MNEMONIC_FILE,
32
- BUILDER_CONFIG_DIR,
33
- BUILDER_FILE,
34
- BUILDER_KEY_FILE,
35
- DvmError,
36
- WALLET_FILE,
37
- ensureConfigDir,
38
87
  resolveRpcOverride
39
88
  } from "./chunk-MKI6OVW4.js";
40
- import {
41
- MemoryCreditLedger
42
- } from "./chunk-EXHBXA4U.js";
43
89
  import {
44
90
  RAIL_REFUNDABLE,
45
- SIGNED_ENVELOPE_FIELDS,
46
- SignedRequestError,
47
91
  canonicalRequestPath,
48
- canonicaliseForSigning,
49
92
  canonicalize,
50
- humanizeZodError,
51
- humanizeZodIssues,
52
- isZodSchema,
53
- signedRequestInput,
54
- zodIssuesOf
55
- } from "./chunk-P4RUVDU7.js";
93
+ isZodSchema
94
+ } from "./chunk-KXZUCCEY.js";
56
95
  import {
57
- JobCancelledError,
58
- MemoryJobStore,
59
96
  StepCache,
60
- UNCAPPABLE_ASK_TOTAL,
61
- abortJob,
62
- accumulateAskedTopUp,
63
97
  createConsoleLogger,
64
- createInstrumentedFetch,
65
- fromJobRecord,
66
- isReceiptIssuingStore,
67
- isStreamableJobStore,
68
- isTerminal,
69
- isYieldMessage,
70
- localMessage,
71
98
  loggers,
72
99
  pinDimension,
73
- providerMessage,
74
- toJobRecord,
75
100
  withSpan,
76
101
  withTraceContext
77
- } from "./chunk-TKA6ZP4M.js";
78
- import {
79
- callerLoggers
80
- } from "./chunk-66HGCPBU.js";
102
+ } from "./chunk-YDIYXGYL.js";
81
103
  import {
82
104
  FxRateUnavailableError,
83
105
  InvalidCurrencyError,
@@ -107,7 +129,10 @@ import {
107
129
  isNonChannelBitcoinRail,
108
130
  netOwedSats,
109
131
  x402SettlementPending
110
- } from "./chunk-LWUR4CGG.js";
132
+ } from "./chunk-MLRCSJYX.js";
133
+ import {
134
+ withSdkInitLock
135
+ } from "./chunk-S3XAHZQY.js";
111
136
  import {
112
137
  withPostgresRecoveryContext
113
138
  } from "./chunk-C3MTFLC6.js";
@@ -116,18 +141,6 @@ import {
116
141
  challengeMeta,
117
142
  isTempoInsufficientFundsError
118
143
  } from "./chunk-5GFED3GJ.js";
119
- import {
120
- MemoryProcessedPaymentStore,
121
- ProcessedPaymentReplayError
122
- } from "./chunk-DMNLFNTW.js";
123
- import {
124
- withSdkInitLock
125
- } from "./chunk-S3XAHZQY.js";
126
-
127
- // src/lib/cashu/lock-pubkey.ts
128
- function toLockPubkey(raw) {
129
- return raw.trim().toLowerCase();
130
- }
131
144
 
132
145
  // src/sdk/server/lock-pubkey-store.ts
133
146
  async function initLockPubkeyTable(db) {
@@ -849,12 +862,12 @@ async function buildCreditMenu(args) {
849
862
  method.intent === "session" ? "tempo_session" : "tempo_charge"
850
863
  )
851
864
  );
852
- const x402Schemes = (args.x402Schemes ?? []).filter(
865
+ const x402Schemes = args.currency === "usd" ? (args.x402Schemes ?? []).filter(
853
866
  (scheme) => stablecoinCreditInstrumentAllowed(
854
867
  args.config.allowOneShotStablecoin,
855
868
  scheme.scheme === "batch-settlement" ? "x402_batch_settlement" : "x402_exact"
856
869
  )
857
- );
870
+ ) : [];
858
871
  const funding = args.funding.filter((rail) => {
859
872
  if (rail === "tempo") return tempoMethods.length > 0;
860
873
  if (rail === "x402") return x402Schemes.length > 0;
@@ -999,11 +1012,6 @@ function warnOnce(event, message) {
999
1012
  console.warn(JSON.stringify({ level: "warn", event, message }));
1000
1013
  }
1001
1014
 
1002
- // src/lib/x402/types.ts
1003
- function isX402SettlementReconciliationReason(value) {
1004
- return value === "settlement_not_on_chain" || value === "chain_unreachable" || value === "settlement_unbookmarked" || value === "facilitator_channel_state_unavailable";
1005
- }
1006
-
1007
1015
  // src/sdk/server/funding.ts
1008
1016
  import { createHash as createHash2 } from "crypto";
1009
1017
  function resolvePriceFiat(args) {
@@ -1092,9 +1100,11 @@ function fundedMicroFor(priceFiat, paidMsats, requiredMsats) {
1092
1100
  if (paidMsats === requiredMsats) return priceFiat.amountMicro;
1093
1101
  return Math.round(priceFiat.amountMicro * paidMsats / requiredMsats);
1094
1102
  }
1095
- function x402RequiredUsdcMicro(priceFiat, requiredMsats, btcUsdRate) {
1096
- if (priceFiat?.currency === "usd") return priceFiat.amountMicro;
1097
- return msatsToUsdc(requiredMsats, btcUsdRate);
1103
+ function x402RequiredUsdcMicro(priceFiat) {
1104
+ if (priceFiat.currency !== "usd") {
1105
+ throw new Error(`x402 is not offered for ${priceFiat.currency}-priced DVMs`);
1106
+ }
1107
+ return priceFiat.amountMicro;
1098
1108
  }
1099
1109
  function x402SettledShare(quoted, requiredUsdcMicro, settledUsdcMicro) {
1100
1110
  if (requiredUsdcMicro <= 0 || settledUsdcMicro === requiredUsdcMicro) return quoted;
@@ -1229,889 +1239,67 @@ function rateUnavailable() {
1229
1239
  );
1230
1240
  }
1231
1241
 
1232
- // src/lib/secret-backup.ts
1233
- import { existsSync, readFileSync, writeFileSync } from "fs";
1234
- function backupSecretFile(file, nextContent) {
1235
- const content = existsSync(file) ? readFileSync(file) : null;
1236
- if (content === null) return null;
1237
- if (content.toString("utf-8").trim() === nextContent?.trim()) return null;
1238
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1239
- for (let n = 0; n < 100; n++) {
1240
- const backup = n === 0 ? `${file}.${stamp}.bak` : `${file}.${stamp}-${String(n)}.bak`;
1241
- try {
1242
- writeFileSync(backup, content, { mode: 384, flag: "wx" });
1243
- return backup;
1244
- } catch (err) {
1245
- if (err.code !== "EEXIST") throw err;
1246
- }
1247
- }
1248
- throw new Error(`Could not create a backup of ${file}: 100 candidate paths already exist.`);
1249
- }
1250
-
1251
- // src/lib/wallet-agent/agent-mnemonic-file.ts
1252
- import {
1253
- chmodSync,
1254
- existsSync as existsSync2,
1255
- readFileSync as readFileSync2,
1256
- renameSync,
1257
- unlinkSync,
1258
- writeFileSync as writeFileSync2
1259
- } from "fs";
1260
- function readAgentMnemonic() {
1261
- if (!existsSync2(AGENT_MNEMONIC_FILE)) return null;
1262
- const raw = readFileSync2(AGENT_MNEMONIC_FILE, "utf-8");
1263
- return raw.trim().toLowerCase().split(/\s+/).join(" ");
1264
- }
1265
- function assertAgentMnemonicReplaceable(opts = {}) {
1266
- if (existsSync2(AGENT_MNEMONIC_FILE) && !opts.force) {
1267
- throw new DvmError(
1268
- "agent_mnemonic_exists",
1269
- `An agent wallet recovery mnemonic already exists at ${AGENT_MNEMONIC_FILE}.`,
1270
- "Re-run with --force to replace it. WARNING: funds locked to keys derived from the current words become unrecoverable without a backup. The replaced mnemonic is copied to a timestamped .bak sibling."
1271
- );
1272
- }
1273
- }
1274
- function writeAgentMnemonic(mnemonic, opts = {}) {
1275
- const normalised = mnemonic.trim().toLowerCase().split(/\s+/).join(" ");
1276
- assertAgentMnemonicReplaceable(opts);
1277
- ensureConfigDir();
1278
- const backupFile = backupSecretFile(AGENT_MNEMONIC_FILE, normalised);
1279
- const tmp = `${AGENT_MNEMONIC_FILE}.tmp`;
1280
- writeFileSync2(tmp, normalised + "\n", { mode: 384 });
1281
- try {
1282
- renameSync(tmp, AGENT_MNEMONIC_FILE);
1283
- } catch (err) {
1284
- try {
1285
- unlinkSync(tmp);
1286
- } catch {
1287
- }
1288
- throw err;
1289
- }
1290
- try {
1291
- chmodSync(AGENT_MNEMONIC_FILE, 384);
1292
- } catch {
1293
- }
1294
- return { backupFile };
1295
- }
1296
- function deleteAgentMnemonic() {
1297
- if (!existsSync2(AGENT_MNEMONIC_FILE)) return { backupFile: null };
1298
- const backupFile = backupSecretFile(AGENT_MNEMONIC_FILE);
1299
- unlinkSync(AGENT_MNEMONIC_FILE);
1300
- return { backupFile };
1301
- }
1302
-
1303
- // src/lib/wallet-agent/mnemonic.ts
1242
+ // src/sdk/server/x402-batch-settlement.ts
1304
1243
  import { createHash as createHash3 } from "crypto";
1305
- import { HDKey } from "@scure/bip32";
1244
+ import { isDeepStrictEqual } from "util";
1245
+ import { x402ResourceServer } from "@x402/core/server";
1306
1246
  import {
1307
- generateMnemonic,
1308
- mnemonicToEntropy,
1309
- mnemonicToSeedSync,
1310
- validateMnemonic
1311
- } from "@scure/bip39";
1312
- import { wordlist } from "@scure/bip39/wordlists/english.js";
1313
- var MNEMONIC_PURPOSE_INDEX = 1789;
1314
- var AGENT_MASTER_ROLE_INDEX = 1;
1315
- var MNEMONIC_STRENGTH_BITS = 128;
1316
- function agentMasterKeypairPath() {
1317
- return `m/${MNEMONIC_PURPOSE_INDEX}'/${AGENT_MASTER_ROLE_INDEX}'`;
1318
- }
1319
- function generateAgentMnemonic() {
1320
- return generateMnemonic(wordlist, MNEMONIC_STRENGTH_BITS);
1321
- }
1322
- function deriveAgentMasterKeypair(mnemonic) {
1323
- const seed = mnemonicToSeedSync(mnemonic);
1324
- const hd = HDKey.fromMasterSeed(seed);
1325
- const child = hd.derive(agentMasterKeypairPath());
1326
- if (!child.privateKey || !child.publicKey) {
1327
- throw new Error("Failed to derive agent master keypair from mnemonic");
1328
- }
1329
- return {
1330
- privkey: Buffer.from(child.privateKey).toString("hex"),
1331
- pubkey: Buffer.from(child.publicKey).toString("hex")
1332
- };
1333
- }
1334
- function validateAgentMnemonic(mnemonic) {
1335
- const words = mnemonic.trim().toLowerCase().split(/\s+/);
1336
- if (words.length !== 12) {
1337
- return { ok: false, reason: "wrong_length", wordCount: words.length };
1338
- }
1339
- for (let i = 0; i < words.length; i++) {
1340
- if (!wordlist.includes(words[i])) {
1341
- return { ok: false, reason: "unknown_word", badWordIndex: i + 1, badWord: words[i] };
1342
- }
1343
- }
1344
- if (!validateMnemonic(words.join(" "), wordlist)) {
1345
- return { ok: false, reason: "checksum" };
1346
- }
1347
- return { ok: true };
1348
- }
1349
- function looksLikeMnemonic(input) {
1350
- return /\s/.test(input.trim());
1351
- }
1352
- function mnemonicFingerprint(mnemonic) {
1353
- const normalised = mnemonic.trim().toLowerCase().split(/\s+/).join(" ");
1354
- const entropy = mnemonicToEntropy(normalised, wordlist);
1355
- return createHash3("sha256").update(entropy).digest("hex");
1356
- }
1247
+ BATCH_SETTLEMENT_ADDRESS as BATCH_SETTLEMENT_ADDRESS2,
1248
+ isBatchSettlementDepositPayload as isBatchSettlementDepositPayload2,
1249
+ isBatchSettlementRefundPayload as isBatchSettlementRefundPayload2,
1250
+ isBatchSettlementVoucherPayload
1251
+ } from "@x402/evm";
1252
+ import {
1253
+ BatchSettlementEvmScheme as BatchSettlementEvmScheme2,
1254
+ InMemoryChannelStorage
1255
+ } from "@x402/evm/batch-settlement/server";
1256
+ import { createPublicClient as createPublicClient3, http as http3 } from "viem";
1257
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
1357
1258
 
1358
- // src/lib/wallet-agent/store.ts
1359
- import { existsSync as existsSync3, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
1360
- import lockfile from "proper-lockfile";
1361
- var LOCK_FILE = `${WALLET_FILE}.lock`;
1362
- var DEFAULT_LOCK_STALE_MS = 3e4;
1363
- var DEFAULT_LOCK_RETRIES = 30;
1364
- var DEFAULT_LOCK_RETRY_INTERVAL_MS = 1e3;
1365
- var WALLET_VERSION = 8;
1366
- var RECENT_SPENDS_CAP = 10;
1367
- var DEFAULT_AGENT_MINT_URL = "https://mint.coinos.io";
1368
- function loadAgentWallet() {
1369
- if (!existsSync3(WALLET_FILE)) return null;
1370
- const raw = readFileSync3(WALLET_FILE, "utf-8");
1371
- let parsed;
1372
- try {
1373
- parsed = JSON.parse(raw);
1374
- } catch (err) {
1375
- throw new DvmError(
1376
- "wallet_corrupt",
1377
- `Wallet file at ${WALLET_FILE} is not valid JSON.`,
1378
- `Restore a backup or delete the file. Underlying error: ${err instanceof Error ? err.message : String(err)}`
1379
- );
1380
- }
1381
- if (typeof parsed !== "object" || parsed === null || !("version" in parsed)) {
1382
- throw new DvmError(
1383
- "wallet_corrupt",
1384
- `Wallet file at ${WALLET_FILE} is missing a version field.`,
1385
- "Restore a backup or run: dvm wallet init --force"
1386
- );
1387
- }
1388
- const version = parsed.version;
1389
- if (version === 1) {
1390
- return migrateV7ToV8(
1391
- migrateV6ToV7(migrateV5ToV6(migrateV4ToV5(migrateV1ToV4(parsed))))
1392
- );
1393
- }
1394
- if (version === 2) {
1395
- return migrateV7ToV8(
1396
- migrateV6ToV7(
1397
- migrateV5ToV6(
1398
- migrateV4ToV5(migrateV3ToV4(migrateV2ToV3(parsed)))
1399
- )
1400
- )
1401
- );
1402
- }
1403
- if (version === 3) {
1404
- return migrateV7ToV8(
1405
- migrateV6ToV7(migrateV5ToV6(migrateV4ToV5(migrateV3ToV4(parsed))))
1406
- );
1407
- }
1408
- if (version === 4) {
1409
- return migrateV7ToV8(
1410
- migrateV6ToV7(migrateV5ToV6(migrateV4ToV5(parsed)))
1411
- );
1412
- }
1413
- if (version === 5) {
1414
- return migrateV7ToV8(migrateV6ToV7(migrateV5ToV6(parsed)));
1415
- }
1416
- if (version === 6) {
1417
- return migrateV7ToV8(migrateV6ToV7(parsed));
1418
- }
1419
- if (version === 7) {
1420
- return migrateV7ToV8(parsed);
1421
- }
1422
- if (version !== WALLET_VERSION) {
1423
- throw new DvmError(
1424
- "wallet_unknown_version",
1425
- `Unknown wallet version ${String(version)} (this CLI understands version ${String(WALLET_VERSION)}).`,
1426
- "Upgrade dvm CLI: a newer version wrote this wallet."
1427
- );
1428
- }
1429
- return parsed;
1430
- }
1431
- function saveAgentWallet(wallet) {
1432
- ensureConfigDir();
1433
- const tmp = `${WALLET_FILE}.tmp`;
1434
- const data = JSON.stringify(wallet, null, 2) + "\n";
1435
- writeFileSync3(tmp, data, { mode: 384 });
1436
- try {
1437
- renameSync2(tmp, WALLET_FILE);
1438
- } catch (err) {
1439
- try {
1440
- unlinkSync2(tmp);
1441
- } catch {
1442
- }
1443
- throw err;
1259
+ // src/sdk/server/x402-self-relay.ts
1260
+ import { x402Facilitator } from "@x402/core/facilitator";
1261
+ import { BatchSettlementEvmScheme } from "@x402/evm/batch-settlement/facilitator";
1262
+ import { createPublicClient, createWalletClient, http } from "viem";
1263
+ import { privateKeyToAccount } from "viem/accounts";
1264
+ import { base } from "viem/chains";
1265
+ var X402_SELF_RELAY_DEFAULT_LOW_BALANCE_WEI = 1000000000000000n;
1266
+ var X402_SELF_RELAY_BALANCE_INTERVAL_MS = 5 * 60 * 1e3;
1267
+ function createX402SelfRelay(opts) {
1268
+ if (!opts.config.rpcUrl.trim()) throw new Error("x402 self-relay requires an explicit RPC URL");
1269
+ if (opts.config.lowBalanceWei !== void 0 && opts.config.lowBalanceWei <= 0n) {
1270
+ throw new Error("x402 self-relay lowBalanceWei must be positive");
1444
1271
  }
1445
- }
1446
- function walletExists() {
1447
- return existsSync3(WALLET_FILE);
1448
- }
1449
- async function withWalletLock(fn, opts = {}) {
1450
- ensureConfigDir();
1451
- const stale = opts.stale ?? DEFAULT_LOCK_STALE_MS;
1452
- const retries = opts.retries ?? DEFAULT_LOCK_RETRIES;
1453
- const interval = opts.retryIntervalMs ?? DEFAULT_LOCK_RETRY_INTERVAL_MS;
1454
- let release;
1455
- try {
1456
- release = await lockfile.lock(WALLET_FILE, {
1457
- lockfilePath: LOCK_FILE,
1458
- stale,
1459
- realpath: false,
1460
- retries: { retries, factor: 1, minTimeout: interval, maxTimeout: interval }
1461
- });
1462
- } catch (err) {
1463
- throw new DvmError(
1464
- "wallet_locked",
1465
- `Could not acquire the agent wallet lock at ${LOCK_FILE} within the retry budget.`,
1466
- `Another dvm CLI may be writing to ${WALLET_FILE}. Wait a moment and retry. If no other dvm CLI is running, remove ${LOCK_FILE} and try again.`,
1467
- { lock_file: LOCK_FILE, underlying: err instanceof Error ? err.message : String(err) }
1468
- );
1272
+ if (opts.config.balanceCheckIntervalMs !== void 0 && (!Number.isFinite(opts.config.balanceCheckIntervalMs) || opts.config.balanceCheckIntervalMs <= 0)) {
1273
+ throw new Error("x402 self-relay balanceCheckIntervalMs must be positive");
1469
1274
  }
1470
- try {
1471
- return await fn();
1472
- } finally {
1275
+ const chain = relayChain(opts.network);
1276
+ const account = privateKeyToAccount(opts.config.privateKey);
1277
+ const transport = http(opts.config.rpcUrl);
1278
+ const publicClient = createPublicClient({ chain, transport });
1279
+ const walletClient = createWalletClient({ account, chain, transport });
1280
+ const observeRpcFailure = async (operation) => {
1473
1281
  try {
1474
- await release();
1475
- } catch {
1282
+ return await operation();
1283
+ } catch (error) {
1284
+ const reason = classifyX402SelfRelayRpcFailure(error);
1285
+ if (reason) await reportRpcFailure(opts.config, account.address, opts.network, reason);
1286
+ throw error;
1476
1287
  }
1477
- }
1478
- }
1479
- function effectiveMints(wallet) {
1480
- return wallet.mints.length > 0 ? wallet.mints : [DEFAULT_AGENT_MINT_URL];
1481
- }
1482
- function effectiveFundingMint(mints) {
1483
- return mints.length > 0 ? { url: mints[0], source: "configured" } : { url: DEFAULT_AGENT_MINT_URL, source: "fallback" };
1484
- }
1485
- function migrateV1ToV4(parsed) {
1486
- const proofs = Array.isArray(parsed.proofs) ? parsed.proofs : [];
1487
- const pending = Array.isArray(parsed.pending_mints) ? parsed.pending_mints : [];
1488
- if (proofs.length > 0 || pending.length > 0) {
1489
- throw new DvmError(
1490
- "wallet_unknown_version",
1491
- `Cannot migrate v1 wallet at ${WALLET_FILE}: contains ${proofs.length} proofs and ${pending.length} pending mints under the legacy schema.`,
1492
- "v1 wallets predate the fund flow and are expected to be empty. If this wallet was somehow populated, restore a backup or run: dvm wallet init --force"
1493
- );
1494
- }
1495
- return {
1496
- version: 4,
1497
- lock_privkey: String(parsed.recovery_privkey ?? parsed.lock_privkey),
1498
- lock_pubkey: String(parsed.recovery_pubkey ?? parsed.lock_pubkey),
1499
- mints: Array.isArray(parsed.mints) ? parsed.mints : [],
1500
- proofs: [],
1501
- pending_mints: [],
1502
- recent_spends: [],
1503
- created_at: typeof parsed.created_at === "string" ? parsed.created_at : (/* @__PURE__ */ new Date()).toISOString()
1504
- };
1505
- }
1506
- function migrateV2ToV3(parsed) {
1507
- return {
1508
- ...parsed,
1509
- version: 3,
1510
- recent_spends: []
1511
- };
1512
- }
1513
- function migrateV3ToV4(parsed) {
1514
- return {
1515
- version: 4,
1516
- lock_privkey: String(parsed.recovery_privkey ?? parsed.lock_privkey),
1517
- lock_pubkey: String(parsed.recovery_pubkey ?? parsed.lock_pubkey),
1518
- mints: Array.isArray(parsed.mints) ? parsed.mints : [],
1519
- proofs: Array.isArray(parsed.proofs) ? parsed.proofs : [],
1520
- pending_mints: Array.isArray(parsed.pending_mints) ? parsed.pending_mints : [],
1521
- recent_spends: Array.isArray(parsed.recent_spends) ? parsed.recent_spends : [],
1522
- created_at: typeof parsed.created_at === "string" ? parsed.created_at : (/* @__PURE__ */ new Date()).toISOString()
1523
- };
1524
- }
1525
- function migrateV4ToV5(parsed) {
1526
- return {
1527
- version: 5,
1528
- lock_privkey: String(parsed.lock_privkey),
1529
- lock_pubkey: String(parsed.lock_pubkey),
1530
- mints: Array.isArray(parsed.mints) ? parsed.mints : [],
1531
- proofs: Array.isArray(parsed.proofs) ? parsed.proofs : [],
1532
- pending_mints: Array.isArray(parsed.pending_mints) ? parsed.pending_mints : [],
1533
- pending_submissions: Array.isArray(parsed.pending_submissions) ? parsed.pending_submissions : [],
1534
- recent_spends: Array.isArray(parsed.recent_spends) ? parsed.recent_spends : [],
1535
- created_at: typeof parsed.created_at === "string" ? parsed.created_at : (/* @__PURE__ */ new Date()).toISOString()
1536
- };
1537
- }
1538
- function migrateV5ToV6(parsed) {
1539
- return {
1540
- version: 6,
1541
- lock_privkey: String(parsed.lock_privkey),
1542
- lock_pubkey: String(parsed.lock_pubkey),
1543
- mnemonic_fingerprint: typeof parsed.mnemonic_fingerprint === "string" ? parsed.mnemonic_fingerprint : null,
1544
- mints: Array.isArray(parsed.mints) ? parsed.mints : [],
1545
- proofs: Array.isArray(parsed.proofs) ? parsed.proofs : [],
1546
- nut13_counters: isCounterMap(parsed.nut13_counters) ? parsed.nut13_counters : {},
1547
- pending_mints: Array.isArray(parsed.pending_mints) ? parsed.pending_mints : [],
1548
- pending_submissions: Array.isArray(parsed.pending_submissions) ? parsed.pending_submissions : [],
1549
- recent_spends: Array.isArray(parsed.recent_spends) ? parsed.recent_spends : [],
1550
- created_at: typeof parsed.created_at === "string" ? parsed.created_at : (/* @__PURE__ */ new Date()).toISOString()
1551
- };
1552
- }
1553
- function migrateV6ToV7(parsed) {
1554
- return {
1555
- version: 7,
1556
- lock_privkey: String(parsed.lock_privkey),
1557
- lock_pubkey: String(parsed.lock_pubkey),
1558
- mnemonic_fingerprint: typeof parsed.mnemonic_fingerprint === "string" ? parsed.mnemonic_fingerprint : null,
1559
- mints: Array.isArray(parsed.mints) ? parsed.mints : [],
1560
- proofs: Array.isArray(parsed.proofs) ? parsed.proofs : [],
1561
- nut13_counters: isCounterMap(parsed.nut13_counters) ? parsed.nut13_counters : {},
1562
- pending_mints: Array.isArray(parsed.pending_mints) ? parsed.pending_mints : [],
1563
- pending_submissions: Array.isArray(parsed.pending_submissions) ? parsed.pending_submissions : [],
1564
- recent_spends: Array.isArray(parsed.recent_spends) ? parsed.recent_spends : [],
1565
- created_at: typeof parsed.created_at === "string" ? parsed.created_at : (/* @__PURE__ */ new Date()).toISOString()
1566
- };
1567
- }
1568
- function migrateV7ToV8(parsed) {
1569
- return {
1570
- version: WALLET_VERSION,
1571
- lock_privkey: String(parsed.lock_privkey),
1572
- lock_pubkey: String(parsed.lock_pubkey),
1573
- mnemonic_fingerprint: typeof parsed.mnemonic_fingerprint === "string" ? parsed.mnemonic_fingerprint : null,
1574
- mints: Array.isArray(parsed.mints) ? parsed.mints : [],
1575
- proofs: Array.isArray(parsed.proofs) ? parsed.proofs : [],
1576
- nut13_counters: isCounterMap(parsed.nut13_counters) ? parsed.nut13_counters : {},
1577
- pending_mints: Array.isArray(parsed.pending_mints) ? parsed.pending_mints : [],
1578
- pending_melts: Array.isArray(parsed.pending_melts) ? parsed.pending_melts : [],
1579
- pending_submissions: Array.isArray(parsed.pending_submissions) ? parsed.pending_submissions : [],
1580
- recent_spends: Array.isArray(parsed.recent_spends) ? parsed.recent_spends : [],
1581
- created_at: typeof parsed.created_at === "string" ? parsed.created_at : (/* @__PURE__ */ new Date()).toISOString()
1582
1288
  };
1583
- }
1584
- function isCounterMap(value) {
1585
- if (typeof value !== "object" || value === null) return false;
1586
- for (const inner of Object.values(value)) {
1587
- if (typeof inner !== "object" || inner === null) return false;
1588
- for (const n of Object.values(inner)) {
1589
- if (typeof n !== "number" || !Number.isFinite(n)) return false;
1590
- }
1591
- }
1592
- return true;
1593
- }
1594
-
1595
- // src/lib/wallet-agent/nut13-counters.ts
1596
- var PersistentCounterSource = class {
1597
- constructor(mintUrl) {
1598
- this.mintUrl = mintUrl;
1599
- }
1600
- mintUrl;
1601
- /**
1602
- * Reserve `n` counters for `keysetId`. Returns `{ start, count: n }`
1603
- * and bumps the persisted `next` to `start + n` under the wallet lock.
1604
- *
1605
- * Per cashu-ts contract, `n === 0` MUST NOT mutate state and is a
1606
- * pure peek — we honour that by returning `{ start, count: 0 }`
1607
- * without acquiring the lock.
1608
- */
1609
- async reserve(keysetId, n) {
1610
- if (n === 0) {
1611
- const wallet = loadAgentWallet();
1612
- const current = wallet?.nut13_counters[this.mintUrl]?.[keysetId] ?? 0;
1613
- return { start: current, count: 0 };
1614
- }
1615
- return withWalletLock(() => {
1616
- const wallet = this.loadOrThrow();
1617
- const current = wallet.nut13_counters[this.mintUrl]?.[keysetId] ?? 0;
1618
- saveAgentWallet(this.withCounter(wallet, keysetId, current + n));
1619
- return { start: current, count: n };
1620
- });
1621
- }
1622
- /**
1623
- * Monotonic bump: ensure the persisted `next` for `keysetId` is at
1624
- * least `minNext`. No-op if the wallet is already ahead. Used by
1625
- * cashu-ts when the mint reports `outputs_already_signed` — the
1626
- * losing wallet rolls forward past the colliding counter.
1627
- */
1628
- async advanceToAtLeast(keysetId, minNext) {
1629
- await withWalletLock(() => {
1630
- const wallet = this.loadOrThrow();
1631
- const current = wallet.nut13_counters[this.mintUrl]?.[keysetId] ?? 0;
1632
- if (current >= minNext) return;
1633
- saveAgentWallet(this.withCounter(wallet, keysetId, minNext));
1634
- });
1635
- }
1636
- /**
1637
- * Snapshot the per-keyset `next` map for this mint. Useful for
1638
- * `dvm wallet show` and test assertions; cashu-ts itself does not
1639
- * depend on `snapshot` being implemented.
1640
- */
1641
- snapshot() {
1642
- const wallet = loadAgentWallet();
1643
- const counters = wallet?.nut13_counters[this.mintUrl] ?? {};
1644
- const out = {};
1645
- for (const [keyset, next] of Object.entries(counters)) {
1646
- if (typeof next === "number") out[keyset] = next;
1647
- }
1648
- return Promise.resolve(out);
1649
- }
1650
- /**
1651
- * Hard-set the persisted `next` for `keysetId`. Used by
1652
- * `dvm wallet restore` to re-anchor counters after walking
1653
- * `batchRestore` per keyset. cashu-ts also exposes this on
1654
- * `wallet.counters.setNext`.
1655
- */
1656
- async setNext(keysetId, next) {
1657
- await withWalletLock(() => {
1658
- const wallet = this.loadOrThrow();
1659
- saveAgentWallet(this.withCounter(wallet, keysetId, next));
1660
- });
1661
- }
1662
- loadOrThrow() {
1663
- const wallet = loadAgentWallet();
1664
- if (!wallet) {
1665
- throw new DvmError(
1666
- "wallet_missing",
1667
- "NUT-13 counter persistence requires an agent wallet at ~/.dvm/wallet.json.",
1668
- "Run `dvm wallet init` or `dvm wallet recover --file <path>` (or pipe the mnemonic via stdin) first."
1669
- );
1670
- }
1671
- return wallet;
1672
- }
1673
- withCounter(wallet, keysetId, next) {
1674
- return {
1675
- ...wallet,
1676
- nut13_counters: {
1677
- ...wallet.nut13_counters,
1678
- [this.mintUrl]: {
1679
- ...wallet.nut13_counters[this.mintUrl] ?? {},
1680
- [keysetId]: next
1681
- }
1682
- }
1683
- };
1684
- }
1685
- };
1686
-
1687
- // src/lib/cashu/health.ts
1688
- var DEFAULT_REQUIRED_NUTS = [4, 5, 7, 9, 10, 11, 20];
1689
- async function checkMintHealth(mintUrl, opts) {
1690
- const timeoutMs = opts?.timeoutMs ?? 5e3;
1691
- const retries = opts?.retries ?? 3;
1692
- const backoff = opts?.backoffMs ?? [200, 500, 1e3];
1693
- const fetchImpl = opts?.fetchImpl ?? fetch;
1694
- const url = mintUrl.replace(/\/+$/, "") + "/v1/info";
1695
- let lastError;
1696
- let lastLatency = 0;
1697
- for (let attempt = 0; attempt < retries; attempt++) {
1698
- if (attempt > 0) {
1699
- const delay = backoff[Math.min(attempt - 1, backoff.length - 1)];
1700
- await sleep(delay);
1701
- }
1702
- const startedAt = Date.now();
1703
- try {
1704
- const res = await fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
1705
- lastLatency = Date.now() - startedAt;
1706
- if (!res.ok) {
1707
- lastError = { code: "http_error", message: `HTTP ${res.status}` };
1708
- continue;
1709
- }
1710
- let info;
1711
- try {
1712
- info = await res.json();
1713
- } catch (err) {
1714
- lastError = {
1715
- code: "parse_error",
1716
- message: err instanceof Error ? err.message : String(err)
1717
- };
1718
- continue;
1719
- }
1720
- return parseInfo(mintUrl, info, lastLatency);
1721
- } catch (err) {
1722
- lastLatency = Date.now() - startedAt;
1723
- lastError = classifyFetchError(err);
1724
- }
1725
- }
1726
- return {
1727
- mintUrl,
1728
- ok: false,
1729
- latencyMs: lastLatency,
1730
- error: lastError ?? { code: "network", message: "no attempts recorded" }
1731
- };
1732
- }
1733
- function parseInfo(mintUrl, info, latencyMs) {
1734
- if (!isRecord(info)) {
1735
- return {
1736
- mintUrl,
1737
- ok: false,
1738
- latencyMs,
1739
- error: { code: "parse_error", message: "/v1/info response is not an object" }
1740
- };
1741
- }
1742
- const pubkey = typeof info.pubkey === "string" ? info.pubkey : void 0;
1743
- const version = typeof info.version === "string" ? info.version : void 0;
1744
- if (!pubkey || !version) {
1745
- const missing = [];
1746
- if (!pubkey) missing.push("pubkey");
1747
- if (!version) missing.push("version");
1748
- return {
1749
- mintUrl,
1750
- ok: false,
1751
- latencyMs,
1752
- pubkey,
1753
- version,
1754
- error: {
1755
- code: "missing_fields",
1756
- message: `NUT-06 missing required fields: ${missing.join(", ")}`
1757
- }
1758
- };
1759
- }
1760
- const nutsRaw = isRecord(info.nuts) ? info.nuts : {};
1761
- const supportedNuts = Object.keys(nutsRaw).sort(byNutKey);
1762
- const missingRequiredNuts = DEFAULT_REQUIRED_NUTS.map(String).filter((n) => !(n in nutsRaw));
1763
- const bolt11SatMintSupported = hasBolt11Sat(nutsRaw["4"]);
1764
- const bolt11SatMeltSupported = hasBolt11Sat(nutsRaw["5"]);
1765
- return {
1766
- mintUrl,
1767
- ok: true,
1768
- latencyMs,
1769
- pubkey,
1770
- version,
1771
- supportedNuts,
1772
- missingRequiredNuts,
1773
- bolt11SatMintSupported,
1774
- bolt11SatMeltSupported,
1775
- mintingEnabled: canMintBolt11Sat(nutsRaw),
1776
- meltingEnabled: canMeltBolt11Sat(nutsRaw),
1777
- swapEnabled: canSwap(nutsRaw),
1778
- mintAmountBounds: mintAmountBounds(nutsRaw),
1779
- meltAmountBounds: meltAmountBounds(nutsRaw),
1780
- nutCompliant: evaluateNutSupport(nutsRaw).ok,
1781
- nutsRaw
1782
- };
1783
- }
1784
- function canMintBolt11Sat(nuts) {
1785
- return isMethodEnabled(nuts["4"]);
1786
- }
1787
- function canMeltBolt11Sat(nuts) {
1788
- return isMethodEnabled(nuts["5"]);
1789
- }
1790
- function canSwap(nuts) {
1791
- const node = nuts["3"];
1792
- if (!isRecord(node)) return true;
1793
- if (node.disabled === true) return false;
1794
- return node.supported !== false;
1795
- }
1796
- function isMethodEnabled(node) {
1797
- if (!hasBolt11Sat(node)) return false;
1798
- return node.disabled !== true;
1799
- }
1800
- function mintAmountBounds(nuts) {
1801
- return boundsOf(nuts["4"]);
1802
- }
1803
- function meltAmountBounds(nuts) {
1804
- return boundsOf(nuts["5"]);
1805
- }
1806
- function amountBoundsVerdict(sats, bounds) {
1807
- if (!bounds) return "ok";
1808
- if (bounds.minSats !== null && sats < bounds.minSats) return "below_min";
1809
- if (bounds.maxSats !== null && sats > bounds.maxSats) return "above_max";
1810
- return "ok";
1811
- }
1812
- function boundsOf(node) {
1813
- const method = findBolt11Sat(node);
1814
- return {
1815
- minSats: method ? toBoundSats(method.min_amount) : null,
1816
- maxSats: method ? toBoundSats(method.max_amount) : null
1817
- };
1818
- }
1819
- function toBoundSats(value) {
1820
- if (typeof value === "bigint") {
1821
- return value >= 0n && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : null;
1822
- }
1823
- const n = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
1824
- if (!Number.isFinite(n) || n < 0) return null;
1825
- return Math.floor(n);
1826
- }
1827
- function evaluateNutSupport(nuts, requiredNuts = DEFAULT_REQUIRED_NUTS) {
1828
- const missingNuts = requiredNuts.filter((n) => {
1829
- const entry = nuts[String(n)];
1830
- if (entry === void 0 || entry === null) return true;
1831
- return typeof entry === "object" && entry.supported === false;
1832
- });
1833
- const missingBolt11 = [];
1834
- if (!hasBolt11Sat(nuts["4"])) missingBolt11.push("mint");
1835
- if (!hasBolt11Sat(nuts["5"])) missingBolt11.push("melt");
1836
- return {
1837
- ok: missingNuts.length === 0 && missingBolt11.length === 0,
1838
- missingNuts: missingNuts.length > 0 ? [...missingNuts] : void 0,
1839
- missingBolt11: missingBolt11.length > 0 ? missingBolt11 : void 0
1840
- };
1841
- }
1842
- function assertNutSupport(mintUrl, nuts, requiredNuts = DEFAULT_REQUIRED_NUTS) {
1843
- const verdict = evaluateNutSupport(nuts, requiredNuts);
1844
- if (verdict.missingNuts && verdict.missingNuts.length > 0) {
1845
- throw new DvmError(
1846
- "cashu_nut_support_missing",
1847
- `Mint ${mintUrl} does not support required NUTs: ${verdict.missingNuts.join(", ")}. Required: ${requiredNuts.join(", ")}.`,
1848
- "Choose a mint that supports all required NUT capabilities for P2PK-locked Cashu payments."
1849
- );
1850
- }
1851
- if (verdict.missingBolt11?.includes("mint")) {
1852
- throw new DvmError(
1853
- "cashu_nut_support_missing",
1854
- `Mint ${mintUrl} does not advertise bolt11/sat method on NUT-4 (mint quotes).`,
1855
- "Choose a mint that advertises bolt11/sat on NUT-4 mint quotes."
1856
- );
1857
- }
1858
- if (verdict.missingBolt11?.includes("melt")) {
1859
- throw new DvmError(
1860
- "cashu_nut_support_missing",
1861
- `Mint ${mintUrl} does not advertise bolt11/sat method on NUT-5 (melt quotes).`,
1862
- "Choose a mint that advertises bolt11/sat on NUT-5 melt quotes."
1863
- );
1864
- }
1865
- }
1866
- function hasBolt11Sat(node) {
1867
- return findBolt11Sat(node) !== void 0;
1868
- }
1869
- function findBolt11Sat(node) {
1870
- if (!isRecord(node)) return void 0;
1871
- const methods = node.methods;
1872
- if (!Array.isArray(methods)) return void 0;
1873
- return methods.find((m) => isRecord(m) && m.method === "bolt11" && m.unit === "sat");
1874
- }
1875
- function classifyFetchError(err) {
1876
- const message = err instanceof Error ? err.message : String(err);
1877
- if (err instanceof Error) {
1878
- if (err.name === "TimeoutError" || err.name === "AbortError") {
1879
- return { code: "timeout", message };
1880
- }
1881
- }
1882
- return { code: "network", message };
1883
- }
1884
- function isRecord(value) {
1885
- return typeof value === "object" && value !== null && !Array.isArray(value);
1886
- }
1887
- function byNutKey(a, b) {
1888
- const an = Number(a);
1889
- const bn = Number(b);
1890
- if (Number.isFinite(an) && Number.isFinite(bn)) return an - bn;
1891
- return a.localeCompare(b);
1892
- }
1893
- function sleep(ms) {
1894
- return new Promise((resolve) => setTimeout(resolve, ms));
1895
- }
1896
-
1897
- // src/lib/cashu/wallet.ts
1898
- import { Wallet } from "@cashu/cashu-ts";
1899
- import { mnemonicToSeedSync as mnemonicToSeedSync2 } from "@scure/bip39";
1900
- var walletCache = /* @__PURE__ */ new Map();
1901
- var keysetResidueRegistry = /* @__PURE__ */ new Map();
1902
- var NUT13_MODULUS = BigInt(2 ** 31 - 1);
1903
- function assertNoKeysetCollision(keysets, mintUrl) {
1904
- for (const keyset of keysets) {
1905
- let residue;
1906
- try {
1907
- residue = BigInt("0x" + keyset.id) % NUT13_MODULUS;
1908
- } catch {
1909
- throw new DvmError(
1910
- "cashu_keyset_collision",
1911
- `Mint ${mintUrl} has keyset ID "${keyset.id}" that is not valid hex. Cannot verify NUT-13 collision safety.`,
1912
- "Contact the mint operator to ensure the mint returns standard keyset IDs."
1913
- );
1914
- }
1915
- const existing = keysetResidueRegistry.get(residue);
1916
- if (existing && existing.keysetId !== keyset.id) {
1917
- throw new DvmError(
1918
- "cashu_keyset_collision",
1919
- `Keyset collision detected: mint ${mintUrl} keyset ${keyset.id} and mint ${existing.mintUrl} keyset ${existing.keysetId} share the same 31-bit NUT-13 residue. Cannot safely derive deterministic secrets.`,
1920
- "Contact the affected mint operators to rotate their keyset IDs."
1921
- );
1922
- }
1923
- keysetResidueRegistry.set(residue, { keysetId: keyset.id, mintUrl });
1924
- }
1925
- }
1926
- async function getWallet(mintUrl) {
1927
- const cached = walletCache.get(mintUrl);
1928
- if (cached) return cached;
1929
- return callerLoggers.cashu.span("cashu.get_wallet", { mint_url: mintUrl }, async () => {
1930
- let nut13 = loadNut13Config(mintUrl);
1931
- let wallet = await buildAndLoadWallet(mintUrl, nut13);
1932
- if (nut13) {
1933
- if (wallet.getKeyset().id.startsWith("00")) {
1934
- nut13 = null;
1935
- wallet = await buildAndLoadWallet(mintUrl, null);
1936
- }
1937
- }
1938
- assertNutSupport(mintUrl, wallet.getMintInfo().cache.nuts);
1939
- if (nut13) {
1940
- const restoreKeysets = wallet.keyChain.getKeysets().filter((keyset) => keyset.unit === wallet.unit && !keyset.id.startsWith("00"));
1941
- assertNoKeysetCollision(restoreKeysets, mintUrl);
1942
- }
1943
- walletCache.set(mintUrl, wallet);
1944
- return wallet;
1945
- });
1946
- }
1947
- async function buildAndLoadWallet(mintUrl, nut13) {
1948
- const wallet = nut13 ? new Wallet(mintUrl, nut13) : new Wallet(mintUrl);
1949
- try {
1950
- await wallet.loadMint();
1951
- } catch {
1952
- throw new DvmError(
1953
- "cashu_mint_unreachable",
1954
- `Cannot connect to mint: ${mintUrl}`,
1955
- "The mint is not responding. Try again later."
1956
- );
1957
- }
1958
- return wallet;
1959
- }
1960
- function loadNut13Config(mintUrl) {
1961
- const wallet = loadAgentWallet();
1962
- if (!wallet?.mnemonic_fingerprint) return null;
1963
- const mnemonic = readAgentMnemonic();
1964
- if (!mnemonic) return null;
1965
- const actual = mnemonicFingerprint(mnemonic);
1966
- if (actual !== wallet.mnemonic_fingerprint) {
1967
- throw new DvmError(
1968
- "wallet_mnemonic_mismatch",
1969
- "The mnemonic at ~/.dvm/wallet.mnemonic does not match the fingerprint recorded on ~/.dvm/wallet.json.",
1970
- "Either restore the matching mnemonic file from a backup, or run `dvm wallet restore --force --file <path>` (or pipe the mnemonic via stdin) with the wallet's actual mnemonic to rebuild from scratch."
1971
- );
1972
- }
1973
- return {
1974
- bip39seed: mnemonicToSeedSync2(mnemonic),
1975
- counterSource: new PersistentCounterSource(mintUrl)
1976
- };
1977
- }
1978
-
1979
- // src/lib/x402/batch-abi.ts
1980
- var X402_CHANNEL_CONFIG_COMPONENTS = [
1981
- { name: "payer", type: "address" },
1982
- { name: "payerAuthorizer", type: "address" },
1983
- { name: "receiver", type: "address" },
1984
- { name: "receiverAuthorizer", type: "address" },
1985
- { name: "token", type: "address" },
1986
- { name: "withdrawDelay", type: "uint40" },
1987
- { name: "salt", type: "bytes32" }
1988
- ];
1989
- var X402_BATCH_CHANNEL_ABI = [
1990
- {
1991
- type: "function",
1992
- name: "initiateWithdraw",
1993
- inputs: [
1994
- { name: "config", type: "tuple", components: X402_CHANNEL_CONFIG_COMPONENTS },
1995
- { name: "amount", type: "uint128" }
1996
- ],
1997
- outputs: [],
1998
- stateMutability: "nonpayable"
1999
- },
2000
- {
2001
- type: "function",
2002
- name: "finalizeWithdraw",
2003
- inputs: [{ name: "config", type: "tuple", components: X402_CHANNEL_CONFIG_COMPONENTS }],
2004
- outputs: [],
2005
- stateMutability: "nonpayable"
2006
- },
2007
- {
2008
- type: "function",
2009
- name: "channels",
2010
- inputs: [{ name: "channelId", type: "bytes32" }],
2011
- outputs: [
2012
- { name: "balance", type: "uint128" },
2013
- { name: "totalClaimed", type: "uint128" }
2014
- ],
2015
- stateMutability: "view"
2016
- },
2017
- {
2018
- type: "function",
2019
- name: "pendingWithdrawals",
2020
- inputs: [{ name: "channelId", type: "bytes32" }],
2021
- outputs: [
2022
- { name: "amount", type: "uint128" },
2023
- { name: "initiatedAt", type: "uint40" }
2024
- ],
2025
- stateMutability: "view"
2026
- },
2027
- {
2028
- type: "function",
2029
- name: "refundNonce",
2030
- inputs: [{ name: "channelId", type: "bytes32" }],
2031
- outputs: [{ name: "", type: "uint256" }],
2032
- stateMutability: "view"
2033
- },
2034
- {
2035
- type: "event",
2036
- name: "WithdrawInitiated",
2037
- inputs: [
2038
- { name: "channelId", type: "bytes32", indexed: true },
2039
- { name: "amount", type: "uint128", indexed: false },
2040
- { name: "finalizeAfter", type: "uint40", indexed: false }
2041
- ]
2042
- },
2043
- {
2044
- type: "event",
2045
- name: "WithdrawFinalized",
2046
- inputs: [
2047
- { name: "channelId", type: "bytes32", indexed: true },
2048
- { name: "sender", type: "address", indexed: true },
2049
- { name: "amount", type: "uint128", indexed: false }
2050
- ]
2051
- }
2052
- ];
2053
-
2054
- // src/sdk/server/x402-batch-settlement.ts
2055
- import { createHash as createHash4 } from "crypto";
2056
- import { isDeepStrictEqual } from "util";
2057
- import { x402ResourceServer } from "@x402/core/server";
2058
- import {
2059
- BATCH_SETTLEMENT_ADDRESS as BATCH_SETTLEMENT_ADDRESS2,
2060
- isBatchSettlementDepositPayload as isBatchSettlementDepositPayload2,
2061
- isBatchSettlementRefundPayload as isBatchSettlementRefundPayload2,
2062
- isBatchSettlementVoucherPayload
2063
- } from "@x402/evm";
2064
- import {
2065
- BatchSettlementEvmScheme as BatchSettlementEvmScheme2,
2066
- InMemoryChannelStorage
2067
- } from "@x402/evm/batch-settlement/server";
2068
- import { createPublicClient as createPublicClient3, http as http3 } from "viem";
2069
- import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
2070
-
2071
- // src/sdk/server/x402-self-relay.ts
2072
- import { x402Facilitator } from "@x402/core/facilitator";
2073
- import { BatchSettlementEvmScheme } from "@x402/evm/batch-settlement/facilitator";
2074
- import { createPublicClient, createWalletClient, http } from "viem";
2075
- import { privateKeyToAccount } from "viem/accounts";
2076
- import { base } from "viem/chains";
2077
- var X402_SELF_RELAY_DEFAULT_LOW_BALANCE_WEI = 1000000000000000n;
2078
- var X402_SELF_RELAY_BALANCE_INTERVAL_MS = 5 * 60 * 1e3;
2079
- function createX402SelfRelay(opts) {
2080
- if (!opts.config.rpcUrl.trim()) throw new Error("x402 self-relay requires an explicit RPC URL");
2081
- if (opts.config.lowBalanceWei !== void 0 && opts.config.lowBalanceWei <= 0n) {
2082
- throw new Error("x402 self-relay lowBalanceWei must be positive");
2083
- }
2084
- if (opts.config.balanceCheckIntervalMs !== void 0 && (!Number.isFinite(opts.config.balanceCheckIntervalMs) || opts.config.balanceCheckIntervalMs <= 0)) {
2085
- throw new Error("x402 self-relay balanceCheckIntervalMs must be positive");
2086
- }
2087
- const chain = relayChain(opts.network);
2088
- const account = privateKeyToAccount(opts.config.privateKey);
2089
- const transport = http(opts.config.rpcUrl);
2090
- const publicClient = createPublicClient({ chain, transport });
2091
- const walletClient = createWalletClient({ account, chain, transport });
2092
- const observeRpcFailure = async (operation) => {
2093
- try {
2094
- return await operation();
2095
- } catch (error) {
2096
- const reason = classifyX402SelfRelayRpcFailure(error);
2097
- if (reason) await reportRpcFailure(opts.config, account.address, opts.network, reason);
2098
- throw error;
2099
- }
2100
- };
2101
- const signer = createX402SelfRelaySigner({
2102
- network: opts.network,
2103
- ...opts.submissionLock ? { submissionLock: opts.submissionLock } : {},
2104
- backend: {
2105
- address: account.address,
2106
- readContract: (args) => observeRpcFailure(() => publicClient.readContract(args)),
2107
- verifyTypedData: (args) => observeRpcFailure(() => publicClient.verifyTypedData(args)),
2108
- writeContract: (args) => observeRpcFailure(() => walletClient.writeContract(args)),
2109
- sendTransaction: (args) => observeRpcFailure(() => walletClient.sendTransaction(args)),
2110
- waitForTransactionReceipt: async (args) => {
2111
- const receipt = await observeRpcFailure(() => publicClient.waitForTransactionReceipt(args));
2112
- return { status: receipt.status, logs: receipt.logs };
2113
- },
2114
- getCode: (args) => observeRpcFailure(() => publicClient.getCode(args))
1289
+ const signer = createX402SelfRelaySigner({
1290
+ network: opts.network,
1291
+ ...opts.submissionLock ? { submissionLock: opts.submissionLock } : {},
1292
+ backend: {
1293
+ address: account.address,
1294
+ readContract: (args) => observeRpcFailure(() => publicClient.readContract(args)),
1295
+ verifyTypedData: (args) => observeRpcFailure(() => publicClient.verifyTypedData(args)),
1296
+ writeContract: (args) => observeRpcFailure(() => walletClient.writeContract(args)),
1297
+ sendTransaction: (args) => observeRpcFailure(() => walletClient.sendTransaction(args)),
1298
+ waitForTransactionReceipt: async (args) => {
1299
+ const receipt = await observeRpcFailure(() => publicClient.waitForTransactionReceipt(args));
1300
+ return { status: receipt.status, logs: receipt.logs };
1301
+ },
1302
+ getCode: (args) => observeRpcFailure(() => publicClient.getCode(args))
2115
1303
  }
2116
1304
  });
2117
1305
  const local = new x402Facilitator().register(
@@ -3678,7 +2866,7 @@ async function applyRecoveredChannelSettlement(storage, payload, requirements, s
3678
2866
  if (!isBatchSettlementDepositPayload2(raw) && !isBatchSettlementRefundPayload2(raw)) {
3679
2867
  throw new Error("x402 recovery supports only irreversible deposit/refund settlements");
3680
2868
  }
3681
- const channelState = isRecord2(settlement.extra?.channelState) ? settlement.extra.channelState : {};
2869
+ const channelState = isRecord(settlement.extra?.channelState) ? settlement.extra.channelState : {};
3682
2870
  const result = await storage.updateChannel(raw.voucher.channelId, (current) => {
3683
2871
  const currentMatchesOriginalAuthority = current?.chargedCumulativeAmount === channelBefore.chargedCumulativeAmount && current.signedMaxClaimable === channelBefore.signedMaxClaimable && current.signature === channelBefore.signature && isDeepStrictEqual(current.channelConfig, channelBefore.channelConfig);
3684
2872
  const intentCreatedMissingChannel = current === void 0 && isBatchSettlementDepositPayload2(raw);
@@ -3728,12 +2916,12 @@ async function applyRecoveredChannelSettlement(storage, payload, requirements, s
3728
2916
  }
3729
2917
  }
3730
2918
  function settlementIdentity(operation, effectId) {
3731
- return createHash4("sha256").update(operation).update("\0").update(effectId).digest("hex");
2919
+ return createHash3("sha256").update(operation).update("\0").update(effectId).digest("hex");
3732
2920
  }
3733
2921
  function depositAuthorizationPaymentId(payload) {
3734
- const deposit = isRecord2(payload.deposit) ? payload.deposit : void 0;
3735
- const wrapper = isRecord2(deposit?.authorization) ? deposit.authorization : void 0;
3736
- const authorization = isRecord2(wrapper?.erc3009Authorization) ? wrapper.erc3009Authorization : void 0;
2922
+ const deposit = isRecord(payload.deposit) ? payload.deposit : void 0;
2923
+ const wrapper = isRecord(deposit?.authorization) ? deposit.authorization : void 0;
2924
+ const authorization = isRecord(wrapper?.erc3009Authorization) ? wrapper.erc3009Authorization : void 0;
3737
2925
  const salt = authorization?.salt;
3738
2926
  const voucher = batchVoucher(payload);
3739
2927
  if (typeof salt !== "string" || !/^0x[0-9a-f]{64}$/i.test(salt)) {
@@ -3786,10 +2974,10 @@ function withCachedSupportedResponse(facilitator, supportedResponse) {
3786
2974
  }
3787
2975
  function decodeBatchPayment(header) {
3788
2976
  const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf8"));
3789
- if (!isRecord2(parsed) || parsed.x402Version !== 2 || !isRecord2(parsed.accepted)) {
2977
+ if (!isRecord(parsed) || parsed.x402Version !== 2 || !isRecord(parsed.accepted)) {
3790
2978
  throw new Error("invalid batch-settlement payment envelope");
3791
2979
  }
3792
- if (parsed.accepted.scheme !== X402_BATCH_SETTLEMENT_SCHEME || typeof parsed.accepted.network !== "string" || !isRecord2(parsed.payload)) {
2980
+ if (parsed.accepted.scheme !== X402_BATCH_SETTLEMENT_SCHEME || typeof parsed.accepted.network !== "string" || !isRecord(parsed.payload)) {
3793
2981
  throw new Error("invalid batch-settlement payment envelope");
3794
2982
  }
3795
2983
  if (!isBatchSettlementDepositPayload2(parsed.payload) && !isBatchSettlementVoucherPayload(parsed.payload) && !isBatchSettlementRefundPayload2(parsed.payload)) {
@@ -3839,7 +3027,7 @@ async function restoreRevokedVoucher(storage, channelId, pendingId, before) {
3839
3027
  };
3840
3028
  });
3841
3029
  }
3842
- function isRecord2(value) {
3030
+ function isRecord(value) {
3843
3031
  return !!value && typeof value === "object" && !Array.isArray(value);
3844
3032
  }
3845
3033
 
@@ -3847,46 +3035,6 @@ function isRecord2(value) {
3847
3035
  import { randomBytes, randomUUID as randomUUID2 } from "crypto";
3848
3036
  import { Challenge } from "mppx";
3849
3037
 
3850
- // src/lib/cashu/verify.ts
3851
- import { getTokenMetadata } from "@cashu/cashu-ts";
3852
- function verifyCashuToken(token, acceptedMints) {
3853
- let mint, unit, totalMsats;
3854
- try {
3855
- const meta = getTokenMetadata(token);
3856
- mint = meta.mint;
3857
- unit = meta.unit;
3858
- totalMsats = meta.amount.toNumber() * 1e3;
3859
- } catch {
3860
- throw new DvmError(
3861
- "cashu_invalid_token",
3862
- "The Cashu token could not be decoded.",
3863
- "Ensure the token is a valid cashuA... or cashuB... string."
3864
- );
3865
- }
3866
- const normalised = acceptedMints.map(normaliseMintUrl);
3867
- if (!normalised.includes(normaliseMintUrl(mint))) {
3868
- throw new DvmError(
3869
- "cashu_mint_not_accepted",
3870
- `Mint ${mint} is not accepted by this provider.`,
3871
- `Accepted mints: ${acceptedMints.join(", ")}`
3872
- );
3873
- }
3874
- return { mint, totalMsats, unit };
3875
- }
3876
- function normaliseMintUrl(url) {
3877
- try {
3878
- const u = new URL(url);
3879
- u.hostname = u.hostname.toLowerCase();
3880
- u.pathname = u.pathname.replace(/\/+/g, "/").replace(/\/+$/, "") || "/";
3881
- return u.toString().replace(/\/$/, "");
3882
- } catch {
3883
- return url.toLowerCase().replace(/\/+$/, "");
3884
- }
3885
- }
3886
-
3887
- // src/lib/payment-rails/tempo-wire.ts
3888
- var TEMPO_SESSION_UPDATED_REASON = "Tempo session updated; attach the next voucher to fund this credit.";
3889
-
3890
3038
  // src/sdk/server/mpp-units.ts
3891
3039
  function decimalsFor(methodName) {
3892
3040
  switch (methodName) {
@@ -4158,21 +3306,19 @@ async function verifyUpfrontPayment(opts) {
4158
3306
  } = opts;
4159
3307
  const sessionOffered = opts.tempoSessionOffered ?? true;
4160
3308
  const sessionUnavailable = tempoSessionUnavailableCopy(opts.tempoSessionUnavailableReason);
3309
+ const x402PriceFiat = opts.priceFiat?.currency === "usd" ? opts.priceFiat : void 0;
3310
+ if (x402Payment && x402Config && requiredMsats && !x402PriceFiat) {
3311
+ return x402NotOfferedResult(opts.priceFiat?.currency);
3312
+ }
4161
3313
  const sessionIssuable = sessionOffered || await targetCreditHoldsTempoChannel(opts);
4162
3314
  const x402ExactVersions = opts.x402ExactVersions ?? { v1: true, v2: true };
4163
- const x402Advertised = Boolean(x402Config) && (x402Config?.dualServe ?? true) && (x402ExactVersions.v1 || x402ExactVersions.v2);
4164
- const needsRate = Boolean(requiredMsats) && (Boolean(mpp) || x402Advertised || Boolean(x402Payment && x402Config));
3315
+ const x402Advertised = Boolean(x402Config) && Boolean(x402PriceFiat) && (x402Config?.dualServe ?? true) && (x402ExactVersions.v1 || x402ExactVersions.v2);
3316
+ const needsRate = Boolean(requiredMsats) && Boolean(mpp);
4165
3317
  const rate = needsRate ? await resolveRateOrNull(opts.fxFetcher) : null;
4166
3318
  const rateOutage = needsRate && rate === null;
4167
- const x402Requirements = x402Config && x402Advertised && requiredMsats && rate !== null && (!opts.fundOnly || stablecoinCreditInstrumentAllowed(opts.fundOnly.allowOneShotStablecoin, "x402_exact")) ? buildX402Requirements(
4168
- x402Config,
4169
- requiredMsats,
4170
- x402Resource ?? "/v1/job",
4171
- rate,
4172
- opts.priceFiat
4173
- ) : void 0;
4174
- const x402BatchRequirements = opts.fundOnly && opts.x402BatchSettlement && x402Advertised && requiredMsats && rate !== null ? await opts.x402BatchSettlement.buildRequirements(
4175
- String(x402RequiredUsdcMicro(opts.priceFiat, requiredMsats, rate))
3319
+ const x402Requirements = x402Config && x402Advertised && x402PriceFiat && requiredMsats && (!opts.fundOnly || stablecoinCreditInstrumentAllowed(opts.fundOnly.allowOneShotStablecoin, "x402_exact")) ? buildX402Requirements(x402Config, x402Resource ?? "/v1/job", x402PriceFiat) : void 0;
3320
+ const x402BatchRequirements = opts.fundOnly && opts.x402BatchSettlement && x402Advertised && x402PriceFiat && requiredMsats ? await opts.x402BatchSettlement.buildRequirements(
3321
+ String(x402RequiredUsdcMicro(x402PriceFiat))
4176
3322
  ) : void 0;
4177
3323
  const ledgerCtx = resolveLedgerContext(opts);
4178
3324
  const creditEnv = opts.creditEnvelope;
@@ -4328,17 +3474,18 @@ async function verifyUpfrontPayment(opts) {
4328
3474
  };
4329
3475
  }
4330
3476
  if (x402Payment && x402Config && requiredMsats && paymentScheme(x402Payment) === X402_BATCH_SETTLEMENT_SCHEME) {
3477
+ if (!x402PriceFiat) return x402NotOfferedResult(opts.priceFiat?.currency);
4331
3478
  if (!opts.fundOnly || !opts.x402BatchSettlement) {
4332
3479
  return creditRefusal(
4333
3480
  "payment_invalid",
4334
3481
  "Reusable x402 channels are accepted only by POST /v1/credit."
4335
3482
  );
4336
3483
  }
4337
- if (rate === null) return rateUnavailableResult();
3484
+ if (rateOutage) return rateUnavailableResult();
4338
3485
  if (!ledgerCtx?.fundOnly?.creditId) {
4339
3486
  throw new Error("x402 batch settlement requires a named credit funding effect");
4340
3487
  }
4341
- const requiredUsdcMicro = x402RequiredUsdcMicro(opts.priceFiat, requiredMsats, rate);
3488
+ const requiredUsdcMicro = x402RequiredUsdcMicro(x402PriceFiat);
4342
3489
  try {
4343
3490
  const accepted = await opts.x402BatchSettlement.accept({
4344
3491
  paymentHeader: x402Payment,
@@ -4414,12 +3561,13 @@ async function verifyUpfrontPayment(opts) {
4414
3561
  }
4415
3562
  }
4416
3563
  if (x402Payment && x402Config && requiredMsats) {
3564
+ if (!x402PriceFiat) return x402NotOfferedResult(opts.priceFiat?.currency);
4417
3565
  if (opts.fundOnly && !stablecoinCreditInstrumentAllowed(opts.fundOnly.allowOneShotStablecoin, "x402_exact")) {
4418
3566
  return oneShotCreditRefusal("x402 exact");
4419
3567
  }
4420
- if (rate === null) return rateUnavailableResult();
3568
+ if (rateOutage) return rateUnavailableResult();
4421
3569
  try {
4422
- const requiredUsdcMicro = BigInt(x402RequiredUsdcMicro(opts.priceFiat, requiredMsats, rate));
3570
+ const requiredUsdcMicro = BigInt(x402RequiredUsdcMicro(x402PriceFiat));
4423
3571
  if (creditEnv?.fund && ledgerCtx) {
4424
3572
  const predicted = predictX402FundedMicro(
4425
3573
  x402Payment,
@@ -4449,7 +3597,7 @@ async function verifyUpfrontPayment(opts) {
4449
3597
  config: x402Config,
4450
3598
  requiredUsdcMicro,
4451
3599
  resource: x402Resource ?? "/v1/job",
4452
- btcUsdRate: rate,
3600
+ btcUsdRate: rate ?? 0,
4453
3601
  exactVersions: x402ExactVersions,
4454
3602
  dvmId: opts.dvmId ?? "",
4455
3603
  requiredMsats,
@@ -4457,13 +3605,13 @@ async function verifyUpfrontPayment(opts) {
4457
3605
  x402Requirements
4458
3606
  });
4459
3607
  }
4460
- const { verifyX402Payment } = await import("./x402-FTG2GRAQ.js");
3608
+ const { verifyX402Payment } = await import("./x402-T2C5MX3T.js");
4461
3609
  const receipt = await verifyX402Payment(
4462
3610
  x402Payment,
4463
3611
  x402Config,
4464
3612
  requiredUsdcMicro,
4465
3613
  x402Resource ?? "/v1/job",
4466
- rate,
3614
+ rate ?? 0,
4467
3615
  x402ExactVersions
4468
3616
  );
4469
3617
  if (receipt.verified) {
@@ -4962,6 +4110,11 @@ var PAYMENT_ERROR_COPY = {
4962
4110
  hint: "Request a fresh quote and pay again \u2014 the previous payment can't be reused.",
4963
4111
  retryable: false
4964
4112
  },
4113
+ x402_not_offered: {
4114
+ display: "This service does not offer x402 payment for prices in its configured currency. Nothing was charged.",
4115
+ hint: "Use one of the payment methods advertised by this service and submit the request again.",
4116
+ retryable: false
4117
+ },
4965
4118
  x402_settlement_ambiguous: {
4966
4119
  display: "The stablecoin payment may have settled, but the service has not finished recording what it bought. Do not pay again.",
4967
4120
  hint: "Retry the identical request with the same signed payment. If it remains pending, give settlement_intent_id to the service operator; do not request a fresh quote or sign another authorization.",
@@ -5359,6 +4512,9 @@ async function verifyIncomingPayment(body, opts, snapshot) {
5359
4512
  };
5360
4513
  }
5361
4514
  if (body.content.x402_payment && opts.x402Config) {
4515
+ if ((opts.pricingCurrency ?? "usd") !== "usd") {
4516
+ return { error: x402NotOfferedPaymentError(opts.pricingCurrency) };
4517
+ }
5362
4518
  const rate = await resolveRateOrNull(opts.fxFetcher);
5363
4519
  if (rate === null) return { error: rateUnavailablePaymentError() };
5364
4520
  try {
@@ -5377,7 +4533,7 @@ async function verifyIncomingPayment(body, opts, snapshot) {
5377
4533
  snapshot
5378
4534
  });
5379
4535
  }
5380
- const { verifyX402Payment } = await import("./x402-FTG2GRAQ.js");
4536
+ const { verifyX402Payment } = await import("./x402-T2C5MX3T.js");
5381
4537
  const receipt = await verifyX402Payment(
5382
4538
  body.content.x402_payment,
5383
4539
  opts.x402Config,
@@ -5782,12 +4938,15 @@ async function processIncomingPayment(job, body, opts) {
5782
4938
  status: 402
5783
4939
  };
5784
4940
  } else if (body.content.x402_payment && opts.x402Config) {
4941
+ if ((opts.pricingCurrency ?? "usd") !== "usd") {
4942
+ return x402NotOfferedPaymentError(opts.pricingCurrency);
4943
+ }
5785
4944
  const rate = await resolveRateOrNull(opts.fxFetcher);
5786
4945
  if (rate === null) return rateUnavailablePaymentError();
5787
4946
  try {
5788
4947
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5789
4948
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5790
- const { verifyX402Payment } = await import("./x402-FTG2GRAQ.js");
4949
+ const { verifyX402Payment } = await import("./x402-T2C5MX3T.js");
5791
4950
  const receipt = await verifyX402Payment(
5792
4951
  body.content.x402_payment,
5793
4952
  opts.x402Config,
@@ -6090,8 +5249,8 @@ async function verifyAccumulatorReceipt(opts, mints, requiredMsats, ledgerCtx) {
6090
5249
  credited
6091
5250
  );
6092
5251
  }
6093
- function buildX402Requirements(config, requiredMsats, resource, btcUsdRate, priceFiat) {
6094
- const requiredUsdcMicro = BigInt(x402RequiredUsdcMicro(priceFiat, requiredMsats, btcUsdRate));
5252
+ function buildX402Requirements(config, resource, priceFiat) {
5253
+ const requiredUsdcMicro = BigInt(x402RequiredUsdcMicro(priceFiat));
6095
5254
  return [
6096
5255
  buildPaymentRequirements({
6097
5256
  x402Config: config,
@@ -6403,11 +5562,15 @@ async function fundAndDraw(ctx, funding, tx) {
6403
5562
  if (createsDepositLiability && enqueue && (!tx || !ctx.dvmId)) {
6404
5563
  throw new Error("deposit outbox enqueue requires the rail's open transaction and a DVM id");
6405
5564
  }
6406
- const commitDeposit = async (result) => {
5565
+ const commitDeposit = async (result, credit) => {
5566
+ const report = {
5567
+ ...deposit,
5568
+ callerPubkey: credit.callerPubkey
5569
+ };
6407
5570
  if (createsDepositLiability && enqueue && tx) {
6408
- await enqueue(tx, creditDepositPayload(ctx.dvmId, deposit));
5571
+ await enqueue(tx, creditDepositPayload(ctx.dvmId, report));
6409
5572
  }
6410
- return result;
5573
+ return { ...result, deposit: report };
6411
5574
  };
6412
5575
  if (ctx.fundOnly) {
6413
5576
  const { creditId: creditId2 } = ctx.fundOnly;
@@ -6435,11 +5598,11 @@ async function fundAndDraw(ctx, funding, tx) {
6435
5598
  ledgerSeq: credit.lastLedgerSeq,
6436
5599
  tx
6437
5600
  });
6438
- return commitDeposit({ creditId: creditId2, deposit });
5601
+ return commitDeposit({ creditId: creditId2 }, credit);
6439
5602
  }
6440
5603
  if (ctx.topUp) {
6441
5604
  const { creditId: creditId2, drawId: drawId2 } = ctx.topUp;
6442
- await ctx.ledger.fund({
5605
+ const credit = await ctx.ledger.fund({
6443
5606
  creditId: creditId2,
6444
5607
  callerPubkey: ctx.callerId,
6445
5608
  currency: ctx.priceFiat.currency,
@@ -6449,9 +5612,9 @@ async function fundAndDraw(ctx, funding, tx) {
6449
5612
  tx
6450
5613
  });
6451
5614
  if (ctx.askSatisfied) {
6452
- return commitDeposit({ creditId: creditId2, deposit, unappliedMicro: fundedMicro });
5615
+ return commitDeposit({ creditId: creditId2, unappliedMicro: fundedMicro }, credit);
6453
5616
  }
6454
- if (shortMidJob) return commitDeposit({ creditId: creditId2, deposit });
5617
+ if (shortMidJob) return commitDeposit({ creditId: creditId2 }, credit);
6455
5618
  const grown = await ctx.ledger.growDraw({
6456
5619
  creditId: creditId2,
6457
5620
  drawId: drawId2,
@@ -6472,15 +5635,17 @@ async function fundAndDraw(ctx, funding, tx) {
6472
5635
  tx
6473
5636
  });
6474
5637
  if (grown.addedAmountMicro <= 0) {
6475
- return commitDeposit({ creditId: creditId2, deposit, unappliedMicro: grown.cappedMicro });
5638
+ return commitDeposit({ creditId: creditId2, unappliedMicro: grown.cappedMicro }, credit);
6476
5639
  }
6477
- return commitDeposit({
6478
- creditId: creditId2,
6479
- draw: grown,
6480
- added: { amountMicro: grown.addedAmountMicro, ...grown.addedRailValue },
6481
- ...grown.cappedMicro > 0 && { unappliedMicro: grown.cappedMicro },
6482
- deposit
6483
- });
5640
+ return commitDeposit(
5641
+ {
5642
+ creditId: creditId2,
5643
+ draw: grown,
5644
+ added: { amountMicro: grown.addedAmountMicro, ...grown.addedRailValue },
5645
+ ...grown.cappedMicro > 0 && { unappliedMicro: grown.cappedMicro }
5646
+ },
5647
+ credit
5648
+ );
6484
5649
  }
6485
5650
  const creditId = deposit.creditId;
6486
5651
  const drawId = env?.drawId ?? funding.paymentId;
@@ -6503,7 +5668,7 @@ async function fundAndDraw(ctx, funding, tx) {
6503
5668
  basis,
6504
5669
  tx
6505
5670
  });
6506
- if (shortMidJob) return commitDeposit({ creditId, deposit });
5671
+ if (shortMidJob) return commitDeposit({ creditId }, fundedCredit);
6507
5672
  let draw;
6508
5673
  try {
6509
5674
  draw = await ctx.ledger.draw({
@@ -6523,7 +5688,10 @@ async function fundAndDraw(ctx, funding, tx) {
6523
5688
  ledgerSeq: fundedCredit.lastLedgerSeq,
6524
5689
  tx
6525
5690
  });
6526
- return commitDeposit({ creditId, drawRefusal: err, deposit, fundingCredit: fundedCredit });
5691
+ return commitDeposit(
5692
+ { creditId, drawRefusal: err, fundingCredit: fundedCredit },
5693
+ fundedCredit
5694
+ );
6527
5695
  }
6528
5696
  throw err;
6529
5697
  }
@@ -6538,17 +5706,20 @@ async function fundAndDraw(ctx, funding, tx) {
6538
5706
  tx
6539
5707
  });
6540
5708
  }
6541
- return commitDeposit({
6542
- creditId,
6543
- draw,
6544
- deposit,
6545
- ...env ? { fundingCredit: fundedCredit } : {}
6546
- });
5709
+ return commitDeposit(
5710
+ {
5711
+ creditId,
5712
+ draw,
5713
+ ...env ? { fundingCredit: fundedCredit } : {}
5714
+ },
5715
+ fundedCredit
5716
+ );
6547
5717
  }
6548
5718
  function creditDepositPayload(dvmId, funding) {
6549
5719
  return {
6550
5720
  dvmId,
6551
5721
  creditId: funding.creditId,
5722
+ callerPubkey: funding.callerPubkey,
6552
5723
  fundingId: funding.fundingId,
6553
5724
  rail: funding.rail,
6554
5725
  paidMsats: funding.paidMsats,
@@ -7119,6 +6290,21 @@ async function resolveRateOrNull(fetcher) {
7119
6290
  function rateUnavailableResult() {
7120
6291
  return { paidMsats: 0, receivedProofs: [], error: rateUnavailablePaymentError() };
7121
6292
  }
6293
+ function x402NotOfferedPaymentError(currency) {
6294
+ return {
6295
+ body: paymentErrorBody("x402_not_offered", {
6296
+ message: `x402 is offered only by USD-priced DVMs; this DVM prices in ${currency ?? "an unknown currency"}.`
6297
+ }),
6298
+ status: 402
6299
+ };
6300
+ }
6301
+ function x402NotOfferedResult(currency) {
6302
+ return {
6303
+ paidMsats: 0,
6304
+ receivedProofs: [],
6305
+ error: x402NotOfferedPaymentError(currency)
6306
+ };
6307
+ }
7122
6308
  function logCredentialRejected(rail, phase, err) {
7123
6309
  console.warn(
7124
6310
  JSON.stringify({
@@ -7226,6 +6412,10 @@ function parseAtomicAmount(value) {
7226
6412
  }
7227
6413
 
7228
6414
  // src/sdk/server/credit-drain-report.ts
6415
+ var CREDIT_DRAIN_REPAIR_KINDS = /* @__PURE__ */ new Set([
6416
+ "x402_settlement_reconciled",
6417
+ "tempo_close_reconciled"
6418
+ ]);
7229
6419
  async function commitTerminalDrain(wiring, transition, outerTx) {
7230
6420
  const { db, dvmId, enqueueCreditDrain } = wiring;
7231
6421
  if (outerTx) {
@@ -7257,15 +6447,29 @@ function drainReport(dvmId, drain) {
7257
6447
  return {
7258
6448
  dvmId,
7259
6449
  creditId: drain.creditId,
6450
+ callerPubkey: drain.callerPubkey,
7260
6451
  drainId: drain.drainId,
7261
6452
  method: drain.method,
7262
6453
  amountMicro: drain.amountMicro,
7263
6454
  creditCurrency: drain.currency,
7264
6455
  status: drain.status,
7265
6456
  drainedAt: drainedAt(drain),
7266
- ledgerSeq: drain.ledgerSeq
6457
+ ledgerSeq: drain.ledgerSeq,
6458
+ ...repairMetadata(drain)
7267
6459
  };
7268
6460
  }
6461
+ function repairMetadata(drain) {
6462
+ const repaired = drain.sentRef?.repaired;
6463
+ const repairKind = drain.sentRef?.repair_kind;
6464
+ if (repaired !== true && repairKind === void 0) return {};
6465
+ if (repaired !== true || !isCreditDrainRepairKind(repairKind)) {
6466
+ throw new Error(`invalid credit-drain repair metadata on ${drain.creditId}/${drain.drainId}`);
6467
+ }
6468
+ return { repaired: true, repairKind };
6469
+ }
6470
+ function isCreditDrainRepairKind(value) {
6471
+ return typeof value === "string" && CREDIT_DRAIN_REPAIR_KINDS.has(value);
6472
+ }
7269
6473
  function drainedAt(drain) {
7270
6474
  return drain.pickedUpAt ?? drain.sentAt ?? drain.createdAt;
7271
6475
  }
@@ -7317,7 +6521,7 @@ async function completeTempoChannelDrain(args) {
7317
6521
  tx_hash: args.reference,
7318
6522
  channel_id: args.channelId,
7319
6523
  ...settlementEvidence,
7320
- ...args.repaired ? { repaired: true } : {}
6524
+ ...args.repaired ? { repaired: true, repair_kind: "tempo_close_reconciled" } : {}
7321
6525
  },
7322
6526
  tx: innerTx
7323
6527
  });
@@ -7384,7 +6588,7 @@ async function completeX402ChannelDrain(args) {
7384
6588
  channel_id: args.channelId,
7385
6589
  ...settlementEvidence,
7386
6590
  ...args.transaction !== void 0 ? { transaction: args.transaction } : {},
7387
- ...args.repaired ? { repaired: true } : {}
6591
+ ...args.repaired ? { repaired: true, repair_kind: "x402_settlement_reconciled" } : {}
7388
6592
  },
7389
6593
  tx: innerTx
7390
6594
  }),
@@ -9088,1238 +8292,106 @@ function x402SettlementPendingRefusal(args) {
9088
8292
  ...args.settlementId ? { settlement_id: args.settlementId } : {},
9089
8293
  ...args.settlementStatus ? { settlement_status: args.settlementStatus } : {},
9090
8294
  ...admitted ? { settlement_drain_id: admitted } : {}
9091
- }
9092
- });
9093
- }
9094
- function drainLedgerErrorResponse(error, creditId, drainId) {
9095
- if (error.code === "nothing_to_drain") {
9096
- return jsonPaymentError("nothing_to_drain", 409, {
9097
- message: `credit ${creditId} has no available balance to drain.`,
9098
- extra: {
9099
- balance_micro: error.details.balanceMicro ?? 0,
9100
- remaining_micro: Math.max(0, error.details.availableMicro ?? 0)
9101
- }
9102
- });
9103
- }
9104
- if (error.code === "drain_below_dust") {
9105
- return jsonPaymentError("drain_below_dust", 409, {
9106
- message: error.message,
9107
- extra: {
9108
- balance_micro: error.details.balanceMicro ?? 0,
9109
- remaining_micro: Math.max(0, error.details.availableMicro ?? 0),
9110
- owed_sats: 0
9111
- }
9112
- });
9113
- }
9114
- if (error.code === "drain_conflict") {
9115
- const copy = error.details.drainConflict ? DRAIN_CONFLICT_COPY[error.details.drainConflict] : void 0;
9116
- return jsonPaymentError("drain_conflict", 409, {
9117
- message: error.message,
9118
- display: copy?.display,
9119
- hint: copy?.hint({ creditId, drainId }),
9120
- // An unclassified conflict keeps the duplicate-payout copy's own
9121
- // `retryable`, and the id it was never told anything about.
9122
- retryable: copy?.retryable,
9123
- extra: { drain_registered: copy?.drainRegistered ?? false }
9124
- });
9125
- }
9126
- if (error.code === "settlement_pending") {
9127
- return x402SettlementPendingRefusal({
9128
- door: "drain",
9129
- message: error.message,
9130
- drainId,
9131
- ...error.details.settlementId ? { settlementId: error.details.settlementId } : {},
9132
- ...error.details.settlementStatus ? { settlementStatus: error.details.settlementStatus } : {},
9133
- ...error.details.blockingDrainId ? { blockingDrainId: error.details.blockingDrainId } : {}
9134
- });
9135
- }
9136
- if (error.code === "caller_mismatch" || error.code === "credit_not_found") {
9137
- return jsonPaymentError("credit_not_found", 404, {
9138
- message: `No credit ${creditId} for this caller.`
9139
- });
9140
- }
9141
- return jsonPaymentError(mapLedgerCode(error), 402, { message: error.message });
9142
- }
9143
- function isHttpResponse(value) {
9144
- return value instanceof Response || typeof value === "object" && "headers" in value && typeof value.status === "number";
9145
- }
9146
- function mapLedgerCode(err) {
9147
- switch (err.code) {
9148
- case "insufficient_credit":
9149
- case "credit_expired":
9150
- case "credit_unbacked":
9151
- case "credit_not_found":
9152
- case "tempo_channel_closing":
9153
- case "settlement_pending":
9154
- case "nothing_to_drain":
9155
- case "drain_below_dust":
9156
- case "drain_not_found":
9157
- case "drain_conflict":
9158
- return err.code;
9159
- default:
9160
- return "payment_invalid";
9161
- }
9162
- }
9163
- function creditBody(credit, reconciliationPending = false) {
9164
- return {
9165
- status: credit.status,
9166
- credit_id: credit.creditId,
9167
- currency: credit.currency,
9168
- balance_micro: credit.balanceMicro,
9169
- remaining_micro: credit.availableMicro,
9170
- expiry_ms: credit.expiryMs,
9171
- expired: credit.expired,
9172
- // Reclaimable via op "drain" (internal-review) — true whenever spendable-or-
9173
- // expired value remains. Expiry ends spending, never ownership.
9174
- drainable: credit.status === "active" && credit.availableMicro > 0,
9175
- ...reconciliationPending && { reconciliation_pending: true },
9176
- ledger_seq: credit.lastLedgerSeq
9177
- };
9178
- }
9179
- function spendable(credit, currency) {
9180
- return !credit.expired && credit.status === "active" && credit.currency === currency;
9181
- }
9182
- function pickPrimary(credits, currency) {
9183
- let best;
9184
- for (const credit of credits) {
9185
- if (!spendable(credit, currency) || credit.availableMicro <= 0) continue;
9186
- if (!best || credit.availableMicro > best.availableMicro || credit.availableMicro === best.availableMicro && credit.createdAt > best.createdAt) {
9187
- best = credit;
9188
- }
9189
- }
9190
- return best;
9191
- }
9192
-
9193
- // src/lib/mints.ts
9194
- var RECOMMENDED_MINTS = [
9195
- {
9196
- name: "Testnut",
9197
- description: "Cashu test mint \u2014 use for testing only, not real funds",
9198
- url: "https://testnut.cashu.space"
9199
- },
9200
- {
9201
- name: "Voltz Mint",
9202
- description: "Production Cashu mint by the Voltz Wallet team",
9203
- url: "https://mint.lnvoltz.com"
9204
- }
9205
- ];
9206
- function isTestMintUrl(url) {
9207
- let host;
9208
- try {
9209
- host = new URL(url).hostname.toLowerCase();
9210
- } catch {
9211
- return false;
9212
- }
9213
- if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1);
9214
- if (LOOPBACK_HOSTS.has(host)) return true;
9215
- if (host.endsWith(".localhost")) return true;
9216
- return TEST_MINT_HOSTS.has(host);
9217
- }
9218
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1"]);
9219
- var TEST_MINT_HOSTS = /* @__PURE__ */ new Set(["testnut.cashu.space"]);
9220
-
9221
- // src/lib/receipt-key.ts
9222
- import { schnorr, secp256k1 } from "@noble/curves/secp256k1.js";
9223
- import { hkdf } from "@noble/hashes/hkdf.js";
9224
- import { sha256 } from "@noble/hashes/sha2.js";
9225
- import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
9226
- var RECEIPT_KDF_SALT = "dvmkit/receipt/v1";
9227
- function deriveReceiptSecret(builderSecretHex, dvmId) {
9228
- const ikm = hexToBytes(builderSecretHex);
9229
- const salt = new TextEncoder().encode(RECEIPT_KDF_SALT);
9230
- for (let counter = 0; counter < 256; counter++) {
9231
- const info = new TextEncoder().encode(
9232
- counter === 0 ? `${RECEIPT_KDF_SALT}/${dvmId}` : `${RECEIPT_KDF_SALT}/${dvmId}#${String(counter)}`
9233
- );
9234
- const candidate = hkdf(sha256, ikm, salt, info, 32);
9235
- if (secp256k1.utils.isValidSecretKey(candidate)) return bytesToHex(candidate);
9236
- }
9237
- throw new Error(`deriveReceiptSecret: no valid secp256k1 scalar for DVM ID "${dvmId}"`);
9238
- }
9239
- function receiptPubkeyFromSecret(secretHex) {
9240
- return bytesToHex(schnorr.getPublicKey(hexToBytes(secretHex)));
9241
- }
9242
- function deriveReceiptKeypair(builderSecretHex, dvmId) {
9243
- const secret = deriveReceiptSecret(builderSecretHex, dvmId);
9244
- return { secret, pubkey: receiptPubkeyFromSecret(secret) };
9245
- }
9246
- function generateEphemeralReceiptKey() {
9247
- const { secretKey, publicKey } = schnorr.keygen();
9248
- return { secret: bytesToHex(secretKey), pubkey: bytesToHex(publicKey) };
9249
- }
9250
-
9251
- // src/lib/x402/env.ts
9252
- import { isAddress } from "viem";
9253
- import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
9254
- var X402_SELF_RELAY_KEY = "DVMKIT_X402_SELF_RELAY_KEY";
9255
- var X402_RECEIVER_AUTHORIZER_KEY = "DVMKIT_X402_RECEIVER_AUTHORIZER_KEY";
9256
- var X402_BATCH_SETTLEMENT_KEY = "DVMKIT_X402_BATCH_SETTLEMENT";
9257
- var X402_RPC_URL_KEY = "DVMKIT_X402_RPC_URL";
9258
- var PRIVATE_KEY_RE = /^0x[0-9a-fA-F]{64}$/;
9259
- var X402EnvValidationError = class extends Error {
9260
- constructor(code, variable, message, expectedAddress) {
9261
- super(message);
9262
- this.code = code;
9263
- this.variable = variable;
9264
- this.expectedAddress = expectedAddress;
9265
- this.name = "X402EnvValidationError";
9266
- }
9267
- code;
9268
- variable;
9269
- expectedAddress;
9270
- };
9271
- function validateX402Env(env, opts = {}) {
9272
- const relayKey = env[X402_SELF_RELAY_KEY];
9273
- const authorizerKey = env[X402_RECEIVER_AUTHORIZER_KEY];
9274
- const hasRelayKey = relayKey !== void 0;
9275
- const hasAuthorizerKey = authorizerKey !== void 0;
9276
- const relay = hasRelayKey ? derive(X402_SELF_RELAY_KEY, relayKey) : void 0;
9277
- const authorizer = hasAuthorizerKey ? derive(X402_RECEIVER_AUTHORIZER_KEY, authorizerKey) : void 0;
9278
- const completeWrite = opts.requireCompleteBundle === true && (hasRelayKey || hasAuthorizerKey);
9279
- if (hasRelayKey || completeWrite) {
9280
- if (env[X402_BATCH_SETTLEMENT_KEY] !== "true") {
9281
- fail(
9282
- "x402_bundle_incomplete",
9283
- hasRelayKey ? X402_SELF_RELAY_KEY : X402_RECEIVER_AUTHORIZER_KEY,
9284
- `Changing either x402 private key requires ${X402_BATCH_SETTLEMENT_KEY}=true in the same write.`
9285
- );
9286
- }
9287
- if (!env[X402_RPC_URL_KEY]?.trim()) {
9288
- fail(
9289
- "x402_bundle_incomplete",
9290
- hasRelayKey ? X402_SELF_RELAY_KEY : X402_RECEIVER_AUTHORIZER_KEY,
9291
- `Changing either x402 private key requires an explicit ${X402_RPC_URL_KEY} in the same write.`
9292
- );
9293
- }
9294
- if (!hasAuthorizerKey || completeWrite && !hasRelayKey) {
9295
- if (!completeWrite && !hasAuthorizerKey) {
9296
- fail(
9297
- "x402_bundle_incomplete",
9298
- X402_RECEIVER_AUTHORIZER_KEY,
9299
- `${X402_SELF_RELAY_KEY} requires ${X402_RECEIVER_AUTHORIZER_KEY}.`
9300
- );
9301
- }
9302
- fail(
9303
- "x402_bundle_incomplete",
9304
- hasRelayKey ? X402_RECEIVER_AUTHORIZER_KEY : X402_SELF_RELAY_KEY,
9305
- `Changing either x402 private key requires both ${X402_SELF_RELAY_KEY} and ${X402_RECEIVER_AUTHORIZER_KEY} in the same write.`
9306
- );
9307
- }
9308
- }
9309
- if (hasRelayKey && relayKey.toLowerCase() === authorizerKey?.toLowerCase()) {
9310
- fail(
9311
- "x402_key_reuse",
9312
- X402_SELF_RELAY_KEY,
9313
- `${X402_SELF_RELAY_KEY} and ${X402_RECEIVER_AUTHORIZER_KEY} must use different private keys.`
9314
- );
9315
- }
9316
- assertExpectation(X402_SELF_RELAY_KEY, relay, opts.expectedRelay);
9317
- assertExpectation(X402_RECEIVER_AUTHORIZER_KEY, authorizer, opts.expectedAuthorizer);
9318
- return { ...relay ? { relay } : {}, ...authorizer ? { authorizer } : {} };
9319
- }
9320
- function derive(variable, key) {
9321
- if (!PRIVATE_KEY_RE.test(key)) {
9322
- fail(
9323
- "invalid_x402_private_key",
9324
- variable,
9325
- `${variable} must be a 0x-prefixed 32-byte hex secp256k1 private key.`
9326
- );
9327
- }
9328
- try {
9329
- return privateKeyToAccount3(key).address;
9330
- } catch {
9331
- fail(
9332
- "invalid_x402_private_key",
9333
- variable,
9334
- `${variable} must be a valid secp256k1 private key.`
9335
- );
9336
- }
9337
- }
9338
- function assertExpectation(variable, actual, expected) {
9339
- if (expected === void 0) return;
9340
- if (!isAddress(expected, { strict: false })) {
9341
- fail(
9342
- "invalid_x402_expected_address",
9343
- variable,
9344
- `Expected address for ${variable} must be a 0x-prefixed 20-byte EVM address.`
9345
- );
9346
- }
9347
- if (actual?.toLowerCase() !== expected.toLowerCase()) {
9348
- fail(
9349
- "x402_address_mismatch",
9350
- variable,
9351
- `${variable} derives to a different public address than expected ${expected}.`,
9352
- expected
9353
- );
9354
- }
9355
- }
9356
- function fail(code, variable, message, expectedAddress) {
9357
- throw new X402EnvValidationError(code, variable, message, expectedAddress);
9358
- }
9359
-
9360
- // src/observability/span-writer.ts
9361
- var noop = () => {
9362
- };
9363
- var SPANS_INSERTED_CHANNEL = "spans_inserted";
9364
- var MAX_BUFFERED_SPANS = 1e4;
9365
- var MAX_ROW_JSONB_BYTES = 64 * 1024;
9366
- var MAX_STRING_VALUE_BYTES = 4 * 1024;
9367
- var FLUSH_THRESHOLD = 1e3;
9368
- var MAX_BACKOFF_EXPONENT = 6;
9369
- var SpanWriter = class {
9370
- constructor(transport, flushIntervalMs = 1e4) {
9371
- this.transport = transport;
9372
- this.flushIntervalMs = flushIntervalMs;
9373
- }
9374
- transport;
9375
- flushIntervalMs;
9376
- buffer = [];
9377
- flushTimer = null;
9378
- flushing = false;
9379
- dropCount = 0;
9380
- truncatedSpanCount = 0;
9381
- consecutiveFailures = 0;
9382
- backoffTimer = null;
9383
- /** Enqueue a span row. Non-blocking — truncates oversize JSONB and appends to buffer. */
9384
- enqueue(row) {
9385
- const originalBytes = JSON.stringify({
9386
- attributes: row.attributes,
9387
- dimensions: row.dimensions
9388
- }).length;
9389
- if (originalBytes > MAX_ROW_JSONB_BYTES) {
9390
- row = this.truncateForSize(row, originalBytes);
9391
- }
9392
- if (this.buffer.length >= MAX_BUFFERED_SPANS) {
9393
- this.dropCount++;
9394
- if (this.dropCount === 1 || this.dropCount % 1e3 === 0) {
9395
- console.error(
9396
- JSON.stringify({
9397
- event: "span_buffer_drop",
9398
- reason: "buffer_full",
9399
- total_dropped: this.dropCount,
9400
- buffer_size: this.buffer.length
9401
- })
9402
- );
9403
- }
9404
- return;
9405
- }
9406
- this.buffer.push(row);
9407
- if (this.buffer.length >= FLUSH_THRESHOLD && !this.flushing) {
9408
- void this.flush().catch(noop);
9409
- }
9410
- }
9411
- /** Start periodic flush to the transport. */
9412
- startFlush() {
9413
- this.flushTimer = setInterval(() => {
9414
- void this.flush().catch(noop);
9415
- }, this.flushIntervalMs);
9416
- }
9417
- /** Stop periodic flush. */
9418
- stopFlush() {
9419
- if (this.flushTimer) {
9420
- clearInterval(this.flushTimer);
9421
- this.flushTimer = null;
9422
- }
9423
- if (this.backoffTimer) {
9424
- clearTimeout(this.backoffTimer);
9425
- this.backoffTimer = null;
9426
- }
9427
- }
9428
- /** Flush buffered spans. Single-flight: concurrent calls exit early. */
9429
- async flush() {
9430
- if (this.flushing) return;
9431
- if (this.buffer.length === 0) return;
9432
- this.flushing = true;
9433
- const batch = this.buffer;
9434
- this.buffer = [];
9435
- try {
9436
- await this.transport.send(batch);
9437
- this.consecutiveFailures = 0;
9438
- } catch (err) {
9439
- this.consecutiveFailures++;
9440
- console.error(
9441
- JSON.stringify({
9442
- event: "span_flush_failed",
9443
- error: err instanceof Error ? err.message : String(err),
9444
- batch_size: batch.length,
9445
- consecutive_failures: this.consecutiveFailures
9446
- })
9447
- );
9448
- const room = MAX_BUFFERED_SPANS - this.buffer.length;
9449
- if (room > 0) {
9450
- this.buffer.unshift(...batch.slice(0, room));
9451
- }
9452
- if (this.consecutiveFailures > 1) {
9453
- const exponent = Math.min(this.consecutiveFailures - 1, MAX_BACKOFF_EXPONENT);
9454
- const backoffMs = this.flushIntervalMs * Math.pow(2, exponent);
9455
- this.stopFlush();
9456
- this.backoffTimer = setTimeout(() => {
9457
- this.backoffTimer = null;
9458
- this.startFlush();
9459
- }, backoffMs);
9460
- }
9461
- } finally {
9462
- this.flushing = false;
9463
- }
9464
- }
9465
- /** Flush remaining spans on shutdown. */
9466
- async shutdown() {
9467
- this.stopFlush();
9468
- await this.flush();
9469
- }
9470
- /** Number of spans dropped due to buffer overflow. */
9471
- getDropCount() {
9472
- return this.dropCount;
9473
- }
9474
- /** Number of spans truncated due to oversized JSONB payload. */
9475
- getTruncatedSpanCount() {
9476
- return this.truncatedSpanCount;
9477
- }
9478
- /** Current buffer size. */
9479
- getBufferSize() {
9480
- return this.buffer.length;
9481
- }
9482
- /** Consecutive flush failures; resets to 0 on the next successful flush. */
9483
- getConsecutiveFailures() {
9484
- return this.consecutiveFailures;
9485
- }
9486
- /**
9487
- * Two-pass truncation: cap individual string values, then fall back to
9488
- * wholesale attribute replacement if the blob is still over the limit.
9489
- */
9490
- truncateForSize(row, originalBytes) {
9491
- const truncatedAttrs = {};
9492
- for (const [key, value] of Object.entries(row.attributes)) {
9493
- if (typeof value === "string" && value.length > MAX_STRING_VALUE_BYTES) {
9494
- const omitted = value.length - MAX_STRING_VALUE_BYTES;
9495
- truncatedAttrs[key] = `${value.slice(0, MAX_STRING_VALUE_BYTES)}\u2026 [truncated, ${omitted} chars omitted]`;
9496
- } else {
9497
- truncatedAttrs[key] = value;
9498
- }
9499
- }
9500
- const afterStringPass = JSON.stringify({
9501
- attributes: truncatedAttrs,
9502
- dimensions: row.dimensions
9503
- }).length;
9504
- let mode;
9505
- if (afterStringPass <= MAX_ROW_JSONB_BYTES) {
9506
- row = { ...row, attributes: truncatedAttrs };
9507
- mode = "string_pass";
9508
- } else {
9509
- const dimSize = JSON.stringify(row.dimensions).length;
9510
- if (dimSize > MAX_ROW_JSONB_BYTES) {
9511
- console.error(
9512
- JSON.stringify({
9513
- event: "span_dimension_oversized",
9514
- name: row.name,
9515
- dataset: row.dataset,
9516
- dimension_bytes: dimSize
9517
- })
9518
- );
9519
- }
9520
- row = {
9521
- ...row,
9522
- attributes: {
9523
- _truncated: true,
9524
- original_bytes: originalBytes,
9525
- original_keys: Object.keys(row.attributes).sort()
9526
- }
9527
- };
9528
- mode = "wholesale_fallback";
9529
- }
9530
- this.truncatedSpanCount++;
9531
- if (this.truncatedSpanCount === 1 || this.truncatedSpanCount % 100 === 0) {
9532
- console.error(
9533
- JSON.stringify({
9534
- event: "span_truncated",
9535
- name: row.name,
9536
- dataset: row.dataset,
9537
- original_bytes: originalBytes,
9538
- after_string_pass_bytes: afterStringPass,
9539
- mode,
9540
- total_truncated: this.truncatedSpanCount
9541
- })
9542
- );
9543
- }
9544
- return row;
9545
- }
9546
- };
9547
- var SPAN_COLUMNS = [
9548
- "span_id",
9549
- "trace_id",
9550
- "parent_span_id",
9551
- "dataset",
9552
- "name",
9553
- "level",
9554
- "status",
9555
- "started_at_ms",
9556
- "ended_at_ms",
9557
- "service",
9558
- "service_version",
9559
- "host",
9560
- "pid",
9561
- "dimensions",
9562
- "attributes",
9563
- "error_message",
9564
- "error_stack",
9565
- "sample_rate"
9566
- ];
9567
- async function insertSpanBatch(pool, rows) {
9568
- if (rows.length === 0) return;
9569
- const values = [];
9570
- const placeholders = [];
9571
- let idx = 1;
9572
- for (const row of rows) {
9573
- const rowPlaceholders = [];
9574
- for (const col of SPAN_COLUMNS) {
9575
- let val = row[col] ?? null;
9576
- if (col === "sample_rate" && val == null) {
9577
- val = 1;
9578
- }
9579
- if (col === "dimensions" || col === "attributes") {
9580
- rowPlaceholders.push(`$${idx}::jsonb`);
9581
- values.push(typeof val === "string" ? val : JSON.stringify(val ?? {}));
9582
- } else {
9583
- rowPlaceholders.push(`$${idx}`);
9584
- values.push(val);
9585
- }
9586
- idx++;
9587
- }
9588
- placeholders.push(`(${rowPlaceholders.join(", ")})`);
9589
- }
9590
- await pool.query(
9591
- `INSERT INTO observability.spans (${SPAN_COLUMNS.join(", ")})
9592
- VALUES ${placeholders.join(",\n")}
9593
- ON CONFLICT (dataset, span_id) DO NOTHING`,
9594
- values
9595
- );
9596
- try {
9597
- await pool.query(`SELECT pg_notify('${SPANS_INSERTED_CHANNEL}', '')`);
9598
- } catch {
9599
- }
9600
- }
9601
- var DbTransport = class {
9602
- constructor(pool) {
9603
- this.pool = pool;
9604
- }
9605
- pool;
9606
- async send(rows) {
9607
- await insertSpanBatch(this.pool, rows);
9608
- }
9609
- };
9610
- var HttpTransport = class {
9611
- constructor(platformUrl, serviceToken) {
9612
- this.platformUrl = platformUrl;
9613
- this.serviceToken = serviceToken;
9614
- }
9615
- platformUrl;
9616
- serviceToken;
9617
- async send(rows) {
9618
- if (rows.length === 0) return;
9619
- const resp = await fetch(`${this.platformUrl.replace(/\/$/, "")}/_internal/spans`, {
9620
- method: "POST",
9621
- headers: {
9622
- "Content-Type": "application/json",
9623
- Authorization: `Bearer ${this.serviceToken}`
9624
- },
9625
- body: JSON.stringify({ spans: rows })
9626
- });
9627
- if (!resp.ok) {
9628
- const text = await resp.text().catch(() => "");
9629
- throw new Error(`Span ingestion failed: ${resp.status} ${text}`);
9630
- }
9631
- }
9632
- };
9633
-
9634
- // src/lib/builder-identity.ts
9635
- import {
9636
- chmodSync as chmodSync3,
9637
- existsSync as existsSync5,
9638
- mkdirSync as mkdirSync2,
9639
- readFileSync as readFileSync5,
9640
- renameSync as renameSync4,
9641
- statSync,
9642
- unlinkSync as unlinkSync4,
9643
- writeFileSync as writeFileSync5
9644
- } from "fs";
9645
- import { schnorr as schnorr2 } from "@noble/curves/secp256k1.js";
9646
- import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
9647
-
9648
- // src/lib/builder-store.ts
9649
- import {
9650
- chmodSync as chmodSync2,
9651
- existsSync as existsSync4,
9652
- mkdirSync,
9653
- readFileSync as readFileSync4,
9654
- renameSync as renameSync3,
9655
- unlinkSync as unlinkSync3,
9656
- writeFileSync as writeFileSync4
9657
- } from "fs";
9658
- var CURRENT_VERSION = 2;
9659
- function loadBuilderStore() {
9660
- if (!existsSync4(BUILDER_FILE)) {
9661
- return { version: CURRENT_VERSION, dvms: {} };
9662
- }
9663
- const raw = readFileSync4(BUILDER_FILE, "utf-8");
9664
- let parsed;
9665
- try {
9666
- parsed = JSON.parse(raw);
9667
- } catch (err) {
9668
- throw new Error(
9669
- `${BUILDER_FILE} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
9670
- { cause: err }
9671
- );
9672
- }
9673
- if (typeof parsed !== "object" || parsed === null) {
9674
- throw new Error(`${BUILDER_FILE} is not a JSON object`);
9675
- }
9676
- const values = parsed;
9677
- const version = values.version;
9678
- if (version === 1) {
9679
- return migrateV1Store(parsed);
9680
- }
9681
- if (version !== CURRENT_VERSION) {
9682
- throw new Error(
9683
- `${BUILDER_FILE} has unknown version ${String(version)} (expected ${CURRENT_VERSION})`
9684
- );
9685
- }
9686
- if (typeof values.dvms !== "object" || values.dvms === null) {
9687
- throw new Error(`${BUILDER_FILE} is missing the dvms object`);
9688
- }
9689
- return {
9690
- version: CURRENT_VERSION,
9691
- ...typeof values.identity_pubkey === "string" ? { identity_pubkey: values.identity_pubkey } : {},
9692
- dvms: values.dvms
9693
- };
9694
- }
9695
- function loadIdentityPubkey() {
9696
- return loadBuilderStore().identity_pubkey ?? null;
9697
- }
9698
- function setIdentityPubkey(pubkey) {
9699
- mkdirSync(BUILDER_CONFIG_DIR, { recursive: true, mode: 448 });
9700
- const store = loadBuilderStore();
9701
- store.identity_pubkey = pubkey;
9702
- writeBuilderStore(store);
9703
- }
9704
- function writeBuilderStore(store) {
9705
- const content = JSON.stringify(store, null, 2) + "\n";
9706
- backupSecretFile(BUILDER_FILE, content);
9707
- const tmp = `${BUILDER_FILE}.tmp`;
9708
- try {
9709
- writeFileSync4(tmp, content, { mode: 384 });
9710
- renameSync3(tmp, BUILDER_FILE);
9711
- } catch (err) {
9712
- try {
9713
- unlinkSync3(tmp);
9714
- } catch {
9715
- }
9716
- throw err;
9717
- }
9718
- try {
9719
- chmodSync2(BUILDER_FILE, 384);
9720
- } catch {
9721
- }
9722
- }
9723
- function migrateV1Store(parsed) {
9724
- if (typeof parsed.dvms !== "object" || parsed.dvms === null) {
9725
- throw new Error(`${BUILDER_FILE} is missing the dvms object`);
9726
- }
9727
- const dvms = {};
9728
- for (const [handle, rawEntry] of Object.entries(parsed.dvms)) {
9729
- if (typeof rawEntry !== "object" || rawEntry === null) {
9730
- throw new Error(`${BUILDER_FILE} has an invalid DVM entry for ${handle}`);
9731
- }
9732
- const entry = rawEntry;
9733
- const dvmId = entry.canonical_dvm_id;
9734
- if (typeof dvmId !== "string") {
9735
- throw new Error(`${BUILDER_FILE} has no canonical_dvm_id for ${handle}`);
9736
- }
9737
- if (Object.prototype.hasOwnProperty.call(dvms, dvmId)) {
9738
- throw new Error(`${BUILDER_FILE} has multiple entries for canonical_dvm_id ${dvmId}`);
9739
- }
9740
- dvms[dvmId] = { ...entry, handle };
9741
- }
9742
- return {
9743
- version: CURRENT_VERSION,
9744
- ...typeof parsed.identity_pubkey === "string" ? { identity_pubkey: parsed.identity_pubkey } : {},
9745
- dvms
9746
- };
9747
- }
9748
-
9749
- // src/lib/builder-identity.ts
9750
- function generateIdentity() {
9751
- const { secretKey, publicKey } = schnorr2.keygen();
9752
- return { pubkey: bytesToHex2(publicKey), secret: bytesToHex2(secretKey) };
9753
- }
9754
- function loadIdentitySecret() {
9755
- if (!existsSync5(BUILDER_KEY_FILE)) return null;
9756
- warnAndTightenLooseKeyMode();
9757
- const raw = readFileSync5(BUILDER_KEY_FILE, "utf-8").trim();
9758
- if (!/^[0-9a-f]{64}$/.test(raw)) {
9759
- throw new DvmError(
9760
- "identity_secret_malformed",
9761
- `${BUILDER_KEY_FILE} is not a 32-byte hex secret.`,
9762
- "Delete the file and re-run `dvmctl identity create` (this rotates the key \u2014 existing DVM attestations need re-deploy)."
9763
- );
9764
- }
9765
- return raw;
9766
- }
9767
- function writeIdentity(keypair, opts = {}) {
9768
- const existing = loadIdentityPubkey();
9769
- if (existing && !opts.force) {
9770
- throw new DvmError(
9771
- "identity_exists",
9772
- "A builder identity already exists.",
9773
- "Pass --force to overwrite. Any DVMs deployed under the old key will need to be re-deployed to refresh their /v1/info attestation."
9774
- );
9775
- }
9776
- mkdirSync2(BUILDER_CONFIG_DIR, { recursive: true, mode: 448 });
9777
- const tmp = `${BUILDER_KEY_FILE}.tmp`;
9778
- writeFileSync5(tmp, keypair.secret + "\n", { mode: 384 });
9779
- try {
9780
- renameSync4(tmp, BUILDER_KEY_FILE);
9781
- } catch (err) {
9782
- try {
9783
- unlinkSync4(tmp);
9784
- } catch {
9785
- }
9786
- throw err;
9787
- }
9788
- try {
9789
- chmodSync3(BUILDER_KEY_FILE, 384);
9790
- } catch {
9791
- }
9792
- setIdentityPubkey(keypair.pubkey);
9793
- }
9794
- function hashCapabilities(capabilities) {
9795
- return bytesToHex2(sha2562(canonicaliseForSigning(capabilities)));
9796
- }
9797
- function buildAttestation(args) {
9798
- return {
9799
- dvm_id: args.dvmId,
9800
- slug: args.slug,
9801
- capabilities_hash: hashCapabilities(args.capabilities),
9802
- builder_pubkey: args.builderPubkey,
9803
- ...args.receiptPubkey ? { receipt_pubkey: args.receiptPubkey } : {},
9804
- deployed_at: args.deployedAt ?? Math.floor(Date.now() / 1e3)
9805
- };
9806
- }
9807
- function signAttestation(payload, secretHex) {
9808
- const secretBytes = hexToBytes2(secretHex);
9809
- const canonicalBytes = canonicaliseForSigning(payload);
9810
- return bytesToHex2(schnorr2.sign(canonicalBytes, secretBytes));
9811
- }
9812
- function verifyAttestation(payload, signatureHex, pubkeyHex) {
9813
- try {
9814
- const canonicalBytes = canonicaliseForSigning(payload);
9815
- return schnorr2.verify(hexToBytes2(signatureHex), canonicalBytes, hexToBytes2(pubkeyHex));
9816
- } catch {
9817
- return false;
9818
- }
9819
- }
9820
- function warnAndTightenLooseKeyMode() {
9821
- if (process.platform === "win32") return;
9822
- try {
9823
- const mode = statSync(BUILDER_KEY_FILE).mode & 511;
9824
- if ((mode & 63) === 0) return;
9825
- console.warn(
9826
- JSON.stringify({
9827
- level: "warn",
9828
- event: "builder_identity_key_loose_mode",
9829
- message: `${BUILDER_KEY_FILE} is group/other-readable (mode ${mode.toString(8).padStart(3, "0")}). The builder signing key should be owner-only \u2014 re-tightening to 0600. A cp/scp/git checkout can widen the mode; verify nobody else read it in the meantime.`,
9830
- path: BUILDER_KEY_FILE,
9831
- mode: mode.toString(8).padStart(3, "0")
9832
- })
9833
- );
9834
- chmodSync3(BUILDER_KEY_FILE, 384);
9835
- } catch {
9836
- }
9837
- }
9838
- function bytesToHex2(bytes) {
9839
- let s = "";
9840
- for (const b of bytes) s += b.toString(16).padStart(2, "0");
9841
- return s;
9842
- }
9843
- function hexToBytes2(hex) {
9844
- if (hex.length % 2 !== 0) {
9845
- throw new Error(`hex string has odd length: ${String(hex.length)}`);
9846
- }
9847
- const out = new Uint8Array(hex.length / 2);
9848
- for (let i = 0; i < out.length; i++) {
9849
- out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
9850
- }
9851
- return out;
9852
- }
9853
-
9854
- // src/lib/receipt.ts
9855
- import { schnorr as schnorr3 } from "@noble/curves/secp256k1.js";
9856
- import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
9857
- import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
9858
- function computeResultHash(summary, artifactContents) {
9859
- const artifactHashes = artifactContents.map(
9860
- (content) => bytesToHex3(sha2563(canonicaliseForSigning(content)))
9861
- );
9862
- return bytesToHex3(
9863
- sha2563(
9864
- canonicaliseForSigning({
9865
- summary: summary ?? null,
9866
- artifact_hashes: artifactHashes
9867
- })
9868
- )
9869
- );
9870
- }
9871
- function signReceipt(unsigned, secretHex) {
9872
- const bytes = canonicaliseForSigning(unsigned);
9873
- const signature = bytesToHex3(schnorr3.sign(bytes, hexToBytes3(secretHex)));
9874
- return { ...unsigned, signature };
9875
- }
9876
- function verifyReceipt(receipt) {
9877
- try {
9878
- const { signature, ...unsigned } = receipt;
9879
- const bytes = canonicaliseForSigning(unsigned);
9880
- return schnorr3.verify(hexToBytes3(signature), bytes, hexToBytes3(receipt.receipt_pubkey));
9881
- } catch {
9882
- return false;
9883
- }
9884
- }
9885
- function isSignedJobReceipt(value) {
9886
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
9887
- const r = value;
9888
- const paid = r.paid;
9889
- return r.v === 1 && typeof r.job_id === "string" && typeof r.dvm_id === "string" && typeof r.dvm === "string" && typeof r.capability === "string" && (r.outcome === "completed" || r.outcome === "failed" || r.outcome === "cancelled") && typeof r.seq === "number" && typeof r.issued_at === "number" && typeof r.receipt_pubkey === "string" && typeof r.signature === "string" && !!paid && typeof paid === "object" && !Array.isArray(paid) && typeof paid.msats === "number";
9890
- }
9891
- function signDrainReceipt(unsigned, secretHex) {
9892
- const bytes = canonicaliseForSigning(unsigned);
9893
- const signature = bytesToHex3(schnorr3.sign(bytes, hexToBytes3(secretHex)));
9894
- return { ...unsigned, signature };
9895
- }
9896
- function signFundingReceipt(unsigned, secretHex) {
9897
- const bytes = canonicaliseForSigning(unsigned);
9898
- const signature = bytesToHex3(schnorr3.sign(bytes, hexToBytes3(secretHex)));
9899
- return { ...unsigned, signature };
9900
- }
9901
- function isFundingReceipt(value) {
9902
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
9903
- const r = value;
9904
- return r.v === 1 && r.kind === "credit_funding" && typeof r.credit_id === "string" && typeof r.fund_id === "string" && typeof r.funded_micro === "number" && typeof r.balance_after_micro === "number" && typeof r.ledger_seq === "number" && typeof r.issued_at === "number" && typeof r.caller_pubkey === "string" && typeof r.dvm_id === "string" && typeof r.dvm === "string" && typeof r.receipt_pubkey === "string" && typeof r.signature === "string";
9905
- }
9906
- function verifyFundingReceipt(receipt) {
9907
- try {
9908
- const { signature, ...unsigned } = receipt;
9909
- const bytes = canonicaliseForSigning(unsigned);
9910
- return schnorr3.verify(hexToBytes3(signature), bytes, hexToBytes3(receipt.receipt_pubkey));
9911
- } catch {
9912
- return false;
9913
- }
9914
- }
9915
- function isDrainReceipt(value) {
9916
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
9917
- const r = value;
9918
- return r.v === 1 && r.kind === "credit_drain" && typeof r.credit_id === "string" && typeof r.drain_id === "string" && (r.event === "requested" || r.event === "parked" || r.event === "picked_up" || r.event === "sent" || r.event === "released") && typeof r.method === "string" && typeof r.amount_micro === "number" && typeof r.issued_at === "number" && typeof r.caller_pubkey === "string" && typeof r.dvm_id === "string" && typeof r.dvm === "string" && typeof r.receipt_pubkey === "string" && typeof r.signature === "string";
9919
- }
9920
- function verifyDrainReceipt(receipt) {
9921
- try {
9922
- const { signature, ...unsigned } = receipt;
9923
- const bytes = canonicaliseForSigning(unsigned);
9924
- return schnorr3.verify(hexToBytes3(signature), bytes, hexToBytes3(receipt.receipt_pubkey));
9925
- } catch {
9926
- return false;
9927
- }
9928
- }
9929
-
9930
- // src/lib/receipt-verify.ts
9931
- function attestedDvmIdentity(builder) {
9932
- const builderPubkey = authenticatedBuilderPubkey(builder);
9933
- if (!builderPubkey) return void 0;
9934
- const dvmId = builder.attestation.dvm_id;
9935
- if (typeof dvmId !== "string" || dvmId.length === 0) return void 0;
9936
- return { dvmId, builderPubkey };
9937
- }
9938
- function authenticatedBuilderPubkey(builder) {
9939
- if (!isTrustAnchor(builder)) return void 0;
9940
- const pubkey = builder.pubkey.trim().toLowerCase();
9941
- const attestedPubkey = builder.attestation.builder_pubkey;
9942
- if (!/^[0-9a-f]{64}$/.test(pubkey) || typeof attestedPubkey !== "string" || attestedPubkey.trim().toLowerCase() !== pubkey) {
9943
- return void 0;
9944
- }
9945
- const payload = builder.attestation;
9946
- return verifyAttestation(payload, builder.signature, pubkey) ? pubkey : void 0;
9947
- }
9948
- function verifyJobReceipt(args) {
9949
- const { receipt, builder, expectedJobId, delivered } = args;
9950
- const checks = {
9951
- job_id: checkJobId(receipt, expectedJobId),
9952
- signature: verifyReceipt(receipt),
9953
- attestation: "absent",
9954
- result_hash: "unchecked"
9955
- };
9956
- if (checks.job_id === "mismatch") {
9957
- return { status: "invalid", checks, reason: "job_id_mismatch" };
9958
- }
9959
- if (!checks.signature) {
9960
- return { status: "invalid", checks, reason: "signature_invalid" };
9961
- }
9962
- checks.attestation = checkReceiptAttestation(receipt, builder);
9963
- if (checks.attestation === "invalid") {
9964
- return { status: "invalid", checks, reason: "attestation_invalid" };
9965
- }
9966
- if (checks.attestation === "identity_mismatch") {
9967
- return { status: "invalid", checks, reason: "dvm_identity_mismatch" };
9968
- }
9969
- if (checks.attestation === "key_mismatch") {
9970
- return { status: "invalid", checks, reason: "receipt_key_mismatch" };
9971
- }
9972
- checks.result_hash = checkResultHash(receipt, delivered);
9973
- if (checks.result_hash === "mismatch") {
9974
- return { status: "invalid", checks, reason: "result_hash_mismatch" };
9975
- }
9976
- return {
9977
- status: checks.attestation === "ok" ? "verified" : "verified_unattested",
9978
- checks
9979
- };
9980
- }
9981
- function checkReceiptAttestation(receipt, builder) {
9982
- if (!isTrustAnchor(builder)) return "absent";
9983
- const payload = builder.attestation;
9984
- if (!authenticatedBuilderPubkey(builder)) return "invalid";
9985
- if (typeof payload.dvm_id !== "string" || payload.dvm_id !== receipt.dvm_id) {
9986
- return "identity_mismatch";
9987
- }
9988
- const attested = builder.attestation.receipt_pubkey;
9989
- if (typeof attested !== "string" || attested.length === 0) return "absent";
9990
- return attested.toLowerCase() === receipt.receipt_pubkey.toLowerCase() ? "ok" : "key_mismatch";
9991
- }
9992
- function verifyDrainReceiptChain(args) {
9993
- const { receipt, builder, expectedDrainId, expectedCreditId } = args;
9994
- const named = isDrainReceipt(receipt) ? receipt : void 0;
9995
- const checks = {
9996
- drain_id: checkDrainField(named?.drain_id, expectedDrainId),
9997
- credit_id: checkDrainField(named?.credit_id, expectedCreditId),
9998
- signature: named !== void 0 && verifyDrainReceipt(named),
9999
- attestation: "absent"
10000
- };
10001
- if (checks.drain_id === "mismatch") {
10002
- return { status: "invalid", checks, reason: "drain_id_mismatch" };
10003
- }
10004
- if (checks.credit_id === "mismatch") {
10005
- return { status: "invalid", checks, reason: "credit_id_mismatch" };
10006
- }
10007
- if (named === void 0 || !checks.signature) {
10008
- return { status: "invalid", checks, reason: "signature_invalid" };
10009
- }
10010
- checks.attestation = checkReceiptAttestation(named, builder);
10011
- if (checks.attestation === "invalid") {
10012
- return { status: "invalid", checks, reason: "attestation_invalid" };
10013
- }
10014
- if (checks.attestation === "identity_mismatch") {
10015
- return { status: "invalid", checks, reason: "dvm_identity_mismatch" };
10016
- }
10017
- if (checks.attestation === "key_mismatch") {
10018
- return { status: "invalid", checks, reason: "receipt_key_mismatch" };
10019
- }
10020
- return {
10021
- status: checks.attestation === "ok" ? "verified" : "verified_unattested",
10022
- checks
10023
- };
10024
- }
10025
- function verifyFundingReceiptChain(args) {
10026
- const named = isFundingReceipt(args.receipt) ? args.receipt : void 0;
10027
- const check = (actual, expected) => expected === void 0 ? "unchecked" : actual === expected ? "ok" : "mismatch";
10028
- const checks = {
10029
- credit_id: check(named?.credit_id, args.expectedCreditId),
10030
- fund_id: check(named?.fund_id, args.expectedFundId),
10031
- funded_micro: check(named?.funded_micro, args.expectedFundedMicro),
10032
- balance_after_micro: check(named?.balance_after_micro, args.expectedBalanceAfterMicro),
10033
- ledger_seq: check(named?.ledger_seq, args.expectedLedgerSeq),
10034
- caller_pubkey: check(named?.caller_pubkey, args.expectedCallerPubkey),
10035
- signature: named !== void 0 && verifyFundingReceipt(named),
10036
- attestation: "absent"
10037
- };
10038
- const mismatch = [
10039
- ["credit_id", "credit_id_mismatch"],
10040
- ["fund_id", "fund_id_mismatch"],
10041
- ["funded_micro", "funding_amount_mismatch"],
10042
- ["balance_after_micro", "funding_balance_mismatch"],
10043
- ["ledger_seq", "funding_ledger_seq_mismatch"],
10044
- ["caller_pubkey", "caller_pubkey_mismatch"]
10045
- ];
10046
- for (const [field, reason] of mismatch) {
10047
- if (checks[field] === "mismatch") return { status: "invalid", checks, reason, receipt: named };
10048
- }
10049
- if (!named || !checks.signature) {
10050
- return { status: "invalid", checks, reason: "signature_invalid", receipt: named };
10051
- }
10052
- checks.attestation = checkReceiptAttestation(named, args.builder);
10053
- if (checks.attestation === "invalid") {
10054
- return { status: "invalid", checks, reason: "attestation_invalid", receipt: named };
8295
+ }
8296
+ });
8297
+ }
8298
+ function drainLedgerErrorResponse(error, creditId, drainId) {
8299
+ if (error.code === "nothing_to_drain") {
8300
+ return jsonPaymentError("nothing_to_drain", 409, {
8301
+ message: `credit ${creditId} has no available balance to drain.`,
8302
+ extra: {
8303
+ balance_micro: error.details.balanceMicro ?? 0,
8304
+ remaining_micro: Math.max(0, error.details.availableMicro ?? 0)
8305
+ }
8306
+ });
10055
8307
  }
10056
- if (checks.attestation === "identity_mismatch") {
10057
- return { status: "invalid", checks, reason: "dvm_identity_mismatch", receipt: named };
8308
+ if (error.code === "drain_below_dust") {
8309
+ return jsonPaymentError("drain_below_dust", 409, {
8310
+ message: error.message,
8311
+ extra: {
8312
+ balance_micro: error.details.balanceMicro ?? 0,
8313
+ remaining_micro: Math.max(0, error.details.availableMicro ?? 0),
8314
+ owed_sats: 0
8315
+ }
8316
+ });
10058
8317
  }
10059
- if (checks.attestation === "key_mismatch") {
10060
- return { status: "invalid", checks, reason: "receipt_key_mismatch", receipt: named };
8318
+ if (error.code === "drain_conflict") {
8319
+ const copy = error.details.drainConflict ? DRAIN_CONFLICT_COPY[error.details.drainConflict] : void 0;
8320
+ return jsonPaymentError("drain_conflict", 409, {
8321
+ message: error.message,
8322
+ display: copy?.display,
8323
+ hint: copy?.hint({ creditId, drainId }),
8324
+ // An unclassified conflict keeps the duplicate-payout copy's own
8325
+ // `retryable`, and the id it was never told anything about.
8326
+ retryable: copy?.retryable,
8327
+ extra: { drain_registered: copy?.drainRegistered ?? false }
8328
+ });
10061
8329
  }
10062
- return {
10063
- status: checks.attestation === "ok" ? "verified" : "verified_unattested",
10064
- checks,
10065
- receipt: named
10066
- };
10067
- }
10068
- function weakestReceiptVerdict(statuses) {
10069
- if (statuses.length === 0) return "absent";
10070
- if (statuses.includes("invalid")) return "invalid";
10071
- if (statuses.includes("absent")) return "absent";
10072
- if (statuses.includes("verified_unattested")) return "verified_unattested";
10073
- return "verified";
10074
- }
10075
- function drainReceiptsDisplay(status, sound, total) {
10076
- const tally = `${String(sound)} of ${String(total)}`;
10077
- switch (status) {
10078
- case "verified":
10079
- return `Verified reclaim receipts (${tally}): signed by the key this DVM's builder identity attests.`;
10080
- case "verified_unattested":
10081
- return `Reclaim receipts, signed but unattested (${tally}): the signature holds, but this DVM publishes no builder identity to vouch for the signing key.`;
10082
- case "invalid":
10083
- return `UNVERIFIED reclaim receipts: only ${tally} hold up \u2014 the refund is still owed, but this evidence is not sound.`;
10084
- case "absent":
10085
- return "No reclaim receipts: this DVM returned no signed evidence of the drain.";
10086
- }
10087
- }
10088
- function drainReceiptsHint(status) {
10089
- switch (status) {
10090
- case "verified":
10091
- return "The refund evidence chains to this DVM's published builder identity, and is stored locally with that identity pinned beside it \u2014 so an unhonoured refund stays provable against the builder, not against an anonymous key.";
10092
- case "verified_unattested":
10093
- return "The receipts are signed, but nothing vouches for the signing key \u2014 expected for a locally-run or development DVM, not for a deployed one. Stored either way; the refund is owed regardless.";
10094
- case "invalid":
10095
- return "The refund is unaffected \u2014 this is a problem with the proof, not the money. Keep this output, and treat the reclaim as unattested when deciding how much to leave on deposit at this DVM.";
10096
- case "absent":
10097
- return "This DVM countersigned nothing, so the only artifact behind this reclaim is the drain request the caller signed. Weaker evidence than a countersigned drain, and worth weighing before funding here again.";
10098
- }
10099
- }
10100
- function receiptDisplay(receipt, status, rate = 0) {
10101
- const what = `${receipt.dvm}/${receipt.capability} (DVM ID ${receipt.dvm_id})`;
10102
- const when = receiptIssuedAtIso(receipt)?.slice(0, 10);
10103
- return `${VERDICT_PREFIX[status]}: paid ${formatPaid(receipt.paid.msats, rate)} for ${what}, ${receipt.outcome}${when ? ` ${when}` : ""}.`;
10104
- }
10105
- function receiptIssuedAtIso(receipt) {
10106
- const seconds = receipt.issued_at;
10107
- return typeof seconds === "number" ? epochMsToIso(seconds * 1e3) : null;
10108
- }
10109
- function epochMsToIso(ms) {
10110
- if (typeof ms !== "number" || !Number.isFinite(ms) || Math.abs(ms) > MAX_TIMESTAMP_MS) {
10111
- return null;
8330
+ if (error.code === "settlement_pending") {
8331
+ return x402SettlementPendingRefusal({
8332
+ door: "drain",
8333
+ message: error.message,
8334
+ drainId,
8335
+ ...error.details.settlementId ? { settlementId: error.details.settlementId } : {},
8336
+ ...error.details.settlementStatus ? { settlementStatus: error.details.settlementStatus } : {},
8337
+ ...error.details.blockingDrainId ? { blockingDrainId: error.details.blockingDrainId } : {}
8338
+ });
10112
8339
  }
10113
- return new Date(ms).toISOString();
10114
- }
10115
- function receiptHint(status, jobId) {
10116
- const verifyCmd = jobId ? `'dvm receipts verify ${jobId}'` : "'dvm receipts verify <job-id>'";
10117
- switch (status) {
10118
- case "verified":
10119
- return `Proof of purchase is stored locally. Re-check it any time with ${verifyCmd}, or hand over the raw bundle with 'dvm receipts export'.`;
10120
- case "verified_unattested":
10121
- return `The receipt's signature is valid, but this DVM publishes no builder identity to vouch for the signing key \u2014 expected for a locally-run or development DVM, not for a deployed one. Stored either way; inspect it with ${verifyCmd}.`;
10122
- case "invalid":
10123
- return `The job itself is unaffected \u2014 this is a problem with the proof, not the result. Run ${verifyCmd} to see which check failed before relying on this receipt as evidence.`;
10124
- case "absent":
10125
- return "This DVM does not issue signed receipts, so there is no proof of purchase to store. Nothing went wrong.";
10126
- }
10127
- }
10128
- function receiptFailureDetail(reason) {
10129
- switch (reason) {
10130
- case "job_id_mismatch":
10131
- return "The receipt names a different job than the one it was collected for \u2014 it is not proof of this job, whoever signed it.";
10132
- case "drain_id_mismatch":
10133
- return "The receipt names a different reclaim than the one it is filed under \u2014 it is not evidence of this drain, whoever signed it.";
10134
- case "credit_id_mismatch":
10135
- return "The receipt names a different credit than the one this reclaim was against \u2014 it is not evidence of this refund, whoever signed it.";
10136
- case "fund_id_mismatch":
10137
- return "The receipt names a different funding operation than the caller submitted.";
10138
- case "funding_amount_mismatch":
10139
- return "The signed funded amount differs from the provider's funding response.";
10140
- case "funding_balance_mismatch":
10141
- return "The signed post-funding balance differs from the provider's fresh balance response.";
10142
- case "funding_ledger_seq_mismatch":
10143
- return "The signed funding sequence differs from the provider's fresh balance response.";
10144
- case "caller_pubkey_mismatch":
10145
- return "The funding receipt names a different caller than the key that submitted the funding.";
10146
- case "signature_invalid":
10147
- return "The receipt's signature does not verify under the key it names \u2014 the bytes were altered, or it was never signed by that key.";
10148
- case "attestation_invalid":
10149
- return "The DVM's published builder attestation does not verify under its own builder pubkey.";
10150
- case "dvm_identity_mismatch":
10151
- return "The receipt names a different immutable DVM ID than the builder attestation, so it is not evidence for this provider.";
10152
- case "receipt_key_mismatch":
10153
- return "The receipt was signed by a different key than the one the DVM's builder attested.";
10154
- case "result_hash_mismatch":
10155
- return "The receipt attests a different result than the one delivered.";
10156
- }
10157
- }
10158
- var MAX_TIMESTAMP_MS = 864e13;
10159
- function checkJobId(receipt, expectedJobId) {
10160
- if (expectedJobId === void 0) return "unchecked";
10161
- return receipt.job_id === expectedJobId ? "ok" : "mismatch";
10162
- }
10163
- function checkDrainField(actual, expected) {
10164
- if (expected === void 0 || actual === void 0) return "unchecked";
10165
- return actual === expected ? "ok" : "mismatch";
10166
- }
10167
- var VERDICT_PREFIX = {
10168
- verified: "Verified receipt",
10169
- verified_unattested: "Receipt (signed, unattested)",
10170
- invalid: "UNVERIFIED receipt",
10171
- absent: "No receipt"
10172
- };
10173
- function checkResultHash(receipt, delivered) {
10174
- if (!delivered) return "unchecked";
10175
- if (receipt.result_hash === null) return "unchecked";
10176
- const summary = receipt.outcome === "completed" ? delivered.summary : void 0;
10177
- let recomputed;
10178
- try {
10179
- recomputed = computeResultHash(summary, delivered.artifactContents);
10180
- } catch {
10181
- return "unchecked";
8340
+ if (error.code === "caller_mismatch" || error.code === "credit_not_found") {
8341
+ return jsonPaymentError("credit_not_found", 404, {
8342
+ message: `No credit ${creditId} for this caller.`
8343
+ });
10182
8344
  }
10183
- return recomputed === receipt.result_hash ? "ok" : "mismatch";
10184
- }
10185
- function isTrustAnchor(value) {
10186
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
10187
- const anchor = value;
10188
- return typeof anchor.pubkey === "string" && typeof anchor.signature === "string" && !!anchor.attestation && typeof anchor.attestation === "object" && !Array.isArray(anchor.attestation);
10189
- }
10190
- function formatPaid(msats, rate) {
10191
- if (msats === 0) return "nothing (free job)";
10192
- const sats = msats % 1e3 === 0 ? msats / 1e3 : Math.round(msats / 1e3 * 100) / 100;
10193
- const satsLabel = `${sats.toLocaleString("en-US")} sats`;
10194
- if (rate <= 0) return satsLabel;
10195
- const usd = msatsToUsd(msats, rate);
10196
- return `~$${formatUsd(usd)} (${satsLabel}, ${costAnchor(usd)})`;
10197
- }
10198
-
10199
- // src/lib/request-id.ts
10200
- var MAX_REQUEST_ID_LENGTH = 128;
10201
- var REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
10202
- function isPublicRequestId(value) {
10203
- return typeof value === "string" && value.length > 0 && value.length <= MAX_REQUEST_ID_LENGTH && REQUEST_ID_PATTERN.test(value);
8345
+ return jsonPaymentError(mapLedgerCode(error), 402, { message: error.message });
10204
8346
  }
10205
-
10206
- // src/lib/traceparent.ts
10207
- var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
10208
- var ALL_ZERO_TRACE = "00000000000000000000000000000000";
10209
- var ALL_ZERO_SPAN = "0000000000000000";
10210
- function parseTraceparent(header) {
10211
- if (!header) return null;
10212
- const match = TRACEPARENT_RE.exec(header.trim());
10213
- if (!match) return null;
10214
- const [, traceId, parentSpanId, flagsHex] = match;
10215
- if (traceId === ALL_ZERO_TRACE || parentSpanId === ALL_ZERO_SPAN) return null;
10216
- return { traceId, parentSpanId, flags: parseInt(flagsHex, 16) };
8347
+ function isHttpResponse(value) {
8348
+ return value instanceof Response || typeof value === "object" && "headers" in value && typeof value.status === "number";
10217
8349
  }
10218
-
10219
- // src/lib/wallet-agent/keypair.ts
10220
- import { secp256k1 as secp256k12 } from "@noble/curves/secp256k1.js";
10221
- var PRIVKEY_HEX_RE = /^[0-9a-f]{64}$/i;
10222
- function derivePubkeyFromPrivkey(privkey) {
10223
- if (!PRIVKEY_HEX_RE.test(privkey)) {
10224
- throw new DvmError(
10225
- "invalid_privkey",
10226
- "Privkey must be 64 hex characters (32 bytes).",
10227
- "Provide a valid secp256k1 private key as a 64-char hex string."
10228
- );
10229
- }
10230
- const bytes = hexToBytes4(privkey);
10231
- if (!secp256k12.utils.isValidSecretKey(bytes)) {
10232
- throw new DvmError(
10233
- "invalid_privkey",
10234
- "Privkey is not a valid secp256k1 scalar (zero or out of curve order).",
10235
- "Generate a fresh key with: dvm wallet init"
10236
- );
8350
+ function mapLedgerCode(err) {
8351
+ switch (err.code) {
8352
+ case "insufficient_credit":
8353
+ case "credit_expired":
8354
+ case "credit_unbacked":
8355
+ case "credit_not_found":
8356
+ case "tempo_channel_closing":
8357
+ case "settlement_pending":
8358
+ case "nothing_to_drain":
8359
+ case "drain_below_dust":
8360
+ case "drain_not_found":
8361
+ case "drain_conflict":
8362
+ return err.code;
8363
+ default:
8364
+ return "payment_invalid";
10237
8365
  }
10238
- const pub = secp256k12.getPublicKey(bytes);
10239
- return bytesToHex4(pub);
10240
8366
  }
10241
- function bytesToHex4(bytes) {
10242
- return Buffer.from(bytes).toString("hex");
8367
+ function creditBody(credit, reconciliationPending = false) {
8368
+ return {
8369
+ status: credit.status,
8370
+ credit_id: credit.creditId,
8371
+ currency: credit.currency,
8372
+ balance_micro: credit.balanceMicro,
8373
+ remaining_micro: credit.availableMicro,
8374
+ expiry_ms: credit.expiryMs,
8375
+ expired: credit.expired,
8376
+ // Reclaimable via op "drain" (internal-review) — true whenever spendable-or-
8377
+ // expired value remains. Expiry ends spending, never ownership.
8378
+ drainable: credit.status === "active" && credit.availableMicro > 0,
8379
+ ...reconciliationPending && { reconciliation_pending: true },
8380
+ ledger_seq: credit.lastLedgerSeq
8381
+ };
10243
8382
  }
10244
- function hexToBytes4(hex) {
10245
- return Uint8Array.from(Buffer.from(hex, "hex"));
8383
+ function spendable(credit, currency) {
8384
+ return !credit.expired && credit.status === "active" && credit.currency === currency;
10246
8385
  }
10247
-
10248
- // src/lib/wallet-agent/sign.ts
10249
- import { schnorr as schnorr4 } from "@noble/curves/secp256k1.js";
10250
- var PUBKEY_COMPRESSED_HEX_RE = /^0[23][0-9a-f]{64}$/i;
10251
- var PUBKEY_XONLY_HEX_RE = /^[0-9a-f]{64}$/i;
10252
- var SIG_HEX_RE = /^[0-9a-f]{128}$/i;
10253
- function signChallenge(privkeyHex, challenge) {
10254
- if (!PRIVKEY_HEX_RE.test(privkeyHex)) {
10255
- throw new DvmError(
10256
- "invalid_privkey",
10257
- "Privkey must be 64 hex characters (32 bytes).",
10258
- "Provide a valid secp256k1 private key as a 64-char hex string."
10259
- );
10260
- }
10261
- if (challenge.length !== 32) {
10262
- throw new DvmError(
10263
- "invalid_challenge",
10264
- `Challenge must be exactly 32 bytes; got ${challenge.length}.`,
10265
- "Pass the SHA-256 digest output (32 bytes)."
10266
- );
10267
- }
10268
- const sig = schnorr4.sign(challenge, hexToBytes5(privkeyHex));
10269
- return bytesToHex5(sig);
10270
- }
10271
- function verifyChallenge(pubkeyHex, challenge, sigHex) {
10272
- if (!SIG_HEX_RE.test(sigHex)) return false;
10273
- if (challenge.length !== 32) return false;
10274
- let xonly;
10275
- if (PUBKEY_COMPRESSED_HEX_RE.test(pubkeyHex)) {
10276
- xonly = hexToBytes5(pubkeyHex.slice(2));
10277
- } else if (PUBKEY_XONLY_HEX_RE.test(pubkeyHex)) {
10278
- xonly = hexToBytes5(pubkeyHex);
10279
- } else {
10280
- return false;
10281
- }
10282
- try {
10283
- return schnorr4.verify(hexToBytes5(sigHex), challenge, xonly);
10284
- } catch {
10285
- return false;
8386
+ function pickPrimary(credits, currency) {
8387
+ let best;
8388
+ for (const credit of credits) {
8389
+ if (!spendable(credit, currency) || credit.availableMicro <= 0) continue;
8390
+ if (!best || credit.availableMicro > best.availableMicro || credit.availableMicro === best.availableMicro && credit.createdAt > best.createdAt) {
8391
+ best = credit;
8392
+ }
10286
8393
  }
10287
- }
10288
- function hexToBytes5(hex) {
10289
- return Uint8Array.from(Buffer.from(hex, "hex"));
10290
- }
10291
- function bytesToHex5(bytes) {
10292
- return Buffer.from(bytes).toString("hex");
10293
- }
10294
-
10295
- // src/lib/cashu/admin-auth.ts
10296
- import { randomBytes as randomBytes3 } from "crypto";
10297
- import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
10298
- var ADMIN_AUTH_NONCE_TTL_SECONDS = 300;
10299
- var ADMIN_AUTH_CLOCK_SKEW_SECONDS = 30;
10300
- var ADMIN_STATE_UNAVAILABLE = "admin_state_unavailable";
10301
- function buildAdminChallenge(args) {
10302
- const msg = `dvmkit-admin:${args.method}:${args.path}:${args.dvmId}:${args.nonceHex}:${args.unixSeconds}`;
10303
- return sha2564(new TextEncoder().encode(msg));
10304
- }
10305
- function buildAdminAuthHeader(args) {
10306
- const nowMs = args.now ? args.now() : Date.now();
10307
- const unixSeconds = Math.floor(nowMs / 1e3);
10308
- const nonceBytes = args.randomNonce ? args.randomNonce() : Uint8Array.from(randomBytes3(16));
10309
- const nonceHex = Buffer.from(nonceBytes).toString("hex");
10310
- const challenge = buildAdminChallenge({
10311
- method: args.method,
10312
- path: args.path,
10313
- dvmId: args.dvmId,
10314
- nonceHex,
10315
- unixSeconds
10316
- });
10317
- const sigHex = signChallenge(args.privkeyHex, challenge);
10318
- return {
10319
- header: `P2PK ${nonceHex}.${unixSeconds}.${sigHex}`,
10320
- nonceHex,
10321
- unixSeconds
10322
- };
8394
+ return best;
10323
8395
  }
10324
8396
 
10325
8397
  // src/sdk/server/admin-cashu.ts
@@ -10653,7 +8725,7 @@ async function handleRotateLockPubkey(c, opts) {
10653
8725
  }
10654
8726
 
10655
8727
  // src/sdk/server/x402-exact-settlement.ts
10656
- import { createHash as createHash5 } from "crypto";
8728
+ import { createHash as createHash4 } from "crypto";
10657
8729
  var X402ExactSettlementServer = class {
10658
8730
  constructor(opts) {
10659
8731
  this.opts = opts;
@@ -10981,7 +9053,7 @@ function settlementMayHaveMoved(response) {
10981
9053
  return reason.includes("authorization_already_used") || reason.includes("authorization already used") || /facilitator_status_5\d\d/.test(reason);
10982
9054
  }
10983
9055
  function hash(parts) {
10984
- return createHash5("sha256").update(JSON.stringify(parts)).digest("hex");
9056
+ return createHash4("sha256").update(JSON.stringify(parts)).digest("hex");
10985
9057
  }
10986
9058
  function safeError(err) {
10987
9059
  if (err instanceof Error) return err.name;
@@ -11005,31 +9077,16 @@ function jobCredentialGate(opts) {
11005
9077
  // (2026-06-29, paid jobs whose results were unretrievable behind an
11006
9078
  // opaque 404) is only one of two ways to get here — a current client
11007
9079
  // reading a job whose local state has no token gets the same refusal,
11008
- // and "upgrade" would be a dead end for them. `display` is relayed to
11009
- // a human verbatim, so the conditional advice lives in `hint`, which
9080
+ // and an upgrade alone would be a dead end for them. `display` is relayed
9081
+ // to a human verbatim, so the conditional advice lives in `hint`, which
11010
9082
  // is what the agent acts on.
11011
9083
  display: "Couldn't retrieve your result \u2014 the request was missing its job credential.",
11012
- hint: `Send the job_token from the POST /v1/job response as the X-Job-Token header${alternative}. The token is minted once, at submit, and the DVM cannot reissue it \u2014 a job submitted from another machine, or by a caller that never stored it, can't be read here. If your caller never sends X-Job-Token at all, upgrade: npm i -g @dvmkit/dvm-cli@0.1.4.`
9084
+ hint: `Send the job_token from the POST /v1/job response as the X-Job-Token header${alternative}. The token is minted once, at submit, and the DVM cannot reissue it \u2014 a job submitted from another machine, or by a caller that never stored it, can't be read here. If your caller never sends X-Job-Token at all, upgrade: npm i -g @dvmkit/dvm-cli@0.2.0, then retry the same command with --endpoint <url> --job-token <hex> (or set DVM_JOB_ENDPOINT and DVM_JOB_TOKEN).`
11013
9085
  };
11014
9086
  }
11015
9087
  };
11016
9088
  }
11017
9089
 
11018
- // src/lib/monotonic-clock.ts
11019
- function createMonotonicClock() {
11020
- let anchorWallMs = Date.now();
11021
- let anchorMonoMs = performance.now();
11022
- return () => {
11023
- const wallMs = Date.now();
11024
- const monoMs = performance.now();
11025
- const projectedMs = anchorWallMs + (monoMs - anchorMonoMs);
11026
- const nowMs = wallMs >= projectedMs ? wallMs : projectedMs;
11027
- anchorWallMs = nowMs;
11028
- anchorMonoMs = monoMs;
11029
- return Math.floor(nowMs);
11030
- };
11031
- }
11032
-
11033
9090
  // src/sdk/server/request-envelope.ts
11034
9091
  var REQUEST_ID_FIELD = "request_id";
11035
9092
  var AUTH_STATEMENT_FIELD = "auth_statement";
@@ -11038,7 +9095,7 @@ function protocolEnvelopeIgnoreFields(schema) {
11038
9095
  }
11039
9096
  function stripProtocolEnvelope(schema, data) {
11040
9097
  const withoutCredit = stripCreditEnvelope(schema, data);
11041
- if (!isRecord3(withoutCredit)) return withoutCredit;
9098
+ if (!isRecord2(withoutCredit)) return withoutCredit;
11042
9099
  const {
11043
9100
  [REQUEST_ID_FIELD]: _requestId,
11044
9101
  [AUTH_STATEMENT_FIELD]: _authStatement,
@@ -11047,26 +9104,26 @@ function stripProtocolEnvelope(schema, data) {
11047
9104
  return builderInput;
11048
9105
  }
11049
9106
  function requestIdFromEnvelope(data) {
11050
- if (!isRecord3(data) || !(REQUEST_ID_FIELD in data)) return void 0;
9107
+ if (!isRecord2(data) || !(REQUEST_ID_FIELD in data)) return void 0;
11051
9108
  return isPublicRequestId(data[REQUEST_ID_FIELD]) ? data[REQUEST_ID_FIELD] : void 0;
11052
9109
  }
11053
9110
  function hasInvalidRequestId(data) {
11054
- return isRecord3(data) && REQUEST_ID_FIELD in data && !isPublicRequestId(data[REQUEST_ID_FIELD]);
9111
+ return isRecord2(data) && REQUEST_ID_FIELD in data && !isPublicRequestId(data[REQUEST_ID_FIELD]);
11055
9112
  }
11056
- function isRecord3(value) {
9113
+ function isRecord2(value) {
11057
9114
  return typeof value === "object" && value !== null && !Array.isArray(value);
11058
9115
  }
11059
9116
 
11060
9117
  // src/sdk/server/job-manager.ts
11061
- import { randomBytes as randomBytes5 } from "crypto";
9118
+ import { randomBytes as randomBytes4 } from "crypto";
11062
9119
 
11063
9120
  // src/sdk/server/context.ts
11064
- import { randomBytes as randomBytes4 } from "crypto";
9121
+ import { randomBytes as randomBytes3 } from "crypto";
11065
9122
 
11066
9123
  // src/sdk/server/instrumented-kv.ts
11067
- import { createHash as createHash6 } from "crypto";
9124
+ import { createHash as createHash5 } from "crypto";
11068
9125
  function hashKey(key) {
11069
- return createHash6("sha256").update(key).digest("hex").slice(0, 16);
9126
+ return createHash5("sha256").update(key).digest("hex").slice(0, 16);
11070
9127
  }
11071
9128
  function instrumentKVStore(inner) {
11072
9129
  return {
@@ -11305,19 +9362,21 @@ function buildContext(job, opts) {
11305
9362
  let x402Nonce;
11306
9363
  let x402AmountUsdcMicro;
11307
9364
  const hasExplicitX402 = Boolean(payOpts?.x402);
11308
- if (payOpts?.x402) {
11309
- paymentRequestContent.x402 = payOpts.x402;
11310
- } else if (opts.paymentMethods?.includes("x402") && opts.x402 && (opts.x402FacilitatorHealth?.support().available ?? true)) {
11311
- paymentRequestContent.x402 = {
11312
- pay_to: opts.x402.payTo,
11313
- network: opts.x402.network,
11314
- asset: opts.x402.asset ?? "USDC",
11315
- ...opts.x402.facilitator && { facilitator: opts.x402.facilitator }
11316
- };
9365
+ if ((opts.pricingCurrency ?? "usd") === "usd") {
9366
+ if (payOpts?.x402) {
9367
+ paymentRequestContent.x402 = payOpts.x402;
9368
+ } else if (opts.paymentMethods?.includes("x402") && opts.x402 && (opts.x402FacilitatorHealth?.support().available ?? true)) {
9369
+ paymentRequestContent.x402 = {
9370
+ pay_to: opts.x402.payTo,
9371
+ network: opts.x402.network,
9372
+ asset: opts.x402.asset ?? "USDC",
9373
+ ...opts.x402.facilitator && { facilitator: opts.x402.facilitator }
9374
+ };
9375
+ }
11317
9376
  }
11318
9377
  if (paymentRequestContent.x402) {
11319
9378
  paymentRate = (await resolveServerBtcUsdRate(opts.fxFetcher)).rate;
11320
- x402Nonce = `0x${randomBytes4(32).toString("hex")}`;
9379
+ x402Nonce = `0x${randomBytes3(32).toString("hex")}`;
11321
9380
  x402AmountUsdcMicro = msatsToUsdc(amountMsats, paymentRate).toString();
11322
9381
  const x402Request = paymentRequestContent.x402;
11323
9382
  const challengeConfig = {
@@ -11888,6 +9947,11 @@ var REACTIVATION_CLAIM_POLL_MS = 25;
11888
9947
  var DEFAULT_ORPHAN_DRAW_AGE_MS = 15 * 6e4;
11889
9948
  var DEFAULT_TERMINAL_DRAW_RECONCILE_AGE_MS = 30 * 6e4;
11890
9949
  var DEFAULT_ORPHAN_DRAW_SWEEP_INTERVAL_MS = 5 * 6e4;
9950
+ var DEFAULT_JOB_RETENTION_SWEEP_INTERVAL_MS = 24 * 60 * 6e4;
9951
+ var DEFAULT_JOB_RETENTION_BOOT_DELAY_MS = 3e4;
9952
+ var JOB_RETENTION_SWEEP_BATCH = 50;
9953
+ var JOB_RETENTION_SWEEP_MAX_PAGES = 20;
9954
+ var DAY_MS = 24 * 60 * 6e4;
11891
9955
  var ORPHAN_DRAW_SWEEP_BATCH = 50;
11892
9956
  var ORPHAN_DRAW_SWEEP_MAX_PAGES = 20;
11893
9957
  var ANY_DRAW_AGE_CUTOFF_MS = Number.MAX_SAFE_INTEGER;
@@ -11909,7 +9973,7 @@ var JobManager = class {
11909
9973
  statusChecksInFlight = /* @__PURE__ */ new Set();
11910
9974
  /** Job ids that were notified mid-re-read and must be re-checked (internal-review). */
11911
9975
  statusChecksQueued = /* @__PURE__ */ new Set();
11912
- sessionId = randomBytes5(3).toString("hex");
9976
+ sessionId = randomBytes4(3).toString("hex");
11913
9977
  nextJobId = 1;
11914
9978
  accumulatorMonitor;
11915
9979
  staleJobTimeoutMs;
@@ -11926,10 +9990,13 @@ var JobManager = class {
11926
9990
  staleSweepTimer;
11927
9991
  heartbeatTimer;
11928
9992
  orphanDrawSweepTimer;
9993
+ jobRetentionSweepTimer;
9994
+ jobRetentionBootTimer;
11929
9995
  /** This manager holds {@link ORPHAN_SWEEP_CLAIMS} for its ledger. */
11930
9996
  orphanSweepClaimed = false;
11931
9997
  /** Where a page-capped tick left off; cleared once a tick reaches the end. */
11932
9998
  orphanSweepResumeFrom;
9999
+ jobRetentionResumeFrom;
11933
10000
  /**
11934
10001
  * A `Date.now()` reading that cannot regress within this manager's lifetime
11935
10002
  * (internal-review). Anchored at construction, so a manager already running when
@@ -11940,6 +10007,7 @@ var JobManager = class {
11940
10007
  monotonicNowMs = createMonotonicClock();
11941
10008
  sweepInFlight = false;
11942
10009
  orphanSweepInFlight = false;
10010
+ jobRetentionSweepInFlight = false;
11943
10011
  heartbeatInFlight = false;
11944
10012
  /**
11945
10013
  * The denomination of every credit this DVM opens (internal-review). Resolved once,
@@ -12038,6 +10106,32 @@ var JobManager = class {
12038
10106
  }, orphanIntervalMs);
12039
10107
  this.orphanDrawSweepTimer.unref();
12040
10108
  }
10109
+ if (descriptor.jobRetentionDays > 0) {
10110
+ if (!isJobRetentionStore(this.jobStore)) {
10111
+ console.warn(
10112
+ JSON.stringify({
10113
+ level: "warn",
10114
+ event: "job_retention_store_unsupported",
10115
+ dvm: descriptor.name,
10116
+ retentionDays: descriptor.jobRetentionDays
10117
+ })
10118
+ );
10119
+ } else {
10120
+ const pass = () => {
10121
+ void this.sweepJobRetention().catch(() => void 0);
10122
+ };
10123
+ const bootDelayMs = opts.jobRetentionSweepBootDelayMs ?? DEFAULT_JOB_RETENTION_BOOT_DELAY_MS;
10124
+ if (bootDelayMs > 0) {
10125
+ this.jobRetentionBootTimer = setTimeout(pass, bootDelayMs);
10126
+ this.jobRetentionBootTimer.unref();
10127
+ }
10128
+ const intervalMs = opts.jobRetentionSweepIntervalMs ?? DEFAULT_JOB_RETENTION_SWEEP_INTERVAL_MS;
10129
+ if (intervalMs > 0) {
10130
+ this.jobRetentionSweepTimer = setInterval(pass, intervalMs);
10131
+ this.jobRetentionSweepTimer.unref();
10132
+ }
10133
+ }
10134
+ }
12041
10135
  }
12042
10136
  /** Active in-memory jobs. */
12043
10137
  get activeJobs() {
@@ -12738,47 +10832,212 @@ var JobManager = class {
12738
10832
  this.sweepInFlight = false;
12739
10833
  }
12740
10834
  }
12741
- /**
12742
- * How far one watchdog arm may lean on {@link monotonicNowMs} past the wall
12743
- * clock to cover a backwards step (internal-review). The compensation is capped,
12744
- * not free: a claimed row must still have been silent for
12745
- * `windowMs - cap`, and unlike the orphan sweep, claiming eagerly here
12746
- * force-fails a job that may well be alive.
12747
- *
12748
- * **Half the window** is the floor that governs a wide window — five missed
12749
- * heartbeats on the processing defaults. On the `awaiting-input` arm it also
12750
- * preserves a real invariant: with the derived
12751
- * `2 x idleTimeout + headroom`, half is `idleTimeout + 45s`, so the sweeper
12752
- * still cannot fire before the in-process idle timer would have. (A builder
12753
- * who overrides `staleJobTimeoutMs` below `2 x idleTimeout` has already
12754
- * opted out of that, guard or no guard.)
12755
- *
12756
- * **Two heartbeat intervals** is the floor that governs a window configured
12757
- * close to the beat cadence — `processingWatchdog: 40` against the 30s
12758
- * default beat, where half the window is less than one beat and a worker
12759
- * heartbeating exactly on schedule would be reaped the moment the host's
12760
- * clock stepped. That pair is marginal already; the guard must not make it
12761
- * deterministic. The cap collapses to zero there, which is today's
12762
- * behaviour: late, never wrong. The `awaiting-input` arm takes no such term
12763
- * — nothing heartbeats it by design, and its refresh is an inbound caller
12764
- * message on no cadence at all.
12765
- *
12766
- * **Why a magnitude and not a predicate.** The tempting sharper rule is to
12767
- * compensate per row, fully, for any stamp that reads ahead of our wall —
12768
- * one our own post-step heartbeat could not have written. It is unsafe: a
12769
- * future-stamped row is equally what a live job heartbeated by a peer whose
12770
- * clock runs fast looks like, and from the stamp alone the two are
12771
- * indistinguishable. The same reasoning bounds the cap from the other side —
12772
- * while our clock is stepped, the guard force-fails the live jobs of any
12773
- * peer slower than `windowMs - cap`, which is 2.5 minutes of tolerated
12774
- * disagreement on the defaults, far outside anything NTP-managed hosts show.
12775
- */
12776
- staleSweepCompensationCapMs(windowMs, heartbeated) {
12777
- if (windowMs <= 0) return 0;
12778
- const halfWindow = Math.floor(windowMs / 2);
12779
- if (!heartbeated || this.heartbeatIntervalMs <= 0) return halfWindow;
12780
- return Math.max(0, Math.min(halfWindow, windowMs - 2 * this.heartbeatIntervalMs));
12781
- }
10835
+ /**
10836
+ * How far one watchdog arm may lean on {@link monotonicNowMs} past the wall
10837
+ * clock to cover a backwards step (internal-review). The compensation is capped,
10838
+ * not free: a claimed row must still have been silent for
10839
+ * `windowMs - cap`, and unlike the orphan sweep, claiming eagerly here
10840
+ * force-fails a job that may well be alive.
10841
+ *
10842
+ * **Half the window** is the floor that governs a wide window — five missed
10843
+ * heartbeats on the processing defaults. On the `awaiting-input` arm it also
10844
+ * preserves a real invariant: with the derived
10845
+ * `2 x idleTimeout + headroom`, half is `idleTimeout + 45s`, so the sweeper
10846
+ * still cannot fire before the in-process idle timer would have. (A builder
10847
+ * who overrides `staleJobTimeoutMs` below `2 x idleTimeout` has already
10848
+ * opted out of that, guard or no guard.)
10849
+ *
10850
+ * **Two heartbeat intervals** is the floor that governs a window configured
10851
+ * close to the beat cadence — `processingWatchdog: 40` against the 30s
10852
+ * default beat, where half the window is less than one beat and a worker
10853
+ * heartbeating exactly on schedule would be reaped the moment the host's
10854
+ * clock stepped. That pair is marginal already; the guard must not make it
10855
+ * deterministic. The cap collapses to zero there, which is today's
10856
+ * behaviour: late, never wrong. The `awaiting-input` arm takes no such term
10857
+ * — nothing heartbeats it by design, and its refresh is an inbound caller
10858
+ * message on no cadence at all.
10859
+ *
10860
+ * **Why a magnitude and not a predicate.** The tempting sharper rule is to
10861
+ * compensate per row, fully, for any stamp that reads ahead of our wall —
10862
+ * one our own post-step heartbeat could not have written. It is unsafe: a
10863
+ * future-stamped row is equally what a live job heartbeated by a peer whose
10864
+ * clock runs fast looks like, and from the stamp alone the two are
10865
+ * indistinguishable. The same reasoning bounds the cap from the other side —
10866
+ * while our clock is stepped, the guard force-fails the live jobs of any
10867
+ * peer slower than `windowMs - cap`, which is 2.5 minutes of tolerated
10868
+ * disagreement on the defaults, far outside anything NTP-managed hosts show.
10869
+ */
10870
+ staleSweepCompensationCapMs(windowMs, heartbeated) {
10871
+ if (windowMs <= 0) return 0;
10872
+ const halfWindow = Math.floor(windowMs / 2);
10873
+ if (!heartbeated || this.heartbeatIntervalMs <= 0) return halfWindow;
10874
+ return Math.max(0, Math.min(halfWindow, windowMs - 2 * this.heartbeatIntervalMs));
10875
+ }
10876
+ // ── Terminal job content retention ───────────────────────────────────
10877
+ /**
10878
+ * Redact terminal job content beyond this DVM's configured window.
10879
+ *
10880
+ * The scan advances over every candidate, including rows that fail draw
10881
+ * resolution, so one money-path fault cannot starve everything behind it.
10882
+ * Any failed row remains unredacted and the next tick restarts from the head;
10883
+ * a page-capped clean pass instead retains its cursor and resumes at the
10884
+ * tail. Failure and recovery callbacks are awaited but never allowed to
10885
+ * change the sweep result.
10886
+ */
10887
+ async sweepJobRetention() {
10888
+ const idle = { examined: 0, redacted: 0, complete: false };
10889
+ if (this.descriptor.jobRetentionDays <= 0 || !isJobRetentionStore(this.jobStore)) return idle;
10890
+ if (this.jobRetentionSweepInFlight) return idle;
10891
+ this.jobRetentionSweepInFlight = true;
10892
+ let examined = 0;
10893
+ let redacted = 0;
10894
+ let complete = false;
10895
+ let cursor = this.jobRetentionResumeFrom;
10896
+ const failures = [];
10897
+ const now = Date.now();
10898
+ const lastActivityBeforeMs = now - this.descriptor.jobRetentionDays * DAY_MS;
10899
+ try {
10900
+ for (let page = 0; page < JOB_RETENTION_SWEEP_MAX_PAGES; page++) {
10901
+ const candidates = await this.jobStore.listJobsForRetention({
10902
+ lastActivityBeforeMs,
10903
+ limit: JOB_RETENTION_SWEEP_BATCH,
10904
+ capabilities: this.capabilityNames(),
10905
+ after: cursor
10906
+ });
10907
+ for (const record of candidates) {
10908
+ examined++;
10909
+ cursor = { lastActivityAt: record.lastActivityAt, id: record.id };
10910
+ try {
10911
+ await this.prepareJobForRedaction(record, now);
10912
+ const changed = await this.jobStore.redactJob({
10913
+ jobId: record.id,
10914
+ expectedLastActivityAt: record.lastActivityAt,
10915
+ redactedAt: now
10916
+ });
10917
+ if (changed) redacted++;
10918
+ } catch (err) {
10919
+ const failure = err instanceof Error ? err : new Error(String(err));
10920
+ failures.push(failure);
10921
+ console.error(
10922
+ JSON.stringify({
10923
+ level: "error",
10924
+ event: "job_retention_candidate_failed",
10925
+ jobId: record.id,
10926
+ error: failure.message
10927
+ })
10928
+ );
10929
+ }
10930
+ }
10931
+ if (candidates.length < JOB_RETENTION_SWEEP_BATCH) {
10932
+ complete = true;
10933
+ break;
10934
+ }
10935
+ }
10936
+ if (failures.length > 0) {
10937
+ this.jobRetentionResumeFrom = void 0;
10938
+ throw new AggregateError(failures, `${failures.length} job retention candidate(s) failed`);
10939
+ }
10940
+ this.jobRetentionResumeFrom = complete ? void 0 : cursor;
10941
+ console.log(
10942
+ JSON.stringify({
10943
+ level: "info",
10944
+ event: "job_retention_swept",
10945
+ retentionDays: this.descriptor.jobRetentionDays,
10946
+ cutoffMs: lastActivityBeforeMs,
10947
+ examined,
10948
+ redacted,
10949
+ complete
10950
+ })
10951
+ );
10952
+ if (complete) {
10953
+ await this.notifyJobRetentionRecovered({ examined, redacted });
10954
+ }
10955
+ return { examined, redacted, complete };
10956
+ } catch (err) {
10957
+ this.jobRetentionResumeFrom = void 0;
10958
+ const error = err instanceof Error ? err.message : String(err);
10959
+ console.error(
10960
+ JSON.stringify({
10961
+ level: "error",
10962
+ event: "job_retention_sweep_failed",
10963
+ retentionDays: this.descriptor.jobRetentionDays,
10964
+ examined,
10965
+ redacted,
10966
+ error
10967
+ })
10968
+ );
10969
+ await this.notifyJobRetentionFailed(error);
10970
+ throw err;
10971
+ } finally {
10972
+ this.jobRetentionSweepInFlight = false;
10973
+ }
10974
+ }
10975
+ /** Resolve every hold and persist any configured receipt before content disappears. */
10976
+ async prepareJobForRedaction(record, nowMs) {
10977
+ const ledger = this.opts.creditLedger;
10978
+ if (!ledger) {
10979
+ if (record.creditId || record.drawId) {
10980
+ throw new Error("job carries a credit draw but no ledger is configured");
10981
+ }
10982
+ } else {
10983
+ const draws = await ledger.listDrawsByJobId(record.id);
10984
+ for (const draw of draws) {
10985
+ if (draw.status !== "pending") continue;
10986
+ await this.reconcileTerminalDraw(ledger, draw, record, nowMs);
10987
+ }
10988
+ const stillPending = (await ledger.listDrawsByJobId(record.id)).filter(
10989
+ (draw) => draw.status === "pending"
10990
+ );
10991
+ if (stillPending.length > 0) {
10992
+ throw new Error(
10993
+ `job still has ${stillPending.length} pending credit draw(s): ${stillPending.map((draw) => draw.drawId).join(", ")}`
10994
+ );
10995
+ }
10996
+ }
10997
+ if (!record.receipt && this.opts.receiptIssuer && isReceiptIssuingStore(this.jobStore) && !await this.issueReceipt(record)) {
10998
+ throw new Error("signed receipt could not be persisted");
10999
+ }
11000
+ }
11001
+ async notifyJobRetentionFailed(error) {
11002
+ const callback = this.opts.onJobRetentionSweepFailed;
11003
+ if (!callback) return;
11004
+ try {
11005
+ await callback({
11006
+ dvmId: this.opts.dvmId ?? "",
11007
+ dvmName: this.descriptor.name,
11008
+ retentionDays: this.descriptor.jobRetentionDays,
11009
+ error
11010
+ });
11011
+ } catch (callbackError) {
11012
+ console.error(
11013
+ JSON.stringify({
11014
+ level: "error",
11015
+ event: "job_retention_alert_failed",
11016
+ error: callbackError instanceof Error ? callbackError.message : String(callbackError)
11017
+ })
11018
+ );
11019
+ }
11020
+ }
11021
+ async notifyJobRetentionRecovered(result) {
11022
+ const callback = this.opts.onJobRetentionSweepRecovered;
11023
+ if (!callback) return;
11024
+ try {
11025
+ await callback({
11026
+ dvmId: this.opts.dvmId ?? "",
11027
+ dvmName: this.descriptor.name,
11028
+ retentionDays: this.descriptor.jobRetentionDays,
11029
+ ...result
11030
+ });
11031
+ } catch (callbackError) {
11032
+ console.error(
11033
+ JSON.stringify({
11034
+ level: "error",
11035
+ event: "job_retention_recovery_alert_failed",
11036
+ error: callbackError instanceof Error ? callbackError.message : String(callbackError)
11037
+ })
11038
+ );
11039
+ }
11040
+ }
12782
11041
  // ── Orphan-draw sweeper (internal-review) + terminal reconciler (internal-review) ──
12783
11042
  /**
12784
11043
  * Resolve `pending` credit draws the terminal funnel can no longer reach.
@@ -13277,6 +11536,7 @@ var JobManager = class {
13277
11536
  {
13278
11537
  mints: this.opts.mints,
13279
11538
  x402Config: this.opts.x402,
11539
+ pricingCurrency: this.pricingCurrency,
13280
11540
  x402ExactSettlement: this.opts.x402ExactSettlement,
13281
11541
  x402ExactVersions: this.opts.x402FacilitatorHealth?.support().exact,
13282
11542
  mpp: this.opts.mpp,
@@ -14088,6 +12348,14 @@ var JobManager = class {
14088
12348
  clearInterval(this.orphanDrawSweepTimer);
14089
12349
  this.orphanDrawSweepTimer = void 0;
14090
12350
  }
12351
+ if (this.jobRetentionBootTimer) {
12352
+ clearTimeout(this.jobRetentionBootTimer);
12353
+ this.jobRetentionBootTimer = void 0;
12354
+ }
12355
+ if (this.jobRetentionSweepTimer) {
12356
+ clearInterval(this.jobRetentionSweepTimer);
12357
+ this.jobRetentionSweepTimer = void 0;
12358
+ }
14091
12359
  if (this.orphanSweepClaimed && this.opts.creditLedger) {
14092
12360
  ORPHAN_SWEEP_CLAIMS.delete(this.opts.creditLedger);
14093
12361
  this.orphanSweepClaimed = false;
@@ -15207,7 +13475,7 @@ function withWallClockDeadline(promise, timeoutMs) {
15207
13475
  }
15208
13476
 
15209
13477
  // src/sdk/server/app.ts
15210
- import { createHash as createHash8, randomBytes as randomBytes6, randomUUID as randomUUID4 } from "crypto";
13478
+ import { createHash as createHash7, randomBytes as randomBytes5, randomUUID as randomUUID4 } from "crypto";
15211
13479
  import { Hono } from "hono";
15212
13480
  import { getCookie } from "hono/cookie";
15213
13481
  import { cors } from "hono/cors";
@@ -15229,7 +13497,7 @@ import {
15229
13497
  Amount as Amount3,
15230
13498
  CheckStateEnum as CheckStateEnum3,
15231
13499
  getP2PKExpectedWitnessPubkeys as getP2PKExpectedWitnessPubkeys2,
15232
- getTokenMetadata as getTokenMetadata2
13500
+ getTokenMetadata
15233
13501
  } from "@cashu/cashu-ts";
15234
13502
  function installAdminCreditRoutes(app, opts) {
15235
13503
  app.get("/admin/credit/pending-drains", (c) => handlePendingDrains(c, opts));
@@ -15505,6 +13773,15 @@ async function handleBitcoinLiability(c, opts) {
15505
13773
  async function handleParkDrain(c, opts) {
15506
13774
  const auth = await requireAuth(c, opts);
15507
13775
  if ("response" in auth) return auth.response;
13776
+ if (!opts.accumulatorWired) {
13777
+ return c.json(
13778
+ {
13779
+ error: "accumulator_not_wired",
13780
+ message: "Cashu accumulator persistence is not configured on this DVM."
13781
+ },
13782
+ 503
13783
+ );
13784
+ }
15508
13785
  let body;
15509
13786
  try {
15510
13787
  body = await c.req.json();
@@ -15592,7 +13869,7 @@ async function handleParkDrain(c, opts) {
15592
13869
  }
15593
13870
  let parsed;
15594
13871
  try {
15595
- const meta = getTokenMetadata2(token);
13872
+ const meta = getTokenMetadata(token);
15596
13873
  parsed = {
15597
13874
  mint: meta.mint,
15598
13875
  faceSats: Amount3.from(meta.amount).toNumber(),
@@ -15939,6 +14216,7 @@ async function handleReconcileInvoice(c, opts) {
15939
14216
  payload: {
15940
14217
  dvmId: opts.dvmId,
15941
14218
  creditId: targetCreditId,
14219
+ callerPubkey: invoice.callerPubkey,
15942
14220
  // The payment hash keys the deposit, as it does on the ordinary
15943
14221
  // settlement path — so a redelivered report collides rather
15944
14222
  // than double-counts.
@@ -17988,7 +16266,7 @@ initSchemaForm();
17988
16266
  }
17989
16267
 
17990
16268
  // src/sdk/server/request-fingerprint.ts
17991
- import { createHash as createHash7 } from "crypto";
16269
+ import { createHash as createHash6 } from "crypto";
17992
16270
  function requestFingerprint(req, opts) {
17993
16271
  const canonical = canonicalize({
17994
16272
  capability: req.capability,
@@ -17996,7 +16274,7 @@ function requestFingerprint(req, opts) {
17996
16274
  params: req.params ?? {},
17997
16275
  data: opts.stripAuthEnvelope ? stripEnvelope(req.data) : req.data
17998
16276
  });
17999
- return createHash7("sha256").update(canonical).digest("hex");
16277
+ return createHash6("sha256").update(canonical).digest("hex");
18000
16278
  }
18001
16279
  var ENVELOPE_KEYS = /* @__PURE__ */ new Set(["pubkey", "signature", "timestamp", "nonce", "auth_statement"]);
18002
16280
  function stripEnvelope(data) {
@@ -18045,12 +16323,12 @@ async function createDVMServer(descriptor, opts) {
18045
16323
  let revenueReporter;
18046
16324
  let payoutReporter;
18047
16325
  if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
18048
- const { RevenueReporter } = await import("./revenue-reporter-JIKUPXOK.js");
16326
+ const { RevenueReporter } = await import("./revenue-reporter-XXSU5KVB.js");
18049
16327
  const reporter = new RevenueReporter(opts.db, platformUrl, platformToken);
18050
16328
  await reporter.init();
18051
16329
  reporter.startRetryLoop();
18052
16330
  revenueReporter = reporter;
18053
- const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-3UB5WRCV.js");
16331
+ const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-RG6XNGPI.js");
18054
16332
  payoutReporter = new PayoutReporter({
18055
16333
  dvmId,
18056
16334
  db: opts.db,
@@ -18328,7 +16606,9 @@ var DVMServer = class {
18328
16606
  // needs-approval — reachable only from a rung that raises a real mid-job
18329
16607
  // ask, which nothing above L1 did. Facilitator availability is still
18330
16608
  // re-checked per ask in `context.ts`, so a static list is safe here.
18331
- paymentMethods: opts.paymentMethods ?? this.defaultPaymentMethods(),
16609
+ paymentMethods: (opts.paymentMethods ?? this.defaultPaymentMethods()).filter(
16610
+ (method) => method !== "x402" || this.x402CurrencySupported()
16611
+ ),
18332
16612
  x402: opts.x402,
18333
16613
  x402ExactSettlement: this.x402ExactSettlement,
18334
16614
  x402FacilitatorHealth: opts.x402FacilitatorHealth,
@@ -18353,6 +16633,8 @@ var DVMServer = class {
18353
16633
  onJobCompleted: opts.onJobCompleted,
18354
16634
  onPaidJobDeath: opts.onPaidJobDeath,
18355
16635
  onRevenueSkippedNoRail: opts.onRevenueSkippedNoRail,
16636
+ onJobRetentionSweepFailed: opts.onJobRetentionSweepFailed,
16637
+ onJobRetentionSweepRecovered: opts.onJobRetentionSweepRecovered,
18356
16638
  reactivationClaimGraceMs: opts.reactivationClaimGraceMs
18357
16639
  });
18358
16640
  this.app = new Hono();
@@ -18488,20 +16770,23 @@ var DVMServer = class {
18488
16770
  this.app.post("/v1/job/:id/messages", (c) => this.handlePostMessage(c));
18489
16771
  this.app.delete("/v1/job/:id", (c) => this.handleDeleteJob(c));
18490
16772
  this.app.post("/v1/credit", (c) => this.handleCredit(c));
18491
- if (this.opts.cashuMode === "p2pk-accumulator" && this.opts.db && this.opts.dvmId && this.opts.lockPubkey) {
16773
+ if (this.opts.db && this.opts.dvmId && this.opts.lockPubkey) {
18492
16774
  const db = this.opts.db;
18493
16775
  const dvmId = this.opts.dvmId;
18494
16776
  const payoutReporter = this.opts.payoutReporter;
18495
- installAdminCashuRoutes(this.app, {
18496
- db,
18497
- dvmId,
18498
- getLockPubkeyState: () => this.loadActiveLockPubkeyState(db, dvmId),
18499
- graceSeconds: this.graceSeconds(),
18500
- nonceStore: this.adminCashuNonceStore,
18501
- // internal-review: a completed melt is a landed payout, queued in the
18502
- // transaction that marks it.
18503
- ...payoutReporter ? { onMelted: payoutReporter.meltHook() } : {}
18504
- });
16777
+ const accumulatorWired = this.opts.cashuMode === "p2pk-accumulator";
16778
+ if (accumulatorWired) {
16779
+ installAdminCashuRoutes(this.app, {
16780
+ db,
16781
+ dvmId,
16782
+ getLockPubkeyState: () => this.loadActiveLockPubkeyState(db, dvmId),
16783
+ graceSeconds: this.graceSeconds(),
16784
+ nonceStore: this.adminCashuNonceStore,
16785
+ // internal-review: a completed melt is a landed payout, queued in the
16786
+ // transaction that marks it.
16787
+ ...payoutReporter ? { onMelted: payoutReporter.meltHook() } : {}
16788
+ });
16789
+ }
18505
16790
  installAdminCreditRoutes(this.app, {
18506
16791
  db,
18507
16792
  dvmId,
@@ -18509,6 +16794,7 @@ var DVMServer = class {
18509
16794
  fxFetcher: this.fxFetcher,
18510
16795
  getLockPubkeyState: () => this.loadActiveLockPubkeyState(db, dvmId),
18511
16796
  nonceStore: this.adminCashuNonceStore,
16797
+ accumulatorWired,
18512
16798
  receiptIssuer: this.receiptIssuer,
18513
16799
  creditTtlMs: () => this.creditTtlMs(),
18514
16800
  creditCurrency: () => this.creditCurrency(),
@@ -18826,7 +17112,7 @@ var DVMServer = class {
18826
17112
  const dvmId = this.opts.dvmId ?? `dev:${this.descriptor.name}`;
18827
17113
  return {
18828
17114
  dvm_id: dvmId,
18829
- builder_pubkey: createHash8("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
17115
+ builder_pubkey: createHash7("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
18830
17116
  };
18831
17117
  }
18832
17118
  throw new AuthAudienceError();
@@ -18882,7 +17168,7 @@ var DVMServer = class {
18882
17168
  const accumulatorReady = this.opts.cashuMode === "p2pk-accumulator" && !!this.opts.lockPubkey && !!this.opts.db;
18883
17169
  if (accumulatorReady) methods.push("cashu");
18884
17170
  }
18885
- if (this.opts.x402) methods.push("x402");
17171
+ if (this.opts.x402 && this.x402CurrencySupported()) methods.push("x402");
18886
17172
  if (this.opts.mpp) methods.push("tempo");
18887
17173
  return methods;
18888
17174
  }
@@ -18892,6 +17178,10 @@ var DVMServer = class {
18892
17178
  if (!this.opts.x402.batchSettlement?.selfRelay) return support;
18893
17179
  return { ...support, batchSettlement: true };
18894
17180
  }
17181
+ /** Whether this DVM can state its price exactly in x402's USDC unit. */
17182
+ x402CurrencySupported() {
17183
+ return this.pricingCurrency === "usd";
17184
+ }
18895
17185
  /**
18896
17186
  * Settleable rails for this DVM, honouring an explicit `opts.paymentMethods`
18897
17187
  * override and otherwise deriving from wired rails. Single source of truth so
@@ -18899,9 +17189,12 @@ var DVMServer = class {
18899
17189
  */
18900
17190
  resolvePaymentMethods() {
18901
17191
  const configured = this.opts.paymentMethods ?? this.defaultPaymentMethods();
18902
- if (!this.opts.x402) return configured;
18903
- const x402Available = this.x402Support()?.available ?? false;
18904
- return configured.filter((method) => method !== "x402" || x402Available);
17192
+ const currencyFiltered = configured.filter(
17193
+ (method) => method !== "x402" || this.x402CurrencySupported()
17194
+ );
17195
+ if (!this.opts.x402) return currencyFiltered;
17196
+ const x402Available = this.x402CurrencySupported() && (this.x402Support()?.available ?? false);
17197
+ return currencyFiltered.filter((method) => method !== "x402" || x402Available);
18905
17198
  }
18906
17199
  /**
18907
17200
  * Rails a **credit top-up** may arrive on — the funding menu's list
@@ -18919,7 +17212,9 @@ var DVMServer = class {
18919
17212
  const methods = this.resolvePaymentMethods().filter(
18920
17213
  (method) => method !== "tempo" || creditMpp
18921
17214
  );
18922
- if (this.x402BatchAvailable() && !methods.includes("x402")) methods.push("x402");
17215
+ if (this.x402CurrencySupported() && this.x402BatchAvailable() && !methods.includes("x402")) {
17216
+ methods.push("x402");
17217
+ }
18923
17218
  if (this.opts.lightningReceive?.available()) methods.push("lightning");
18924
17219
  return methods;
18925
17220
  }
@@ -18967,7 +17262,7 @@ var DVMServer = class {
18967
17262
  creditX402Schemes() {
18968
17263
  const x402 = this.opts.x402;
18969
17264
  const support = this.x402Support();
18970
- if (!x402 || !support) return [];
17265
+ if (!this.x402CurrencySupported() || !x402 || !support) return [];
18971
17266
  return [
18972
17267
  ...support.available ? [{ scheme: X402_EXACT_SCHEME, network: x402.network }] : [],
18973
17268
  ...this.x402BatchAvailable() ? [{ scheme: X402_BATCH_SETTLEMENT_SCHEME, network: x402.network }] : []
@@ -19009,6 +17304,7 @@ var DVMServer = class {
19009
17304
  if (replayed || !funding || !credit) continue;
19010
17305
  this.reportCreditDeposit({
19011
17306
  creditId: invoice.creditId,
17307
+ callerPubkey: credit.callerPubkey,
19012
17308
  // The payment hash is Lightning's own settlement reference, so it is
19013
17309
  // what keys the deposit — a redelivered report collides rather than
19014
17310
  // double-counts.
@@ -19061,7 +17357,7 @@ var DVMServer = class {
19061
17357
  }
19062
17358
  const x402Support = this.x402Support();
19063
17359
  const x402BatchSettlement = await this.resolveX402BatchSettlement();
19064
- const x402Payment = x402Support?.exact.v2 || x402Support?.batchSettlement ? c.req.header("PAYMENT-SIGNATURE") : void 0;
17360
+ const x402Payment = this.x402CurrencySupported() ? x402Support?.exact.v2 || x402Support?.batchSettlement ? c.req.header("PAYMENT-SIGNATURE") : void 0 : c.req.header("PAYMENT-SIGNATURE") ?? c.req.header("X-PAYMENT");
19065
17361
  return {
19066
17362
  cashuToken: c.req.header("X-Cashu"),
19067
17363
  mints: this.opts.mints,
@@ -19244,7 +17540,7 @@ var DVMServer = class {
19244
17540
  if (this.opts.mints) info.mints = this.advertisedMints();
19245
17541
  info.capabilities = this.renderCapabilities();
19246
17542
  info.payment = { methods: this.resolvePaymentMethods() };
19247
- if (this.opts.x402 && this.x402Support()?.available) {
17543
+ if (this.x402CurrencySupported() && this.opts.x402 && this.x402Support()?.available) {
19248
17544
  info.x402 = {
19249
17545
  pay_to: this.opts.x402.payTo,
19250
17546
  network: this.opts.x402.network,
@@ -19877,7 +18173,7 @@ var DVMServer = class {
19877
18173
  requesterId: c.get("requesterId"),
19878
18174
  requestFingerprint: fingerprint,
19879
18175
  jobId: this.jobManager.allocateJobId(),
19880
- claimToken: randomBytes6(32).toString("hex")
18176
+ claimToken: randomBytes5(32).toString("hex")
19881
18177
  };
19882
18178
  publicRequestClaimResult = resumeOnly ? await recoveryStore.resumeRequestId(publicRequestClaim) : await recoveryStore.claimRequestId(publicRequestClaim);
19883
18179
  if (!publicRequestClaimResult) {
@@ -20104,8 +18400,8 @@ var DVMServer = class {
20104
18400
  }
20105
18401
  }
20106
18402
  const isAnonymous = requesterId === "anonymous" || requesterId === "dev-anonymous";
20107
- const jobToken = isAnonymous ? randomBytes6(32).toString("hex") : void 0;
20108
- const requesterTokenHash = jobToken ? createHash8("sha256").update(jobToken).digest("hex") : void 0;
18403
+ const jobToken = isAnonymous ? randomBytes5(32).toString("hex") : void 0;
18404
+ const requesterTokenHash = jobToken ? createHash7("sha256").update(jobToken).digest("hex") : void 0;
20109
18405
  const job = jm.createJob(
20110
18406
  normalisedBody,
20111
18407
  requesterId,
@@ -20805,7 +19101,7 @@ function isJobOwner(c, job) {
20805
19101
  if (job.requesterTokenHash) {
20806
19102
  const token = c.req.header("X-Job-Token");
20807
19103
  if (!token) return false;
20808
- const provided = createHash8("sha256").update(token).digest("hex");
19104
+ const provided = createHash7("sha256").update(token).digest("hex");
20809
19105
  return timingSafeEqualHex(provided, job.requesterTokenHash);
20810
19106
  }
20811
19107
  return c.get("requesterId") === job.requesterId;
@@ -21071,567 +19367,6 @@ function validateMessageContent(type, content, devMode) {
21071
19367
  return null;
21072
19368
  }
21073
19369
 
21074
- // src/lib/nwc.ts
21075
- import { decrypt, encrypt } from "nostr-tools/nip04";
21076
- import { SimplePool, useWebSocketImplementation } from "nostr-tools/pool";
21077
- import { finalizeEvent, getPublicKey } from "nostr-tools/pure";
21078
- import WebSocket from "ws";
21079
-
21080
- // src/lib/hash.ts
21081
- import { createHash as createHash9 } from "crypto";
21082
- function sha256hex(input) {
21083
- return createHash9("sha256").update(input).digest("hex");
21084
- }
21085
-
21086
- // src/lib/nwc.ts
21087
- useWebSocketImplementation(WebSocket);
21088
- var NwcError = class extends Error {
21089
- constructor(code, message, method) {
21090
- super(message);
21091
- this.code = code;
21092
- this.method = method;
21093
- this.name = "NwcError";
21094
- }
21095
- code;
21096
- method;
21097
- };
21098
- function nwcTimeoutMs() {
21099
- return positiveSecondsEnv("DVM_NWC_TIMEOUT_SEC", 30);
21100
- }
21101
- function nwcDedupWindowMs() {
21102
- return positiveSecondsEnv("DVM_NWC_DEDUP_WINDOW_SEC", 240);
21103
- }
21104
- async function withNwcPool(conn, fn, timeoutMs) {
21105
- return withPool(void 0, conn, (pool) => fn({ pool, timeoutMs }));
21106
- }
21107
- function parseNwcUri(uri) {
21108
- if (!uri.startsWith("nostr+walletconnect://")) {
21109
- throw new NwcError("invalid_uri", "NWC URI must start with nostr+walletconnect://");
21110
- }
21111
- const withoutScheme = uri.slice("nostr+walletconnect://".length);
21112
- const [walletPubkey, queryString] = withoutScheme.split("?", 2);
21113
- if (!walletPubkey || !queryString) {
21114
- throw new NwcError("invalid_uri", "NWC URI missing pubkey or query params");
21115
- }
21116
- const params = new URLSearchParams(queryString);
21117
- const relays = params.getAll("relay").filter((r) => r.length > 0);
21118
- const secretHex = params.get("secret");
21119
- if (relays.length === 0) throw new NwcError("invalid_uri", "NWC URI missing relay param");
21120
- if (!secretHex) throw new NwcError("invalid_uri", "NWC URI missing secret param");
21121
- if (!/^[0-9a-fA-F]{64}$/.test(secretHex)) {
21122
- throw new NwcError("invalid_uri", "NWC URI secret must be 64 hex characters");
21123
- }
21124
- const secret = hexToBytes6(secretHex);
21125
- const clientPubkey = getPublicKey(secret);
21126
- return { walletPubkey, relays, secret, clientPubkey };
21127
- }
21128
- async function payInvoiceReconciling(conn, bolt11, opts = {}) {
21129
- return callerLoggers.lightning.span(
21130
- "lightning.pay_invoice",
21131
- {
21132
- relays: conn.relays.join(","),
21133
- wallet_pubkey: conn.walletPubkey,
21134
- bolt11_hash: sha256hex(bolt11).slice(0, 16)
21135
- },
21136
- async () => {
21137
- const now = opts.now ?? (() => Date.now());
21138
- const dedupWindowMs = opts.dedupWindowMs ?? nwcDedupWindowMs();
21139
- return withPool(opts.pool, conn, async (pool) => {
21140
- const method = "pay_invoice";
21141
- const event = buildRequestEvent(conn, method, { invoice: bolt11 });
21142
- const publishedAtMs = now();
21143
- const first = await attemptPayment(conn, method, event, pool, opts.timeoutMs);
21144
- if (first.kind === "settled") {
21145
- return settledOutcome(first, "paid");
21146
- }
21147
- if (first.kind === "rejected") throw first.error;
21148
- if (first.kind === "not_sent") {
21149
- throw first.error;
21150
- }
21151
- const reconciled = await reconcile(conn, bolt11, pool, opts.timeoutMs);
21152
- if (reconciled.kind === "settled") {
21153
- callerLoggers.lightning.info("lightning.pay_invoice.reconciled", { republished: false });
21154
- return settledOutcome(reconciled, "reconciled");
21155
- }
21156
- if (reconciled.kind === "unknown") {
21157
- throw new NwcError(
21158
- "payment_unknown",
21159
- "The wallet never answered and could not confirm whether the payment went through."
21160
- );
21161
- }
21162
- if (now() - publishedAtMs >= dedupWindowMs) {
21163
- throw new NwcError(
21164
- "payment_unknown",
21165
- "The wallet never answered and too much time has passed to safely retry the payment."
21166
- );
21167
- }
21168
- callerLoggers.lightning.info("lightning.pay_invoice.republish", { event_id: event.id });
21169
- const second = await attemptPayment(conn, method, event, pool, opts.timeoutMs);
21170
- if (second.kind === "settled") {
21171
- return settledOutcome(second, "republished");
21172
- }
21173
- if (second.kind === "rejected") throw second.error;
21174
- const final = await reconcile(conn, bolt11, pool, opts.timeoutMs);
21175
- if (final.kind === "settled") {
21176
- return settledOutcome(final, "republished");
21177
- }
21178
- throw new NwcError(
21179
- "payment_unknown",
21180
- "The wallet never answered and could not confirm whether the payment went through."
21181
- );
21182
- });
21183
- }
21184
- );
21185
- }
21186
- async function getBalance(conn, opts = {}) {
21187
- return callerLoggers.lightning.span(
21188
- "lightning.get_balance",
21189
- { relays: conn.relays.join(","), wallet_pubkey: conn.walletPubkey },
21190
- async () => {
21191
- const result = await sendNwcRequest(conn, "get_balance", {}, opts);
21192
- const balance = result.balance ?? 0;
21193
- callerLoggers.lightning.info("lightning.get_balance.result", { balance_msats: balance });
21194
- return balance;
21195
- }
21196
- );
21197
- }
21198
- async function getInfo(conn, opts = {}) {
21199
- return callerLoggers.lightning.span(
21200
- "lightning.get_info",
21201
- { relays: conn.relays.join(","), wallet_pubkey: conn.walletPubkey },
21202
- async () => {
21203
- const result = await sendNwcRequest(conn, "get_info", {}, opts);
21204
- const info = { alias: result.alias, methods: result.methods ?? [] };
21205
- const volunteered = extractWalletReportedBudget(result);
21206
- if (volunteered) info.walletReportedBudget = volunteered;
21207
- callerLoggers.lightning.info("lightning.get_info.result", {
21208
- alias: info.alias,
21209
- methods: info.methods,
21210
- wallet_reported_budget: volunteered !== void 0
21211
- });
21212
- return info;
21213
- }
21214
- );
21215
- }
21216
- async function makeInvoice(conn, params, opts = {}) {
21217
- return callerLoggers.lightning.span(
21218
- "lightning.make_invoice",
21219
- {
21220
- relays: conn.relays.join(","),
21221
- wallet_pubkey: conn.walletPubkey,
21222
- amount_msats: params.amountMsats
21223
- },
21224
- async () => {
21225
- const body = { amount: params.amountMsats };
21226
- if (params.description !== void 0) body.description = params.description;
21227
- if (params.expirySeconds !== void 0) body.expiry = params.expirySeconds;
21228
- const result = await sendNwcRequest(conn, "make_invoice", body, opts);
21229
- const tx = toTransaction(result);
21230
- if (!tx.invoice) {
21231
- throw new NwcError("missing_invoice", "Wallet did not return a BOLT11 invoice");
21232
- }
21233
- if (!tx.paymentHash) {
21234
- throw new NwcError("missing_payment_hash", "Wallet did not return a payment hash");
21235
- }
21236
- callerLoggers.lightning.info("lightning.make_invoice.result", {
21237
- payment_hash_hash: sha256hex(tx.paymentHash).slice(0, 16)
21238
- });
21239
- return tx;
21240
- }
21241
- );
21242
- }
21243
- async function lookupInvoice(conn, params, opts = {}) {
21244
- return callerLoggers.lightning.span(
21245
- "lightning.lookup_invoice",
21246
- {
21247
- relays: conn.relays.join(","),
21248
- wallet_pubkey: conn.walletPubkey,
21249
- payment_hash_hash: params.paymentHash ? sha256hex(params.paymentHash).slice(0, 16) : void 0
21250
- },
21251
- async () => {
21252
- const body = {};
21253
- if (params.paymentHash) body.payment_hash = params.paymentHash;
21254
- if (params.invoice) body.invoice = params.invoice;
21255
- if (!body.payment_hash && !body.invoice) {
21256
- throw new NwcError("bad_request", "lookupInvoice requires payment_hash or invoice");
21257
- }
21258
- const result = await sendNwcRequest(conn, "lookup_invoice", body, opts);
21259
- return toTransaction(result);
21260
- }
21261
- );
21262
- }
21263
- async function listTransactions(conn, options, opts = {}) {
21264
- return callerLoggers.lightning.span(
21265
- "lightning.list_transactions",
21266
- {
21267
- relays: conn.relays.join(","),
21268
- wallet_pubkey: conn.walletPubkey,
21269
- from: options.from,
21270
- limit: options.limit
21271
- },
21272
- async () => {
21273
- const body = {};
21274
- if (options.from !== void 0) body.from = options.from;
21275
- if (options.limit !== void 0) body.limit = options.limit;
21276
- const result = await sendNwcRequest(conn, "list_transactions", body, opts);
21277
- if (!Array.isArray(result.transactions)) {
21278
- throw new NwcError("invalid_response", "Wallet did not return a transaction list.");
21279
- }
21280
- const transactions = result.transactions.map(toTransactionSnapshot);
21281
- callerLoggers.lightning.info("lightning.list_transactions.result", {
21282
- transaction_count: transactions.length
21283
- });
21284
- return transactions;
21285
- }
21286
- );
21287
- }
21288
- var VOLUNTEERED_BUDGET_KEYS = [
21289
- "budget",
21290
- "budget_renewal",
21291
- "budget_msats",
21292
- "budget_amount",
21293
- "max_amount",
21294
- "remaining_budget",
21295
- "budget_remaining",
21296
- "renews_at"
21297
- ];
21298
- function extractWalletReportedBudget(result) {
21299
- const out = {};
21300
- for (const key of VOLUNTEERED_BUDGET_KEYS) {
21301
- const value = result[key];
21302
- if (value !== void 0 && value !== null) out[key] = value;
21303
- }
21304
- return Object.keys(out).length > 0 ? out : void 0;
21305
- }
21306
- async function attemptPayment(conn, method, event, pool, timeoutMs) {
21307
- let result;
21308
- try {
21309
- result = await publishAndAwait(conn, method, event, pool, timeoutMs);
21310
- } catch (err) {
21311
- if (err instanceof NwcError && err.code === "relay_unreachable") {
21312
- return { kind: "not_sent", error: err };
21313
- }
21314
- if (err instanceof NwcError && (err.code === "timeout" || err.code === "publish_rejected")) {
21315
- return { kind: "unknown" };
21316
- }
21317
- if (err instanceof NwcError && err.code === "invalid_response") return { kind: "unknown" };
21318
- if (err instanceof NwcError) return { kind: "rejected", error: err };
21319
- throw err;
21320
- }
21321
- if (!result.preimage) {
21322
- return {
21323
- kind: "rejected",
21324
- error: new NwcError("missing_preimage", "Wallet did not return a payment preimage.")
21325
- };
21326
- }
21327
- const feesPaidMsats = optionalFeeMsats(result.fees_paid);
21328
- return {
21329
- kind: "settled",
21330
- preimage: result.preimage,
21331
- ...feesPaidMsats !== void 0 ? { feesPaidMsats } : {}
21332
- };
21333
- }
21334
- async function reconcile(conn, bolt11, pool, timeoutMs) {
21335
- try {
21336
- const tx = await lookupInvoice(conn, { invoice: bolt11 }, { pool, timeoutMs });
21337
- if (tx.invoice !== bolt11) return { kind: "unknown" };
21338
- if (tx.settledAt != null && tx.preimage) {
21339
- return {
21340
- kind: "settled",
21341
- preimage: tx.preimage,
21342
- ...tx.feesPaidMsats !== void 0 ? { feesPaidMsats: tx.feesPaidMsats } : {}
21343
- };
21344
- }
21345
- if (tx.settledAt != null) {
21346
- return {
21347
- kind: "settled",
21348
- preimage: "",
21349
- ...tx.feesPaidMsats !== void 0 ? { feesPaidMsats: tx.feesPaidMsats } : {}
21350
- };
21351
- }
21352
- return { kind: "unpaid" };
21353
- } catch (err) {
21354
- if (err instanceof NwcError && err.code.toUpperCase() === "NOT_FOUND") {
21355
- return { kind: "unpaid" };
21356
- }
21357
- return { kind: "unknown" };
21358
- }
21359
- }
21360
- async function sendNwcRequest(conn, method, params, opts) {
21361
- return withPool(
21362
- opts.pool,
21363
- conn,
21364
- (pool) => publishAndAwait(conn, method, buildRequestEvent(conn, method, params), pool, opts.timeoutMs)
21365
- );
21366
- }
21367
- function buildRequestEvent(conn, method, params) {
21368
- const content = JSON.stringify({ method, params });
21369
- const encrypted = encrypt(conn.secret, conn.walletPubkey, content);
21370
- return finalizeEvent(
21371
- {
21372
- kind: 23194,
21373
- tags: [["p", conn.walletPubkey]],
21374
- content: encrypted,
21375
- created_at: Math.floor(Date.now() / 1e3)
21376
- },
21377
- conn.secret
21378
- );
21379
- }
21380
- async function publishAndAwait(conn, method, event, pool, timeoutMs) {
21381
- const relays = conn.relays;
21382
- const budgetMs = timeoutMs ?? nwcTimeoutMs();
21383
- const waiter = awaitResponse(pool, relays, conn.walletPubkey, event.id, budgetMs);
21384
- try {
21385
- await publishEvent(pool, relays, event);
21386
- } catch (err) {
21387
- waiter.cancel();
21388
- if (err instanceof NwcError) throw err;
21389
- throw new NwcError(
21390
- "publish_rejected",
21391
- `No relay accepted the request (${relays.join(", ")}): ${err instanceof Error ? err.message : String(err)}`
21392
- );
21393
- }
21394
- const response = await waiter.promise;
21395
- if (response === null) {
21396
- throw new NwcError("timeout", `Wallet did not respond within ${Math.round(budgetMs / 1e3)}s`);
21397
- }
21398
- let decrypted;
21399
- try {
21400
- decrypted = decrypt(conn.secret, conn.walletPubkey, response);
21401
- } catch {
21402
- throw new NwcError("invalid_response", "Wallet response could not be decrypted.");
21403
- }
21404
- let parsed;
21405
- try {
21406
- parsed = JSON.parse(decrypted);
21407
- } catch {
21408
- throw new NwcError("invalid_response", "Wallet returned a malformed response.");
21409
- }
21410
- if (parsed.error) {
21411
- throw new NwcError(
21412
- parsed.error.code ?? "wallet_error",
21413
- parsed.error.message ?? "Wallet error",
21414
- method
21415
- );
21416
- }
21417
- return parsed.result ?? {};
21418
- }
21419
- function awaitResponse(pool, relays, walletPubkey, eventId, timeoutMs) {
21420
- let settle;
21421
- const promise = new Promise((resolve) => {
21422
- const done = (value) => {
21423
- clearTimeout(timer);
21424
- sub.close();
21425
- resolve(value);
21426
- };
21427
- settle = done;
21428
- const timer = setTimeout(() => {
21429
- done(null);
21430
- }, timeoutMs);
21431
- const sub = pool.subscribeMany(
21432
- relays,
21433
- {
21434
- kinds: [23195],
21435
- authors: [walletPubkey],
21436
- "#e": [eventId]
21437
- },
21438
- {
21439
- onevent(ev) {
21440
- done(ev.content);
21441
- },
21442
- // required by nostr-tools
21443
- oneose() {
21444
- }
21445
- // eslint-disable-line @typescript-eslint/no-empty-function
21446
- }
21447
- );
21448
- });
21449
- return {
21450
- promise,
21451
- cancel: () => {
21452
- settle?.(null);
21453
- }
21454
- };
21455
- }
21456
- async function withPool(external, conn, fn) {
21457
- if (external) return fn(external);
21458
- const pool = new SimplePool();
21459
- try {
21460
- return await fn(pool);
21461
- } finally {
21462
- pool.close(conn.relays);
21463
- }
21464
- }
21465
- function toTransaction(raw) {
21466
- const feesPaidMsats = optionalFeeMsats(raw.fees_paid);
21467
- return {
21468
- type: raw.type ?? "incoming",
21469
- invoice: raw.invoice,
21470
- description: raw.description,
21471
- paymentHash: raw.payment_hash ?? "",
21472
- preimage: raw.preimage,
21473
- amountMsats: raw.amount ?? 0,
21474
- ...feesPaidMsats !== void 0 ? { feesPaidMsats } : {},
21475
- createdAt: raw.created_at ?? Math.floor(Date.now() / 1e3),
21476
- expiresAt: raw.expires_at,
21477
- settledAt: raw.settled_at ?? void 0
21478
- };
21479
- }
21480
- function toTransactionSnapshot(value) {
21481
- if (!value || typeof value !== "object" || Array.isArray(value)) {
21482
- throw new NwcError("invalid_response", "Wallet returned a malformed transaction.");
21483
- }
21484
- const raw = value;
21485
- if (raw.type !== "incoming" && raw.type !== "outgoing") {
21486
- throw new NwcError("invalid_response", "Wallet returned an invalid transaction type.");
21487
- }
21488
- const amountMsats = requiredNonNegativeInteger(raw.amount, "amount");
21489
- const createdAt = requiredNonNegativeInteger(raw.created_at, "creation time");
21490
- const feesPaidMsats = optionalFeeMsats(raw.fees_paid);
21491
- if (raw.fees_paid != null && feesPaidMsats === void 0) {
21492
- throw new NwcError("invalid_response", "Wallet returned an invalid transaction fee.");
21493
- }
21494
- const settledAt = optionalNonNegativeInteger(raw.settled_at);
21495
- if (raw.settled_at != null && settledAt === void 0) {
21496
- throw new NwcError("invalid_response", "Wallet returned an invalid settlement time.");
21497
- }
21498
- const invoice = optionalPrivateString(raw.invoice, "invoice");
21499
- const paymentHash = optionalPrivateString(raw.payment_hash, "payment hash");
21500
- const reducedIdentity = {
21501
- type: raw.type,
21502
- invoice: invoice ?? null,
21503
- paymentHash: paymentHash ?? null,
21504
- amountMsats,
21505
- feesPaidMsats: feesPaidMsats ?? null,
21506
- createdAt,
21507
- settledAt: settledAt ?? null
21508
- };
21509
- return {
21510
- fingerprint: sha256hex(JSON.stringify(reducedIdentity)),
21511
- ...invoice ? { invoiceHash: sha256hex(invoice) } : {},
21512
- type: raw.type,
21513
- amountMsats,
21514
- ...feesPaidMsats !== void 0 ? { feesPaidMsats } : {},
21515
- createdAt,
21516
- ...settledAt !== void 0 ? { settledAt } : {}
21517
- };
21518
- }
21519
- function settledOutcome(payment, outcome) {
21520
- return {
21521
- preimage: payment.preimage,
21522
- outcome,
21523
- ...payment.feesPaidMsats !== void 0 ? { feesPaidMsats: payment.feesPaidMsats } : {}
21524
- };
21525
- }
21526
- function optionalFeeMsats(value) {
21527
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
21528
- }
21529
- function optionalNonNegativeInteger(value) {
21530
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
21531
- }
21532
- function requiredNonNegativeInteger(value, field) {
21533
- const parsed = optionalNonNegativeInteger(value);
21534
- if (parsed === void 0) {
21535
- throw new NwcError("invalid_response", `Wallet returned an invalid transaction ${field}.`);
21536
- }
21537
- return parsed;
21538
- }
21539
- function optionalPrivateString(value, field) {
21540
- if (value === void 0 || value === null) return void 0;
21541
- if (typeof value !== "string" || value.length === 0) {
21542
- throw new NwcError("invalid_response", `Wallet returned an invalid transaction ${field}.`);
21543
- }
21544
- return value;
21545
- }
21546
- async function publishEvent(pool, relays, event) {
21547
- const connections = await Promise.allSettled(relays.map((url) => pool.ensureRelay(url)));
21548
- const live = connections.flatMap((c) => c.status === "fulfilled" ? [c.value] : []);
21549
- if (live.length === 0) {
21550
- throw new NwcError(
21551
- "relay_unreachable",
21552
- `Could not connect to any of the wallet's relays (${relays.join(", ")}): ${describeRejections(
21553
- connections
21554
- )}`
21555
- );
21556
- }
21557
- try {
21558
- await Promise.any(live.map((relay) => relay.publish(event)));
21559
- } catch (err) {
21560
- throw new NwcError(
21561
- "publish_rejected",
21562
- `No relay accepted the request (${relays.join(", ")}): ${describeAggregate(err)}`
21563
- );
21564
- }
21565
- }
21566
- function describeRejections(results) {
21567
- const reasons = results.flatMap((r) => r.status === "rejected" ? [errorText(r.reason)] : []);
21568
- return reasons.length > 0 ? reasons.join("; ") : "no relays configured";
21569
- }
21570
- function describeAggregate(err) {
21571
- if (err instanceof AggregateError) {
21572
- return err.errors.map((e) => errorText(e)).join("; ");
21573
- }
21574
- return errorText(err);
21575
- }
21576
- function errorText(err) {
21577
- return err instanceof Error ? err.message : String(err);
21578
- }
21579
- function positiveSecondsEnv(name, fallbackSec) {
21580
- const raw = process.env[name];
21581
- if (!raw) return fallbackSec * 1e3;
21582
- const n = Number(raw);
21583
- if (!Number.isFinite(n) || n <= 0) return fallbackSec * 1e3;
21584
- return Math.floor(n) * 1e3;
21585
- }
21586
- function hexToBytes6(hex) {
21587
- const bytes = new Uint8Array(hex.length / 2);
21588
- for (let i = 0; i < bytes.length; i++) {
21589
- bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
21590
- }
21591
- return bytes;
21592
- }
21593
-
21594
- // src/lib/nwc-backend.ts
21595
- var NwcBackend = class {
21596
- constructor(conn, opts = {}) {
21597
- this.conn = conn;
21598
- this.opts = opts;
21599
- }
21600
- conn;
21601
- opts;
21602
- async payInvoice(bolt11, _amountMsats) {
21603
- return payInvoiceReconciling(this.conn, bolt11, this.opts);
21604
- }
21605
- async getBalance() {
21606
- return getBalance(this.conn, this.opts);
21607
- }
21608
- async getInfo() {
21609
- return getInfo(this.conn, this.opts);
21610
- }
21611
- async createInvoice(params) {
21612
- const tx = await makeInvoice(this.conn, params, this.opts);
21613
- const expiresAtMs = tx.expiresAt ? tx.expiresAt * 1e3 : Date.now() + (params.expirySeconds ?? 3600) * 1e3;
21614
- return {
21615
- bolt11: tx.invoice ?? "",
21616
- paymentHash: tx.paymentHash,
21617
- amountMsats: tx.amountMsats || params.amountMsats,
21618
- expiresAt: expiresAtMs
21619
- };
21620
- }
21621
- async lookupInvoice(paymentHash) {
21622
- const tx = await lookupInvoice(this.conn, { paymentHash }, this.opts);
21623
- return {
21624
- settled: tx.settledAt != null,
21625
- preimage: tx.preimage,
21626
- settledAt: tx.settledAt != null ? tx.settledAt * 1e3 : void 0,
21627
- expiresAt: tx.expiresAt != null ? tx.expiresAt * 1e3 : void 0
21628
- };
21629
- }
21630
- async listTransactions(options) {
21631
- return listTransactions(this.conn, options, this.opts);
21632
- }
21633
- };
21634
-
21635
19370
  // src/sdk/server/tempo-observer-health.ts
21636
19371
  var TEMPO_OBSERVER_HEALTH_REFRESH_TTL_MS = 60 * 1e3;
21637
19372
  var TEMPO_OBSERVER_HEALTH_MAX_CURSOR_AGE_MS = 2 * 60 * 1e3;
@@ -21803,7 +19538,6 @@ function createPlatformTempoObserverHealth(env) {
21803
19538
  }
21804
19539
 
21805
19540
  export {
21806
- toLockPubkey,
21807
19541
  initLockPubkeyTable,
21808
19542
  seedLockPubkeyState,
21809
19543
  loadLockPubkeyState,
@@ -21836,7 +19570,6 @@ export {
21836
19570
  toCreditView,
21837
19571
  attachCreditMenu,
21838
19572
  toCreditTerms,
21839
- isX402SettlementReconciliationReason,
21840
19573
  resolvePriceFiat,
21841
19574
  priceFiatMicro,
21842
19575
  resolveFxSnapshot,
@@ -21850,35 +19583,7 @@ export {
21850
19583
  isDerivedCreditId,
21851
19584
  derivedFundCreditId,
21852
19585
  drawSettlementRef,
21853
- TEMPO_SESSION_UPDATED_REASON,
21854
19586
  rateUnavailablePaymentError,
21855
- backupSecretFile,
21856
- readAgentMnemonic,
21857
- assertAgentMnemonicReplaceable,
21858
- writeAgentMnemonic,
21859
- deleteAgentMnemonic,
21860
- generateAgentMnemonic,
21861
- deriveAgentMasterKeypair,
21862
- validateAgentMnemonic,
21863
- looksLikeMnemonic,
21864
- mnemonicFingerprint,
21865
- WALLET_VERSION,
21866
- RECENT_SPENDS_CAP,
21867
- DEFAULT_AGENT_MINT_URL,
21868
- loadAgentWallet,
21869
- saveAgentWallet,
21870
- walletExists,
21871
- withWalletLock,
21872
- effectiveMints,
21873
- effectiveFundingMint,
21874
- PersistentCounterSource,
21875
- checkMintHealth,
21876
- canSwap,
21877
- meltAmountBounds,
21878
- amountBoundsVerdict,
21879
- assertNutSupport,
21880
- getWallet,
21881
- X402_BATCH_CHANNEL_ABI,
21882
19587
  X402_BATCH_SETTLEMENT_NETWORK,
21883
19588
  X402_BATCH_SETTLEMENT_MAINNET_NETWORK,
21884
19589
  X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS,
@@ -21901,68 +19606,11 @@ export {
21901
19606
  repairX402ExactSettlementEffect,
21902
19607
  CREDIT_REQUEST_SCHEMA,
21903
19608
  handleCreditRequest,
21904
- RECOMMENDED_MINTS,
21905
- isTestMintUrl,
21906
- deriveReceiptSecret,
21907
- receiptPubkeyFromSecret,
21908
- deriveReceiptKeypair,
21909
- generateEphemeralReceiptKey,
21910
- X402_SELF_RELAY_KEY,
21911
- X402_RECEIVER_AUTHORIZER_KEY,
21912
- X402_BATCH_SETTLEMENT_KEY,
21913
- X402_RPC_URL_KEY,
21914
- X402EnvValidationError,
21915
- validateX402Env,
21916
- SPANS_INSERTED_CHANNEL,
21917
- SpanWriter,
21918
- insertSpanBatch,
21919
- DbTransport,
21920
- HttpTransport,
21921
- generateIdentity,
21922
- loadIdentitySecret,
21923
- writeIdentity,
21924
- hashCapabilities,
21925
- buildAttestation,
21926
- signAttestation,
21927
- verifyAttestation,
21928
- computeResultHash,
21929
- signReceipt,
21930
- verifyReceipt,
21931
- isSignedJobReceipt,
21932
- signDrainReceipt,
21933
- signFundingReceipt,
21934
- isFundingReceipt,
21935
- verifyFundingReceipt,
21936
- isDrainReceipt,
21937
- verifyDrainReceipt,
21938
- attestedDvmIdentity,
21939
- authenticatedBuilderPubkey,
21940
- verifyJobReceipt,
21941
- checkReceiptAttestation,
21942
- verifyDrainReceiptChain,
21943
- verifyFundingReceiptChain,
21944
- weakestReceiptVerdict,
21945
- drainReceiptsDisplay,
21946
- drainReceiptsHint,
21947
- receiptDisplay,
21948
- receiptIssuedAtIso,
21949
- epochMsToIso,
21950
- receiptHint,
21951
- receiptFailureDetail,
21952
- isPublicRequestId,
21953
- parseTraceparent,
21954
- PRIVKEY_HEX_RE,
21955
- derivePubkeyFromPrivkey,
21956
- verifyChallenge,
21957
- ADMIN_STATE_UNAVAILABLE,
21958
- buildAdminChallenge,
21959
- buildAdminAuthHeader,
21960
19609
  installAdminCashuRoutes,
21961
19610
  createAdminCashuNonceStore,
21962
19611
  X402ExactSettlementServer,
21963
19612
  X402ExactSettlementEvidenceMissingError,
21964
19613
  jobCredentialGate,
21965
- createMonotonicClock,
21966
19614
  REQUEST_ID_FIELD,
21967
19615
  protocolEnvelopeIgnoreFields,
21968
19616
  stripProtocolEnvelope,
@@ -21981,18 +19629,5 @@ export {
21981
19629
  revenueReporterBannerState,
21982
19630
  createDVMServer,
21983
19631
  unknownRouteNotFound,
21984
- sha256hex,
21985
- NwcError,
21986
- nwcTimeoutMs,
21987
- nwcDedupWindowMs,
21988
- withNwcPool,
21989
- parseNwcUri,
21990
- payInvoiceReconciling,
21991
- getBalance,
21992
- getInfo,
21993
- makeInvoice,
21994
- lookupInvoice,
21995
- listTransactions,
21996
- NwcBackend,
21997
19632
  createPlatformTempoObserverHealth
21998
19633
  };