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

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 (69) hide show
  1. package/README.md +39 -2
  2. package/dist/{chunk-S3OY533W.js → chunk-2NUFE2PG.js} +47 -5
  3. package/dist/{chunk-RA2WIX7G.js → chunk-3E4C6W7W.js} +17 -6
  4. package/dist/{chunk-GY66THYI.js → chunk-53SNRGQQ.js} +372 -153
  5. package/dist/{chunk-CXCQLOOK.js → chunk-62FI3NJF.js} +42 -107
  6. package/dist/chunk-6HJEVCVI.js +17 -0
  7. package/dist/chunk-A262WVYX.js +6 -0
  8. package/dist/{chunk-IV6Q7NRW.js → chunk-C5RIQWG4.js} +1 -1
  9. package/dist/{chunk-GAOAYHZA.js → chunk-FBQVOOYF.js} +37 -6
  10. package/dist/{chunk-Z4ZT5LNT.js → chunk-GEI4MAXE.js} +3 -3
  11. package/dist/{chunk-OML32F6I.js → chunk-GLIAR7RN.js} +1 -1
  12. package/dist/{chunk-X7EZ5CLI.js → chunk-HCATFJDD.js} +3 -3
  13. package/dist/{chunk-BYCEKO65.js → chunk-HPCXF3DV.js} +220 -32
  14. package/dist/{chunk-6XF3RVVX.js → chunk-IRXIH775.js} +5 -5
  15. package/dist/{chunk-QR2EF2NI.js → chunk-KCQDXIJU.js} +1 -42
  16. package/dist/chunk-MDW7ZR7V.js +47 -0
  17. package/dist/{chunk-KA6VN5FK.js → chunk-MQYM4T66.js} +3 -3
  18. package/dist/{chunk-P6QON7ER.js → chunk-NV7Z7377.js} +1 -1
  19. package/dist/{chunk-KMED3H3A.js → chunk-OBBVDKLE.js} +476 -230
  20. package/dist/{chunk-EQLA4FFY.js → chunk-OHUXCAOM.js} +17 -6
  21. package/dist/{chunk-FWGWLPXF.js → chunk-PZ5IAWFD.js} +1 -1
  22. package/dist/{chunk-IXR5RFK7.js → chunk-R3TQSXWT.js} +191 -5
  23. package/dist/{chunk-VHNPVGSA.js → chunk-T5DIXOLR.js} +3 -3
  24. package/dist/{chunk-RCM3P2HE.js → chunk-UKD3R2KU.js} +55 -4
  25. package/dist/chunk-WMGXJQI3.js +120 -0
  26. package/dist/{chunk-N5RZ5HVB.js → chunk-X6S2CHFM.js} +2 -2
  27. package/dist/{chunk-ETXDTUJO.js → chunk-ZCNPIILZ.js} +112 -1
  28. package/dist/{content-retention-DHyLdA6b.d.ts → content-retention-D2K4MKNO.d.ts} +1 -1
  29. package/dist/{credit-ledger-MCUXYKSJ.js → credit-ledger-MFIIOSZH.js} +4 -4
  30. package/dist/{credit-menu-DjnkeEUU.d.ts → credit-menu-D2t1-Tf6.d.ts} +13 -6
  31. package/dist/{fx-zMWFzfrU.d.ts → fx-Ql4nvdbh.d.ts} +10 -3
  32. package/dist/index.d.ts +5 -5
  33. package/dist/index.js +248 -16
  34. package/dist/internal/caller.d.ts +5032 -8272
  35. package/dist/internal/caller.js +4302 -8084
  36. package/dist/internal/index.js +1 -1
  37. package/dist/internal/server.d.ts +7 -7
  38. package/dist/internal/server.js +30 -31
  39. package/dist/{job-store-DdLXBpol.d.ts → job-store-DCvJQUX0.d.ts} +1 -1
  40. package/dist/{lightning-backend-Bw-qtAbt.d.ts → lightning-backend-CY5ML3km.d.ts} +2 -2
  41. package/dist/{memory-credit-ledger-SBSMGMDX.js → memory-credit-ledger-44NE4PZC.js} +5 -5
  42. package/dist/{mpp-setup-RF5FXGGV.js → mpp-setup-HRWUBULX.js} +4 -2
  43. package/dist/{payout-reporter-3Y2U55ZX.js → payout-reporter-7P66T77I.js} +6 -4
  44. package/dist/{postgres-consumed-credential-store-BUVCAPLS.js → postgres-consumed-credential-store-IDGPFKIL.js} +3 -3
  45. package/dist/{postgres-job-store-VHLQBJZC.js → postgres-job-store-UKNJ2F6Z.js} +2 -2
  46. package/dist/{postgres-replay-store-ARZSQ45X.js → postgres-replay-store-PMA4ADXN.js} +2 -2
  47. package/dist/{pricing-XT2IAPNE.js → pricing-52XJHJ6K.js} +12 -12
  48. package/dist/{revenue-reporter-MAERPYGK.js → revenue-reporter-HV7RMS54.js} +2 -2
  49. package/dist/server/index.d.ts +12 -11
  50. package/dist/server/index.js +47 -48
  51. package/dist/{step-cache-2MkvenJn.d.ts → step-cache-BOWAn_BH.d.ts} +84 -7
  52. package/dist/{tempo-charge-store-Q3D7KYMH.js → tempo-charge-store-P2Z4RMU7.js} +2 -2
  53. package/dist/{tempo-session-store-ZRNCOX7P.js → tempo-session-store-VSP7KLBS.js} +3 -3
  54. package/dist/testing/index.d.ts +3 -3
  55. package/dist/testing/index.js +7 -6
  56. package/dist/{usd-DI3d3gTK.d.ts → usd-MAtMp3DB.d.ts} +1 -1
  57. package/dist/{x402-FT4S4JS4.js → x402-3IQ3HXSG.js} +8 -26
  58. package/package.json +1 -1
  59. package/dist/chunk-AT6V3SY7.js +0 -102
  60. package/dist/chunk-EZXAJ6EI.js +0 -1082
  61. package/dist/chunk-HCLVYZUZ.js +0 -244
  62. package/dist/chunk-JEVLNAO3.js +0 -1668
  63. package/dist/chunk-NFMOPYBA.js +0 -357
  64. package/dist/chunk-O2OTZ5GP.js +0 -1869
  65. package/dist/chunk-OFVV37XC.js +0 -57
  66. package/dist/chunk-SZVXOQ75.js +0 -211
  67. package/dist/chunk-WBOKPC37.js +0 -795
  68. package/dist/tempo-lifecycle-GJ77YKVY.js +0 -15
  69. package/dist/tempo-wallet-X2GGAZ6X.js +0 -40
@@ -1,25 +1,6 @@
1
- import {
2
- computeResultHash,
3
- costAnchor,
4
- formatUsd,
5
- isDrainReceipt,
6
- isFundingReceipt,
7
- msatsToUsd,
8
- verifyDrainReceipt,
9
- verifyFundingReceipt,
10
- verifyReceipt
11
- } from "./chunk-NFMOPYBA.js";
12
- import {
13
- AGENT_MNEMONIC_FILE,
14
- BUILDER_CONFIG_DIR,
15
- BUILDER_FILE,
16
- BUILDER_KEY_FILE,
17
- WALLET_FILE,
18
- ensureConfigDir
19
- } from "./chunk-CXCQLOOK.js";
20
1
  import {
21
2
  canonicaliseForSigning
22
- } from "./chunk-OFVV37XC.js";
3
+ } from "./chunk-UKD3R2KU.js";
23
4
  import {
24
5
  callerLoggers
25
6
  } from "./chunk-66HGCPBU.js";
@@ -28,7 +9,7 @@ import {
28
9
  } from "./chunk-BPYMEMQX.js";
29
10
  import {
30
11
  diagnosticConsole
31
- } from "./chunk-ETXDTUJO.js";
12
+ } from "./chunk-ZCNPIILZ.js";
32
13
 
33
14
  // src/lib/cashu/lock-pubkey.ts
34
15
  function toLockPubkey(raw) {
@@ -40,48 +21,257 @@ function isX402SettlementReconciliationReason(value) {
40
21
  return value === "settlement_not_on_chain" || value === "chain_unreachable" || value === "settlement_unbookmarked" || value === "facilitator_channel_state_unavailable";
41
22
  }
42
23
 
24
+ // src/lib/credit-implicit.ts
25
+ var IMPLICIT_CREDIT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
26
+
43
27
  // src/lib/payment-rails/tempo-wire.ts
44
28
  var TEMPO_SESSION_UPDATED_REASON = "Tempo session updated; attach the next voucher to fund this credit.";
45
29
 
46
- // src/lib/mints.ts
47
- var RECOMMENDED_MINTS = [
48
- {
49
- name: "Coinos",
50
- description: "Primary Cashu mint accepted by dvmkit DVMs",
51
- url: "https://mint.coinos.io"
52
- },
53
- {
54
- name: "Voltz Mint",
55
- description: "Fallback Cashu mint accepted by dvmkit DVMs",
56
- url: "https://mint.lnvoltz.com"
30
+ // src/defaults.ts
31
+ import { homedir } from "os";
32
+ import { join } from "path";
33
+ var CONFIG_DIR = process.env.DVM_CONFIG_DIR ?? join(homedir(), ".dvm");
34
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
35
+ var CONFIG_BACKUP_FILE = join(CONFIG_DIR, "config.json.bak");
36
+ var CONFIG_AUDIT_FILE = join(CONFIG_DIR, "config-audit.jsonl");
37
+ var CONFIG_PINNED_FILE = join(CONFIG_DIR, "config.json.pinned");
38
+ var JOBS_FILE = join(CONFIG_DIR, "jobs.json");
39
+ var REQUEST_INTENTS_DIR = join(CONFIG_DIR, "request-intents");
40
+ var FAVORITES_FILE = join(CONFIG_DIR, "favorites.json");
41
+ var MESSAGES_DIR = join(CONFIG_DIR, "messages");
42
+ var RECEIPTS_FILE = join(CONFIG_DIR, "receipts.jsonl");
43
+ var CREDITS_FILE = join(CONFIG_DIR, "credits.json");
44
+ var X402_CHANNELS_FILE = join(CONFIG_DIR, "x402-channels.json");
45
+ var TEMPO_CHANNELS_FILE = join(CONFIG_DIR, "tempo-channels.json");
46
+ var FEEDBACK_NUDGES_FILE = join(CONFIG_DIR, "feedback-nudges.json");
47
+ var RECOVERY_MNEMONIC_FILE = join(CONFIG_DIR, "recovery.mnemonic");
48
+ var WALLET_FILE = join(CONFIG_DIR, "wallet.json");
49
+ var AGENT_MNEMONIC_FILE = join(CONFIG_DIR, "wallet.mnemonic");
50
+ var IDENTITIES_FILE = join(CONFIG_DIR, "identities.json");
51
+ var PAIRINGS_DIR = join(CONFIG_DIR, "pairings");
52
+ var BUDGET_FILE = join(CONFIG_DIR, "budget.json");
53
+ var BTC_RATE_FILE = join(CONFIG_DIR, "btc-rate.json");
54
+ var ADMIN_DIR = join(CONFIG_DIR, "admin");
55
+ var PROXY_STATE_FILE = join(ADMIN_DIR, "proxy.json");
56
+ var BUILDER_CONFIG_DIR = process.env.DVMKIT_CONFIG_DIR ?? join(homedir(), ".dvmkit");
57
+ var BUILDER_FILE = join(BUILDER_CONFIG_DIR, "builder.json");
58
+ var BUILDER_MNEMONIC_FILE = join(BUILDER_CONFIG_DIR, "recovery.mnemonic");
59
+ var BUILDER_KEY_FILE = join(BUILDER_CONFIG_DIR, "builder.key");
60
+ var MELT_PENDING_LOG_FILE = join(BUILDER_CONFIG_DIR, "melt-pending.log");
61
+ var DRAIN_PARK_LOG_FILE = join(BUILDER_CONFIG_DIR, "drain-park.log");
62
+ var DRAIN_HOLD_STATE_FILE = join(BUILDER_CONFIG_DIR, "drain-hold.json");
63
+ var MELT_SUCCESS_FILE = join(BUILDER_CONFIG_DIR, "melt-success.json");
64
+
65
+ // src/lib/config-dir.ts
66
+ import { chmodSync, mkdirSync, statSync } from "fs";
67
+ var CONFIG_DIR_MODE = 448;
68
+ function ensureConfigDir() {
69
+ ensureConfigSubdir(CONFIG_DIR);
70
+ }
71
+ function ensureConfigSubdir(path) {
72
+ mkdirSync(path, { recursive: true, mode: CONFIG_DIR_MODE });
73
+ }
74
+ function repairConfigDirMode() {
75
+ const base = {
76
+ path: CONFIG_DIR,
77
+ exists: false,
78
+ modeOctal: null,
79
+ repaired: false,
80
+ skippedNonPosix: process.platform === "win32"
81
+ };
82
+ if (base.skippedNonPosix) return base;
83
+ let mode;
84
+ try {
85
+ mode = statSync(CONFIG_DIR).mode & 511;
86
+ } catch (err) {
87
+ const message = err instanceof Error ? err.message : String(err);
88
+ const missing = err.code === "ENOENT";
89
+ return missing ? base : { ...base, error: message };
57
90
  }
58
- ];
59
- function isTestMintUrl(url) {
60
- let host;
91
+ if ((mode & 63) === 0) {
92
+ return { ...base, exists: true, modeOctal: toOctal(mode) };
93
+ }
94
+ try {
95
+ chmodSync(CONFIG_DIR, CONFIG_DIR_MODE);
96
+ } catch (err) {
97
+ return {
98
+ ...base,
99
+ exists: true,
100
+ modeOctal: toOctal(mode),
101
+ error: err instanceof Error ? err.message : String(err)
102
+ };
103
+ }
104
+ return {
105
+ ...base,
106
+ exists: true,
107
+ modeOctal: toOctal(CONFIG_DIR_MODE),
108
+ previousModeOctal: toOctal(mode),
109
+ repaired: true
110
+ };
111
+ }
112
+ function toOctal(mode) {
113
+ return "0" + mode.toString(8).padStart(3, "0");
114
+ }
115
+
116
+ // src/lib/fx-coingecko.ts
117
+ var COINGECKO_SIMPLE_PRICE_URL = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,usd-coin&vs_currencies=usd";
118
+ var COINGECKO_BTC_USD_URL = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd";
119
+ var DEFAULT_TIMEOUT_MS = 5e3;
120
+ var DEFAULT_USER_AGENT = "dvmkit-platform/1.0 (+https://dvmkit.com)";
121
+ async function fetchCoinGeckoSpot(opts) {
122
+ const body = await fetchCoinGeckoBody(opts, COINGECKO_SIMPLE_PRICE_URL);
123
+ const btc = body.bitcoin?.usd;
124
+ const usdc = body["usd-coin"]?.usd;
125
+ if (!isPositiveNumber(btc)) {
126
+ throw new Error("coingecko response missing bitcoin.usd");
127
+ }
128
+ if (!isPositiveNumber(usdc)) {
129
+ throw new Error("coingecko response missing usd-coin.usd");
130
+ }
131
+ return { btc_usd: btc, usdc_usd: usdc, as_of: (/* @__PURE__ */ new Date()).toISOString() };
132
+ }
133
+ async function fetchCoinGeckoBtcUsd(opts) {
134
+ const body = await fetchCoinGeckoBody(opts, COINGECKO_BTC_USD_URL);
135
+ const btc = body.bitcoin?.usd;
136
+ if (!isPositiveNumber(btc)) {
137
+ throw new Error("coingecko response missing bitcoin.usd");
138
+ }
139
+ return { btc_usd: btc, as_of: (/* @__PURE__ */ new Date()).toISOString() };
140
+ }
141
+ async function fetchCoinGeckoBody(opts, defaultUrl) {
142
+ const url = opts?.url ?? defaultUrl;
143
+ const fetchImpl = opts?.fetchImpl ?? fetch;
144
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
145
+ const userAgent = opts?.userAgent ?? DEFAULT_USER_AGENT;
146
+ const res = await fetchImpl(url, {
147
+ headers: { "User-Agent": userAgent, Accept: "application/json" },
148
+ signal: AbortSignal.timeout(timeoutMs)
149
+ });
150
+ if (!res.ok) {
151
+ throw new Error(`coingecko returned HTTP ${res.status.toString()}`);
152
+ }
153
+ return await res.json();
154
+ }
155
+ function isPositiveNumber(v) {
156
+ return typeof v === "number" && Number.isFinite(v) && v > 0;
157
+ }
158
+
159
+ // src/lib/currency.ts
160
+ import { existsSync, readFileSync, writeFileSync } from "fs";
161
+ import { dirname } from "path";
162
+ var COST_ANCHOR_BANDS = {
163
+ usd: [0.01, 0.1, 1, 10],
164
+ eur: [0.01, 0.1, 1, 10],
165
+ gbp: [0.01, 0.1, 1, 10],
166
+ jpy: [1, 10, 100, 1500]
167
+ };
168
+ function costAnchor(amount, currency = "usd") {
169
+ const bands = COST_ANCHOR_BANDS[currency] ?? COST_ANCHOR_BANDS.usd;
170
+ if (amount < bands[0]) return "fraction of a cent";
171
+ if (amount <= bands[1]) return "comparable to an API call";
172
+ if (amount <= bands[2]) return "comparable to a ChatGPT query";
173
+ if (amount <= bands[3]) return "comparable to a coffee";
174
+ return "significant purchase \u2014 review carefully";
175
+ }
176
+ function formatUsd(usd) {
177
+ if (usd === 0) return "0.00";
178
+ if (usd >= 0.01) return usd.toFixed(2);
179
+ const digits = Math.max(2, -Math.floor(Math.log10(usd)) + 1);
180
+ return usd.toFixed(digits);
181
+ }
182
+ function formatUsdcMicrounits(microunits) {
183
+ const value = typeof microunits === "bigint" ? microunits : BigInt(microunits);
184
+ const whole = value / 1000000n;
185
+ const fraction = (value % 1000000n).toString().padStart(6, "0").replace(/0+$/, "");
186
+ return `${whole}.${fraction.padEnd(2, "0") || "00"}`;
187
+ }
188
+ async function fetchBtcUsdRate() {
189
+ if (memRate && Date.now() - memTime < CACHE_TTL) {
190
+ return memRate;
191
+ }
192
+ const disk = readDiskCache();
193
+ if (disk) {
194
+ memRate = disk;
195
+ memTime = Date.now();
196
+ return disk;
197
+ }
198
+ const rate = await fetchFromApi();
199
+ memRate = rate;
200
+ memTime = Date.now();
201
+ writeDiskCache(rate);
202
+ return rate;
203
+ }
204
+ function getLastKnownBtcRate() {
205
+ if (memRate && memTime > 0) return { rate: memRate, cachedAt: memTime };
206
+ return readDiskCacheStale();
207
+ }
208
+ function formatStaleness(cachedAt, now = Date.now()) {
209
+ const ageMs = Math.max(0, now - cachedAt);
210
+ const seconds = Math.floor(ageMs / 1e3);
211
+ if (seconds < 60) return `${seconds}s ago`;
212
+ const minutes = Math.floor(seconds / 60);
213
+ if (minutes < 60) return `${minutes} min ago`;
214
+ const hours = Math.floor(minutes / 60);
215
+ if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
216
+ const days = Math.floor(hours / 24);
217
+ return `${days} day${days === 1 ? "" : "s"} ago`;
218
+ }
219
+ var CACHE_TTL = 5 * 60 * 1e3;
220
+ var CACHE_FILE = BTC_RATE_FILE;
221
+ var memRate = null;
222
+ var memTime = 0;
223
+ function readDiskCache() {
61
224
  try {
62
- host = new URL(url).hostname.toLowerCase();
225
+ if (!existsSync(CACHE_FILE)) return null;
226
+ const data = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
227
+ if (Date.now() - data.timestamp >= CACHE_TTL) return null;
228
+ if (!isPlausibleBtcUsdRate(data.rate)) return null;
229
+ return data.rate;
63
230
  } catch {
64
- return false;
231
+ return null;
232
+ }
233
+ }
234
+ function readDiskCacheStale() {
235
+ try {
236
+ if (!existsSync(CACHE_FILE)) return null;
237
+ const data = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
238
+ if (!isPlausibleBtcUsdRate(data.rate)) return null;
239
+ return { rate: data.rate, cachedAt: data.timestamp };
240
+ } catch {
241
+ return null;
242
+ }
243
+ }
244
+ function writeDiskCache(rate) {
245
+ try {
246
+ ensureConfigSubdir(dirname(CACHE_FILE));
247
+ writeFileSync(CACHE_FILE, JSON.stringify({ rate, timestamp: Date.now() }));
248
+ } catch {
249
+ }
250
+ }
251
+ var MAX_PLAUSIBLE_BTC_USD = 1e7;
252
+ function isPlausibleBtcUsdRate(rate) {
253
+ return typeof rate === "number" && Number.isFinite(rate) && rate > 0 && rate < MAX_PLAUSIBLE_BTC_USD;
254
+ }
255
+ async function fetchFromApi() {
256
+ const spot = await fetchCoinGeckoBtcUsd();
257
+ const rate = spot.btc_usd;
258
+ if (!isPlausibleBtcUsdRate(rate)) {
259
+ throw new Error(`implausible BTC/USD rate from upstream: ${String(rate)}`);
65
260
  }
66
- if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1);
67
- if (LOOPBACK_HOSTS.has(host)) return true;
68
- if (host.endsWith(".localhost")) return true;
69
- return TEST_MINT_HOSTS.has(host);
261
+ return rate;
70
262
  }
71
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1"]);
72
- var TEST_MINT_HOSTS = /* @__PURE__ */ new Set(["testnut.cashu.space", "dvmkit-testmint.fly.dev"]);
73
263
 
74
264
  // src/lib/secret-backup.ts
75
- import { existsSync, readFileSync, writeFileSync } from "fs";
265
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
76
266
  function backupSecretFile(file, nextContent) {
77
- const content = existsSync(file) ? readFileSync(file) : null;
267
+ const content = existsSync2(file) ? readFileSync2(file) : null;
78
268
  if (content === null) return null;
79
269
  if (content.toString("utf-8").trim() === nextContent?.trim()) return null;
80
270
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
81
271
  for (let n = 0; n < 100; n++) {
82
272
  const backup = n === 0 ? `${file}.${stamp}.bak` : `${file}.${stamp}-${String(n)}.bak`;
83
273
  try {
84
- writeFileSync(backup, content, { mode: 384, flag: "wx" });
274
+ writeFileSync2(backup, content, { mode: 384, flag: "wx" });
85
275
  return backup;
86
276
  } catch (err) {
87
277
  if (err.code !== "EEXIST") throw err;
@@ -92,20 +282,20 @@ function backupSecretFile(file, nextContent) {
92
282
 
93
283
  // src/lib/wallet-agent/agent-mnemonic-file.ts
94
284
  import {
95
- chmodSync,
96
- existsSync as existsSync2,
97
- readFileSync as readFileSync2,
285
+ chmodSync as chmodSync2,
286
+ existsSync as existsSync3,
287
+ readFileSync as readFileSync3,
98
288
  renameSync,
99
289
  unlinkSync,
100
- writeFileSync as writeFileSync2
290
+ writeFileSync as writeFileSync3
101
291
  } from "fs";
102
292
  function readAgentMnemonic() {
103
- if (!existsSync2(AGENT_MNEMONIC_FILE)) return null;
104
- const raw = readFileSync2(AGENT_MNEMONIC_FILE, "utf-8");
293
+ if (!existsSync3(AGENT_MNEMONIC_FILE)) return null;
294
+ const raw = readFileSync3(AGENT_MNEMONIC_FILE, "utf-8");
105
295
  return raw.trim().toLowerCase().split(/\s+/).join(" ");
106
296
  }
107
297
  function assertAgentMnemonicReplaceable(opts = {}) {
108
- if (existsSync2(AGENT_MNEMONIC_FILE) && !opts.force) {
298
+ if (existsSync3(AGENT_MNEMONIC_FILE) && !opts.force) {
109
299
  throw new DvmError(
110
300
  "agent_mnemonic_exists",
111
301
  `An agent wallet recovery mnemonic already exists at ${AGENT_MNEMONIC_FILE}.`,
@@ -119,7 +309,7 @@ function writeAgentMnemonic(mnemonic, opts = {}) {
119
309
  ensureConfigDir();
120
310
  const backupFile = backupSecretFile(AGENT_MNEMONIC_FILE, normalised);
121
311
  const tmp = `${AGENT_MNEMONIC_FILE}.tmp`;
122
- writeFileSync2(tmp, normalised + "\n", { mode: 384 });
312
+ writeFileSync3(tmp, normalised + "\n", { mode: 384 });
123
313
  try {
124
314
  renameSync(tmp, AGENT_MNEMONIC_FILE);
125
315
  } catch (err) {
@@ -130,13 +320,13 @@ function writeAgentMnemonic(mnemonic, opts = {}) {
130
320
  throw err;
131
321
  }
132
322
  try {
133
- chmodSync(AGENT_MNEMONIC_FILE, 384);
323
+ chmodSync2(AGENT_MNEMONIC_FILE, 384);
134
324
  } catch {
135
325
  }
136
326
  return { backupFile };
137
327
  }
138
328
  function deleteAgentMnemonic() {
139
- if (!existsSync2(AGENT_MNEMONIC_FILE)) return { backupFile: null };
329
+ if (!existsSync3(AGENT_MNEMONIC_FILE)) return { backupFile: null };
140
330
  const backupFile = backupSecretFile(AGENT_MNEMONIC_FILE);
141
331
  unlinkSync(AGENT_MNEMONIC_FILE);
142
332
  return { backupFile };
@@ -198,7 +388,7 @@ function mnemonicFingerprint(mnemonic) {
198
388
  }
199
389
 
200
390
  // src/lib/wallet-agent/store.ts
201
- import { existsSync as existsSync3, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
391
+ import { existsSync as existsSync4, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
202
392
  import lockfile from "proper-lockfile";
203
393
  var LOCK_FILE = `${WALLET_FILE}.lock`;
204
394
  var DEFAULT_LOCK_STALE_MS = 3e4;
@@ -207,12 +397,22 @@ var DEFAULT_LOCK_RETRY_INTERVAL_MS = 1e3;
207
397
  var WALLET_VERSION = 9;
208
398
  var RECENT_SPENDS_CAP = 10;
209
399
  var DEFAULT_AGENT_MINT_URL = "https://mint.coinos.io";
400
+ var MAX_STORABLE_JOB_ID_LENGTH = 512;
401
+ function isStorableJobId(value) {
402
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_STORABLE_JOB_ID_LENGTH && // eslint-disable-next-line no-control-regex -- control characters are what this rejects
403
+ !/[\u0000-\u001f\u007f-\u009f]/u.test(value);
404
+ }
210
405
  function loadWalletConfiguredMints() {
211
406
  return loadAgentWallet()?.mints ?? [];
212
407
  }
213
408
  function loadAgentWallet() {
214
- if (!existsSync3(WALLET_FILE)) return null;
215
- const raw = readFileSync3(WALLET_FILE, "utf-8");
409
+ const wallet = parseAgentWallet();
410
+ if (wallet) normalizeAcceptedUnresolved(wallet);
411
+ return wallet;
412
+ }
413
+ function parseAgentWallet() {
414
+ if (!existsSync4(WALLET_FILE)) return null;
415
+ const raw = readFileSync4(WALLET_FILE, "utf-8");
216
416
  let parsed;
217
417
  try {
218
418
  parsed = JSON.parse(raw);
@@ -291,10 +491,11 @@ function loadAgentWallet() {
291
491
  return parsed;
292
492
  }
293
493
  function saveAgentWallet(wallet) {
494
+ assertAcceptedUnresolvedStorable(wallet);
294
495
  ensureConfigDir();
295
496
  const tmp = `${WALLET_FILE}.tmp`;
296
497
  const data = JSON.stringify(wallet, null, 2) + "\n";
297
- writeFileSync3(tmp, data, { mode: 384 });
498
+ writeFileSync4(tmp, data, { mode: 384 });
298
499
  try {
299
500
  renameSync2(tmp, WALLET_FILE);
300
501
  } catch (err) {
@@ -306,7 +507,7 @@ function saveAgentWallet(wallet) {
306
507
  }
307
508
  }
308
509
  function walletExists() {
309
- return existsSync3(WALLET_FILE);
510
+ return existsSync4(WALLET_FILE);
310
511
  }
311
512
  async function withWalletLock(fn, opts = {}) {
312
513
  ensureConfigDir();
@@ -459,6 +660,62 @@ function migrateV8ToV9(parsed) {
459
660
  created_at: typeof parsed.created_at === "string" ? parsed.created_at : (/* @__PURE__ */ new Date()).toISOString()
460
661
  };
461
662
  }
663
+ var ACCEPTED_UNRESOLVED_KINDS = /* @__PURE__ */ new Set([
664
+ "identity_mismatch",
665
+ "body_unreadable",
666
+ "router_identity_conflict"
667
+ ]);
668
+ var OBSERVED_JOB_ID_FIELDS = ["header_job_id", "body_job_id", "accepted_job_id"];
669
+ var PLACEHOLDER_KIND = "body_unreadable";
670
+ var PLACEHOLDER_OBSERVED_AT = (/* @__PURE__ */ new Date(0)).toISOString();
671
+ function assertAcceptedUnresolvedStorable(wallet) {
672
+ const entries = wallet.pending_submissions;
673
+ if (!Array.isArray(entries)) return;
674
+ for (const entry of entries) {
675
+ const record = entry?.accepted_unresolved;
676
+ if (record === void 0 || isStorableAcceptedUnresolved(record)) continue;
677
+ throw new DvmError(
678
+ "accepted_unresolved_invalid",
679
+ `Refusing to save pending submission ${String(entry?.request_id)}: its accepted_unresolved record is outside the stored contract.`,
680
+ "Use one of the three kinds, an ISO-8601 observed_at, and job IDs and ambiguity_id that pass isStorableJobId; omit an observed ID that does not. The wallet file was not changed."
681
+ );
682
+ }
683
+ }
684
+ function isStorableAcceptedUnresolved(value) {
685
+ if (!isPlainRecord(value)) return false;
686
+ return ACCEPTED_UNRESOLVED_KINDS.has(value.kind) && isIsoTimestamp(value.observed_at) && isStorableJobId(value.ambiguity_id) && OBSERVED_JOB_ID_FIELDS.every((field) => value[field] === void 0 || isStorableJobId(value[field]));
687
+ }
688
+ function normalizeAcceptedUnresolved(wallet) {
689
+ const entries = wallet.pending_submissions;
690
+ if (!Array.isArray(entries)) return;
691
+ for (const entry of entries) {
692
+ if (!entry || entry.accepted_unresolved === void 0) continue;
693
+ const stored = entry.accepted_unresolved;
694
+ if (isStorableAcceptedUnresolved(stored)) continue;
695
+ const record = isPlainRecord(stored) ? { ...stored } : {};
696
+ if (!ACCEPTED_UNRESOLVED_KINDS.has(record.kind)) record.kind = PLACEHOLDER_KIND;
697
+ if (!isIsoTimestamp(record.observed_at)) record.observed_at = PLACEHOLDER_OBSERVED_AT;
698
+ if (!isStorableJobId(record.ambiguity_id)) {
699
+ record.ambiguity_id = isStorableJobId(entry.request_id) ? entry.request_id : "missing-ambiguity";
700
+ }
701
+ for (const field of OBSERVED_JOB_ID_FIELDS) {
702
+ if (record[field] !== void 0 && !isStorableJobId(record[field])) delete record[field];
703
+ }
704
+ entry.accepted_unresolved = record;
705
+ }
706
+ }
707
+ function isPlainRecord(value) {
708
+ return typeof value === "object" && value !== null && !Array.isArray(value);
709
+ }
710
+ var ISO_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d{1,9})?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
711
+ function isIsoTimestamp(value) {
712
+ if (typeof value !== "string") return false;
713
+ const match = ISO_TIMESTAMP.exec(value);
714
+ if (!match) return false;
715
+ const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
716
+ const date = new Date(Date.UTC(year, month - 1, day));
717
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
718
+ }
462
719
  function isCounterMap(value) {
463
720
  if (typeof value !== "object" || value === null) return false;
464
721
  for (const inner of Object.values(value)) {
@@ -1065,36 +1322,47 @@ function buildAdminAuthHeader(args) {
1065
1322
  };
1066
1323
  }
1067
1324
 
1325
+ // src/lib/job-identity-header.ts
1326
+ var JOB_IDENTITY_HEADER = "dvm-job-identity-v1";
1327
+ var JOB_IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
1328
+ function isJobIdentityValue(value) {
1329
+ return typeof value === "string" && JOB_IDENTITY_PATTERN.test(value);
1330
+ }
1331
+ function parseJobIdentityHeader(value) {
1332
+ if (value === null) return { kind: "absent" };
1333
+ return isJobIdentityValue(value) ? { kind: "valid", jobId: value } : { kind: "invalid" };
1334
+ }
1335
+
1068
1336
  // src/lib/builder-identity.ts
1069
1337
  import {
1070
- chmodSync as chmodSync3,
1071
- existsSync as existsSync5,
1072
- mkdirSync as mkdirSync2,
1073
- readFileSync as readFileSync5,
1338
+ chmodSync as chmodSync4,
1339
+ existsSync as existsSync6,
1340
+ mkdirSync as mkdirSync3,
1341
+ readFileSync as readFileSync6,
1074
1342
  renameSync as renameSync4,
1075
- statSync,
1343
+ statSync as statSync2,
1076
1344
  unlinkSync as unlinkSync4,
1077
- writeFileSync as writeFileSync5
1345
+ writeFileSync as writeFileSync6
1078
1346
  } from "fs";
1079
1347
  import { schnorr as schnorr3 } from "@noble/curves/secp256k1.js";
1080
1348
  import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
1081
1349
 
1082
1350
  // src/lib/builder-store.ts
1083
1351
  import {
1084
- chmodSync as chmodSync2,
1085
- existsSync as existsSync4,
1086
- mkdirSync,
1087
- readFileSync as readFileSync4,
1352
+ chmodSync as chmodSync3,
1353
+ existsSync as existsSync5,
1354
+ mkdirSync as mkdirSync2,
1355
+ readFileSync as readFileSync5,
1088
1356
  renameSync as renameSync3,
1089
1357
  unlinkSync as unlinkSync3,
1090
- writeFileSync as writeFileSync4
1358
+ writeFileSync as writeFileSync5
1091
1359
  } from "fs";
1092
1360
  var CURRENT_VERSION = 2;
1093
1361
  function loadBuilderStore() {
1094
- if (!existsSync4(BUILDER_FILE)) {
1362
+ if (!existsSync5(BUILDER_FILE)) {
1095
1363
  return { version: CURRENT_VERSION, dvms: {} };
1096
1364
  }
1097
- const raw = readFileSync4(BUILDER_FILE, "utf-8");
1365
+ const raw = readFileSync5(BUILDER_FILE, "utf-8");
1098
1366
  let parsed;
1099
1367
  try {
1100
1368
  parsed = JSON.parse(raw);
@@ -1130,7 +1398,7 @@ function loadIdentityPubkey() {
1130
1398
  return loadBuilderStore().identity_pubkey ?? null;
1131
1399
  }
1132
1400
  function setIdentityPubkey(pubkey) {
1133
- mkdirSync(BUILDER_CONFIG_DIR, { recursive: true, mode: 448 });
1401
+ mkdirSync2(BUILDER_CONFIG_DIR, { recursive: true, mode: 448 });
1134
1402
  const store = loadBuilderStore();
1135
1403
  store.identity_pubkey = pubkey;
1136
1404
  writeBuilderStore(store);
@@ -1140,7 +1408,7 @@ function writeBuilderStore(store) {
1140
1408
  backupSecretFile(BUILDER_FILE, content);
1141
1409
  const tmp = `${BUILDER_FILE}.tmp`;
1142
1410
  try {
1143
- writeFileSync4(tmp, content, { mode: 384 });
1411
+ writeFileSync5(tmp, content, { mode: 384 });
1144
1412
  renameSync3(tmp, BUILDER_FILE);
1145
1413
  } catch (err) {
1146
1414
  try {
@@ -1150,7 +1418,7 @@ function writeBuilderStore(store) {
1150
1418
  throw err;
1151
1419
  }
1152
1420
  try {
1153
- chmodSync2(BUILDER_FILE, 384);
1421
+ chmodSync3(BUILDER_FILE, 384);
1154
1422
  } catch {
1155
1423
  }
1156
1424
  }
@@ -1186,9 +1454,9 @@ function generateIdentity() {
1186
1454
  return { pubkey: bytesToHex4(publicKey), secret: bytesToHex4(secretKey) };
1187
1455
  }
1188
1456
  function loadIdentitySecret() {
1189
- if (!existsSync5(BUILDER_KEY_FILE)) return null;
1457
+ if (!existsSync6(BUILDER_KEY_FILE)) return null;
1190
1458
  warnAndTightenLooseKeyMode();
1191
- const raw = readFileSync5(BUILDER_KEY_FILE, "utf-8").trim();
1459
+ const raw = readFileSync6(BUILDER_KEY_FILE, "utf-8").trim();
1192
1460
  if (!/^[0-9a-f]{64}$/.test(raw)) {
1193
1461
  throw new DvmError(
1194
1462
  "identity_secret_malformed",
@@ -1207,9 +1475,9 @@ function writeIdentity(keypair, opts = {}) {
1207
1475
  "Pass --force to overwrite. Any DVMs deployed under the old key will need to be re-deployed to refresh their /v1/info attestation."
1208
1476
  );
1209
1477
  }
1210
- mkdirSync2(BUILDER_CONFIG_DIR, { recursive: true, mode: 448 });
1478
+ mkdirSync3(BUILDER_CONFIG_DIR, { recursive: true, mode: 448 });
1211
1479
  const tmp = `${BUILDER_KEY_FILE}.tmp`;
1212
- writeFileSync5(tmp, keypair.secret + "\n", { mode: 384 });
1480
+ writeFileSync6(tmp, keypair.secret + "\n", { mode: 384 });
1213
1481
  try {
1214
1482
  renameSync4(tmp, BUILDER_KEY_FILE);
1215
1483
  } catch (err) {
@@ -1220,7 +1488,7 @@ function writeIdentity(keypair, opts = {}) {
1220
1488
  throw err;
1221
1489
  }
1222
1490
  try {
1223
- chmodSync3(BUILDER_KEY_FILE, 384);
1491
+ chmodSync4(BUILDER_KEY_FILE, 384);
1224
1492
  } catch {
1225
1493
  }
1226
1494
  setIdentityPubkey(keypair.pubkey);
@@ -1254,7 +1522,7 @@ function verifyAttestation(payload, signatureHex, pubkeyHex) {
1254
1522
  function warnAndTightenLooseKeyMode() {
1255
1523
  if (process.platform === "win32") return;
1256
1524
  try {
1257
- const mode = statSync(BUILDER_KEY_FILE).mode & 511;
1525
+ const mode = statSync2(BUILDER_KEY_FILE).mode & 511;
1258
1526
  if ((mode & 63) === 0) return;
1259
1527
  diagnosticConsole.warn(
1260
1528
  {
@@ -1265,7 +1533,7 @@ function warnAndTightenLooseKeyMode() {
1265
1533
  mode: mode.toString(8).padStart(3, "0")
1266
1534
  }
1267
1535
  );
1268
- chmodSync3(BUILDER_KEY_FILE, 384);
1536
+ chmodSync4(BUILDER_KEY_FILE, 384);
1269
1537
  } catch {
1270
1538
  }
1271
1539
  }
@@ -1285,6 +1553,82 @@ function hexToBytes4(hex) {
1285
1553
  return out;
1286
1554
  }
1287
1555
 
1556
+ // src/lib/receipt.ts
1557
+ import { schnorr as schnorr4 } from "@noble/curves/secp256k1.js";
1558
+ import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
1559
+ import { bytesToHex as bytesToHex5, hexToBytes as hexToBytes5 } from "@noble/hashes/utils.js";
1560
+ function computeResultHash(summary, artifactContents) {
1561
+ const artifactHashes = artifactContents.map(
1562
+ (content) => bytesToHex5(sha2564(canonicaliseForSigning(content)))
1563
+ );
1564
+ return bytesToHex5(
1565
+ sha2564(
1566
+ canonicaliseForSigning({
1567
+ summary: summary ?? null,
1568
+ artifact_hashes: artifactHashes
1569
+ })
1570
+ )
1571
+ );
1572
+ }
1573
+ function signReceipt(unsigned, secretHex) {
1574
+ const bytes = canonicaliseForSigning(unsigned);
1575
+ const signature = bytesToHex5(schnorr4.sign(bytes, hexToBytes5(secretHex)));
1576
+ return { ...unsigned, signature };
1577
+ }
1578
+ function verifyReceipt(receipt) {
1579
+ try {
1580
+ const { signature, ...unsigned } = receipt;
1581
+ const bytes = canonicaliseForSigning(unsigned);
1582
+ return schnorr4.verify(hexToBytes5(signature), bytes, hexToBytes5(receipt.receipt_pubkey));
1583
+ } catch {
1584
+ return false;
1585
+ }
1586
+ }
1587
+ function isSignedJobReceipt(value) {
1588
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1589
+ const r = value;
1590
+ const paid = r.paid;
1591
+ 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";
1592
+ }
1593
+ function signDrainReceipt(unsigned, secretHex) {
1594
+ const bytes = canonicaliseForSigning(unsigned);
1595
+ const signature = bytesToHex5(schnorr4.sign(bytes, hexToBytes5(secretHex)));
1596
+ return { ...unsigned, signature };
1597
+ }
1598
+ function signFundingReceipt(unsigned, secretHex) {
1599
+ const bytes = canonicaliseForSigning(unsigned);
1600
+ const signature = bytesToHex5(schnorr4.sign(bytes, hexToBytes5(secretHex)));
1601
+ return { ...unsigned, signature };
1602
+ }
1603
+ function isFundingReceipt(value) {
1604
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1605
+ const r = value;
1606
+ 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";
1607
+ }
1608
+ function verifyFundingReceipt(receipt) {
1609
+ try {
1610
+ const { signature, ...unsigned } = receipt;
1611
+ const bytes = canonicaliseForSigning(unsigned);
1612
+ return schnorr4.verify(hexToBytes5(signature), bytes, hexToBytes5(receipt.receipt_pubkey));
1613
+ } catch {
1614
+ return false;
1615
+ }
1616
+ }
1617
+ function isDrainReceipt(value) {
1618
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1619
+ const r = value;
1620
+ 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";
1621
+ }
1622
+ function verifyDrainReceipt(receipt) {
1623
+ try {
1624
+ const { signature, ...unsigned } = receipt;
1625
+ const bytes = canonicaliseForSigning(unsigned);
1626
+ return schnorr4.verify(hexToBytes5(signature), bytes, hexToBytes5(receipt.receipt_pubkey));
1627
+ } catch {
1628
+ return false;
1629
+ }
1630
+ }
1631
+
1288
1632
  // src/lib/receipt-verify.ts
1289
1633
  function attestedDvmIdentity(builder) {
1290
1634
  const builderPubkey = authenticatedBuilderPubkey(builder);
@@ -1430,90 +1774,6 @@ function weakestReceiptVerdict(statuses) {
1430
1774
  if (statuses.includes("verified_unattested")) return "verified_unattested";
1431
1775
  return "verified";
1432
1776
  }
1433
- function drainReceiptsDisplay(status, sound, total) {
1434
- const tally = `${String(sound)} of ${String(total)}`;
1435
- switch (status) {
1436
- case "verified":
1437
- return `Verified reclaim receipts (${tally}): signed by the key this DVM's builder identity attests.`;
1438
- case "verified_unattested":
1439
- return `Reclaim receipts, signed but unattested (${tally}): the signature holds, but this DVM publishes no builder identity to vouch for the signing key.`;
1440
- case "invalid":
1441
- return `UNVERIFIED reclaim receipts: only ${tally} hold up \u2014 the refund is still owed, but this evidence is not sound.`;
1442
- case "absent":
1443
- return "No reclaim receipts: this DVM returned no signed evidence of the drain.";
1444
- }
1445
- }
1446
- function drainReceiptsHint(status) {
1447
- switch (status) {
1448
- case "verified":
1449
- 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.";
1450
- case "verified_unattested":
1451
- 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.";
1452
- case "invalid":
1453
- 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.";
1454
- case "absent":
1455
- 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.";
1456
- }
1457
- }
1458
- function receiptDisplay(receipt, status, rate = 0) {
1459
- const what = `${receipt.dvm}/${receipt.capability} (DVM ID ${receipt.dvm_id})`;
1460
- const when = receiptIssuedAtIso(receipt)?.slice(0, 10);
1461
- return `${VERDICT_PREFIX[status]}: paid ${formatPaid(receipt.paid.msats, rate)} for ${what}, ${receipt.outcome}${when ? ` ${when}` : ""}.`;
1462
- }
1463
- function receiptIssuedAtIso(receipt) {
1464
- const seconds = receipt.issued_at;
1465
- return typeof seconds === "number" ? epochMsToIso(seconds * 1e3) : null;
1466
- }
1467
- function epochMsToIso(ms) {
1468
- if (typeof ms !== "number" || !Number.isFinite(ms) || Math.abs(ms) > MAX_TIMESTAMP_MS) {
1469
- return null;
1470
- }
1471
- return new Date(ms).toISOString();
1472
- }
1473
- function receiptHint(status, jobId) {
1474
- const verifyCmd = jobId ? `'dvm receipts verify ${jobId}'` : "'dvm receipts verify <job-id>'";
1475
- switch (status) {
1476
- case "verified":
1477
- return `Proof of purchase is stored locally. Re-check it any time with ${verifyCmd}, or hand over the raw bundle with 'dvm receipts export'.`;
1478
- case "verified_unattested":
1479
- 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}.`;
1480
- case "invalid":
1481
- 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.`;
1482
- case "absent":
1483
- return "This DVM does not issue signed receipts, so there is no proof of purchase to store. Nothing went wrong.";
1484
- }
1485
- }
1486
- function receiptFailureDetail(reason) {
1487
- switch (reason) {
1488
- case "job_id_mismatch":
1489
- 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.";
1490
- case "drain_id_mismatch":
1491
- 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.";
1492
- case "credit_id_mismatch":
1493
- 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.";
1494
- case "fund_id_mismatch":
1495
- return "The receipt names a different funding operation than the caller submitted.";
1496
- case "funding_amount_mismatch":
1497
- return "The signed funded amount differs from the provider's funding response.";
1498
- case "funding_balance_mismatch":
1499
- return "The signed post-funding balance differs from the provider's fresh balance response.";
1500
- case "funding_ledger_seq_mismatch":
1501
- return "The signed funding sequence differs from the provider's fresh balance response.";
1502
- case "caller_pubkey_mismatch":
1503
- return "The funding receipt names a different caller than the key that submitted the funding.";
1504
- case "signature_invalid":
1505
- 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.";
1506
- case "attestation_invalid":
1507
- return "The DVM's published builder attestation does not verify under its own builder pubkey.";
1508
- case "dvm_identity_mismatch":
1509
- return "The receipt names a different immutable DVM ID than the builder attestation, so it is not evidence for this provider.";
1510
- case "receipt_key_mismatch":
1511
- return "The receipt was signed by a different key than the one the DVM's builder attested.";
1512
- case "result_hash_mismatch":
1513
- return "The receipt attests a different result than the one delivered.";
1514
- }
1515
- }
1516
- var MAX_TIMESTAMP_MS = 864e13;
1517
1777
  function checkJobId(receipt, expectedJobId) {
1518
1778
  if (expectedJobId === void 0) return "unchecked";
1519
1779
  return receipt.job_id === expectedJobId ? "ok" : "mismatch";
@@ -1522,12 +1782,6 @@ function checkDrainField(actual, expected) {
1522
1782
  if (expected === void 0 || actual === void 0) return "unchecked";
1523
1783
  return actual === expected ? "ok" : "mismatch";
1524
1784
  }
1525
- var VERDICT_PREFIX = {
1526
- verified: "Verified receipt",
1527
- verified_unattested: "Receipt (signed, unattested)",
1528
- invalid: "UNVERIFIED receipt",
1529
- absent: "No receipt"
1530
- };
1531
1785
  function checkResultHash(receipt, delivered) {
1532
1786
  if (!delivered) return "unchecked";
1533
1787
  if (receipt.result_hash === null) return "unchecked";
@@ -1545,14 +1799,6 @@ function isTrustAnchor(value) {
1545
1799
  const anchor = value;
1546
1800
  return typeof anchor.pubkey === "string" && typeof anchor.signature === "string" && !!anchor.attestation && typeof anchor.attestation === "object" && !Array.isArray(anchor.attestation);
1547
1801
  }
1548
- function formatPaid(msats, rate) {
1549
- if (msats === 0) return "nothing (free job)";
1550
- const sats = msats % 1e3 === 0 ? msats / 1e3 : Math.round(msats / 1e3 * 100) / 100;
1551
- const satsLabel = `${sats.toLocaleString("en-US")} sats`;
1552
- if (rate <= 0) return satsLabel;
1553
- const usd = msatsToUsd(msats, rate);
1554
- return `~$${formatUsd(usd)} (${satsLabel}, ${costAnchor(usd)})`;
1555
- }
1556
1802
 
1557
1803
  // src/lib/request-id.ts
1558
1804
  var MAX_REQUEST_ID_LENGTH = 128;
@@ -1589,51 +1835,44 @@ function createMonotonicClock() {
1589
1835
  };
1590
1836
  }
1591
1837
 
1592
- // src/lib/cashu/verify.ts
1593
- import { getTokenMetadata } from "@cashu/cashu-ts";
1594
- function verifyCashuToken(token, acceptedMints) {
1595
- let mint, unit, totalMsats;
1596
- try {
1597
- const meta = getTokenMetadata(token);
1598
- mint = meta.mint;
1599
- unit = meta.unit;
1600
- totalMsats = meta.amount.toNumber() * 1e3;
1601
- } catch {
1602
- throw new DvmError(
1603
- "cashu_invalid_token",
1604
- "The Cashu token could not be decoded.",
1605
- "Ensure the token is a valid cashuA... or cashuB... string."
1606
- );
1607
- }
1608
- const normalised = acceptedMints.map(normaliseMintUrl);
1609
- if (!normalised.includes(normaliseMintUrl(mint))) {
1610
- throw new DvmError(
1611
- "cashu_mint_not_accepted",
1612
- `Mint ${mint} is not accepted by this provider.`,
1613
- `Accepted mints: ${acceptedMints.join(", ")}`
1614
- );
1615
- }
1616
- return { mint, totalMsats, unit };
1617
- }
1618
- function normaliseMintUrl(url) {
1619
- try {
1620
- const u = new URL(url);
1621
- u.hostname = u.hostname.toLowerCase();
1622
- u.pathname = u.pathname.replace(/\/+/g, "/").replace(/\/+$/, "") || "/";
1623
- return u.toString().replace(/\/$/, "");
1624
- } catch {
1625
- return url.toLowerCase().replace(/\/+$/, "");
1626
- }
1627
- }
1628
-
1629
1838
  export {
1630
1839
  toLockPubkey,
1631
1840
  isX402SettlementReconciliationReason,
1632
- verifyCashuToken,
1633
- normaliseMintUrl,
1841
+ IMPLICIT_CREDIT_TTL_MS,
1634
1842
  TEMPO_SESSION_UPDATED_REASON,
1635
- RECOMMENDED_MINTS,
1636
- isTestMintUrl,
1843
+ CONFIG_DIR,
1844
+ CONFIG_FILE,
1845
+ CONFIG_BACKUP_FILE,
1846
+ CONFIG_AUDIT_FILE,
1847
+ CONFIG_PINNED_FILE,
1848
+ JOBS_FILE,
1849
+ REQUEST_INTENTS_DIR,
1850
+ FAVORITES_FILE,
1851
+ MESSAGES_DIR,
1852
+ RECEIPTS_FILE,
1853
+ CREDITS_FILE,
1854
+ X402_CHANNELS_FILE,
1855
+ TEMPO_CHANNELS_FILE,
1856
+ FEEDBACK_NUDGES_FILE,
1857
+ RECOVERY_MNEMONIC_FILE,
1858
+ WALLET_FILE,
1859
+ AGENT_MNEMONIC_FILE,
1860
+ IDENTITIES_FILE,
1861
+ PAIRINGS_DIR,
1862
+ BUDGET_FILE,
1863
+ BTC_RATE_FILE,
1864
+ ensureConfigDir,
1865
+ ensureConfigSubdir,
1866
+ repairConfigDirMode,
1867
+ COINGECKO_SIMPLE_PRICE_URL,
1868
+ fetchCoinGeckoSpot,
1869
+ fetchCoinGeckoBtcUsd,
1870
+ costAnchor,
1871
+ formatUsd,
1872
+ formatUsdcMicrounits,
1873
+ fetchBtcUsdRate,
1874
+ getLastKnownBtcRate,
1875
+ formatStaleness,
1637
1876
  backupSecretFile,
1638
1877
  readAgentMnemonic,
1639
1878
  assertAgentMnemonicReplaceable,
@@ -1647,6 +1886,7 @@ export {
1647
1886
  WALLET_VERSION,
1648
1887
  RECENT_SPENDS_CAP,
1649
1888
  DEFAULT_AGENT_MINT_URL,
1889
+ isStorableJobId,
1650
1890
  loadWalletConfiguredMints,
1651
1891
  loadAgentWallet,
1652
1892
  saveAgentWallet,
@@ -1674,6 +1914,9 @@ export {
1674
1914
  ADMIN_STATE_UNAVAILABLE,
1675
1915
  buildAdminChallenge,
1676
1916
  buildAdminAuthHeader,
1917
+ JOB_IDENTITY_HEADER,
1918
+ isJobIdentityValue,
1919
+ parseJobIdentityHeader,
1677
1920
  generateIdentity,
1678
1921
  loadIdentitySecret,
1679
1922
  writeIdentity,
@@ -1681,6 +1924,16 @@ export {
1681
1924
  buildAttestation,
1682
1925
  signAttestation,
1683
1926
  verifyAttestation,
1927
+ computeResultHash,
1928
+ signReceipt,
1929
+ verifyReceipt,
1930
+ isSignedJobReceipt,
1931
+ signDrainReceipt,
1932
+ signFundingReceipt,
1933
+ isFundingReceipt,
1934
+ verifyFundingReceipt,
1935
+ isDrainReceipt,
1936
+ verifyDrainReceipt,
1684
1937
  attestedDvmIdentity,
1685
1938
  authenticatedBuilderPubkey,
1686
1939
  verifyJobReceipt,
@@ -1688,13 +1941,6 @@ export {
1688
1941
  verifyDrainReceiptChain,
1689
1942
  verifyFundingReceiptChain,
1690
1943
  weakestReceiptVerdict,
1691
- drainReceiptsDisplay,
1692
- drainReceiptsHint,
1693
- receiptDisplay,
1694
- receiptIssuedAtIso,
1695
- epochMsToIso,
1696
- receiptHint,
1697
- receiptFailureDetail,
1698
1944
  isPublicRequestId,
1699
1945
  parseTraceparent,
1700
1946
  createMonotonicClock