@dvmkit/sdk 0.7.0-rc.1 → 0.7.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{chunk-S3OY533W.js → chunk-26W6GPCD.js} +48 -6
- package/dist/{chunk-FWGWLPXF.js → chunk-2OM2QPBE.js} +2 -2
- package/dist/{chunk-CXCQLOOK.js → chunk-62FI3NJF.js} +42 -107
- package/dist/{chunk-VHNPVGSA.js → chunk-6STC5TCP.js} +3 -3
- package/dist/{chunk-N5RZ5HVB.js → chunk-72V5DTEC.js} +2 -2
- package/dist/chunk-A262WVYX.js +6 -0
- package/dist/{chunk-GY66THYI.js → chunk-A6CXKAQV.js} +183 -88
- package/dist/{chunk-QR2EF2NI.js → chunk-AQ7OX5NX.js} +2 -2
- package/dist/{chunk-BYCEKO65.js → chunk-CLIAFTUR.js} +102 -13
- package/dist/{chunk-GAOAYHZA.js → chunk-DNG4KGSC.js} +3 -3
- package/dist/{chunk-Z4ZT5LNT.js → chunk-GSLS4464.js} +3 -3
- package/dist/{chunk-RA2WIX7G.js → chunk-H5FVVEEN.js} +18 -7
- package/dist/{chunk-IXR5RFK7.js → chunk-HCE4MPZ5.js} +1 -1
- package/dist/{chunk-OML32F6I.js → chunk-I555L2WK.js} +1 -1
- package/dist/{chunk-EQLA4FFY.js → chunk-IIZQNDQU.js} +3 -3
- package/dist/{chunk-FUJ36YDV.js → chunk-KXWROQGK.js} +24 -1
- package/dist/{chunk-ETXDTUJO.js → chunk-LAKP6FUI.js} +55 -0
- package/dist/{chunk-X7EZ5CLI.js → chunk-MON76YBJ.js} +3 -3
- package/dist/chunk-OFSOLHRX.js +120 -0
- package/dist/{chunk-IV6Q7NRW.js → chunk-RAGX6VG7.js} +2 -2
- package/dist/{chunk-KA6VN5FK.js → chunk-RXPHMS4C.js} +3 -3
- package/dist/{chunk-6XF3RVVX.js → chunk-SPYEJXI7.js} +5 -5
- package/dist/{chunk-P6QON7ER.js → chunk-TRNMTTPX.js} +1 -1
- package/dist/{chunk-RCM3P2HE.js → chunk-UKD3R2KU.js} +55 -4
- package/dist/chunk-USL7ZEXN.js +17 -0
- package/dist/{chunk-KMED3H3A.js → chunk-VB5OLOZH.js} +395 -231
- package/dist/{content-retention-DHyLdA6b.d.ts → content-retention-CbnouEnh.d.ts} +1 -1
- package/dist/{credit-ledger-MCUXYKSJ.js → credit-ledger-7Z7LOCTN.js} +4 -4
- package/dist/{credit-menu-DjnkeEUU.d.ts → credit-menu-BO8yJppy.d.ts} +4 -4
- package/dist/{fx-zMWFzfrU.d.ts → fx-DSOZQ2z7.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +236 -16
- package/dist/internal/caller.d.ts +4968 -8286
- package/dist/internal/caller.js +4291 -8084
- package/dist/internal/index.js +1 -1
- package/dist/internal/server.d.ts +7 -7
- package/dist/internal/server.js +27 -30
- package/dist/{job-store-DdLXBpol.d.ts → job-store-BJRlmq2Q.d.ts} +1 -1
- package/dist/{lightning-backend-Bw-qtAbt.d.ts → lightning-backend-f16-fsYK.d.ts} +2 -2
- package/dist/{memory-credit-ledger-SBSMGMDX.js → memory-credit-ledger-3WOS74UH.js} +5 -5
- package/dist/{mpp-setup-RF5FXGGV.js → mpp-setup-XB4KATMU.js} +2 -2
- package/dist/{payout-reporter-3Y2U55ZX.js → payout-reporter-WT7XYW42.js} +2 -2
- package/dist/{postgres-consumed-credential-store-BUVCAPLS.js → postgres-consumed-credential-store-W7UP33AH.js} +3 -3
- package/dist/{postgres-job-store-VHLQBJZC.js → postgres-job-store-3KGUGFRD.js} +2 -2
- package/dist/{postgres-replay-store-ARZSQ45X.js → postgres-replay-store-CVNJJOQG.js} +2 -2
- package/dist/{pricing-XT2IAPNE.js → pricing-QAVZUTZR.js} +12 -12
- package/dist/{revenue-reporter-MAERPYGK.js → revenue-reporter-7UR3W2SD.js} +2 -2
- package/dist/server/index.d.ts +10 -10
- package/dist/server/index.js +44 -48
- package/dist/{step-cache-2MkvenJn.d.ts → step-cache-CJFiZU3n.d.ts} +1 -1
- package/dist/{tempo-charge-store-Q3D7KYMH.js → tempo-charge-store-GA6CED6O.js} +2 -2
- package/dist/{tempo-session-store-ZRNCOX7P.js → tempo-session-store-WZWQRITB.js} +3 -3
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +6 -7
- package/dist/{usd-DI3d3gTK.d.ts → usd-DmHHg0_4.d.ts} +1 -1
- package/dist/{x402-FT4S4JS4.js → x402-ZU4GML7Z.js} +8 -27
- package/package.json +1 -1
- package/dist/chunk-66HGCPBU.js +0 -25
- package/dist/chunk-AT6V3SY7.js +0 -102
- package/dist/chunk-EZXAJ6EI.js +0 -1082
- package/dist/chunk-HCLVYZUZ.js +0 -244
- package/dist/chunk-JEVLNAO3.js +0 -1668
- package/dist/chunk-NFMOPYBA.js +0 -357
- package/dist/chunk-O2OTZ5GP.js +0 -1869
- package/dist/chunk-OFVV37XC.js +0 -57
- package/dist/chunk-SZVXOQ75.js +0 -211
- package/dist/chunk-WBOKPC37.js +0 -795
- package/dist/tempo-lifecycle-GJ77YKVY.js +0 -15
- package/dist/tempo-wallet-X2GGAZ6X.js +0 -40
|
@@ -1,34 +1,15 @@
|
|
|
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-
|
|
3
|
+
} from "./chunk-UKD3R2KU.js";
|
|
23
4
|
import {
|
|
24
5
|
callerLoggers
|
|
25
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KXWROQGK.js";
|
|
26
7
|
import {
|
|
27
8
|
DvmError
|
|
28
9
|
} from "./chunk-BPYMEMQX.js";
|
|
29
10
|
import {
|
|
30
11
|
diagnosticConsole
|
|
31
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LAKP6FUI.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/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
97
|
-
readFileSync as
|
|
285
|
+
chmodSync as chmodSync2,
|
|
286
|
+
existsSync as existsSync3,
|
|
287
|
+
readFileSync as readFileSync3,
|
|
98
288
|
renameSync,
|
|
99
289
|
unlinkSync,
|
|
100
|
-
writeFileSync as
|
|
290
|
+
writeFileSync as writeFileSync3
|
|
101
291
|
} from "fs";
|
|
102
292
|
function readAgentMnemonic() {
|
|
103
|
-
if (!
|
|
104
|
-
const raw =
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
323
|
+
chmodSync2(AGENT_MNEMONIC_FILE, 384);
|
|
134
324
|
} catch {
|
|
135
325
|
}
|
|
136
326
|
return { backupFile };
|
|
137
327
|
}
|
|
138
328
|
function deleteAgentMnemonic() {
|
|
139
|
-
if (!
|
|
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
|
|
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;
|
|
@@ -211,8 +401,8 @@ function loadWalletConfiguredMints() {
|
|
|
211
401
|
return loadAgentWallet()?.mints ?? [];
|
|
212
402
|
}
|
|
213
403
|
function loadAgentWallet() {
|
|
214
|
-
if (!
|
|
215
|
-
const raw =
|
|
404
|
+
if (!existsSync4(WALLET_FILE)) return null;
|
|
405
|
+
const raw = readFileSync4(WALLET_FILE, "utf-8");
|
|
216
406
|
let parsed;
|
|
217
407
|
try {
|
|
218
408
|
parsed = JSON.parse(raw);
|
|
@@ -294,7 +484,7 @@ function saveAgentWallet(wallet) {
|
|
|
294
484
|
ensureConfigDir();
|
|
295
485
|
const tmp = `${WALLET_FILE}.tmp`;
|
|
296
486
|
const data = JSON.stringify(wallet, null, 2) + "\n";
|
|
297
|
-
|
|
487
|
+
writeFileSync4(tmp, data, { mode: 384 });
|
|
298
488
|
try {
|
|
299
489
|
renameSync2(tmp, WALLET_FILE);
|
|
300
490
|
} catch (err) {
|
|
@@ -306,7 +496,7 @@ function saveAgentWallet(wallet) {
|
|
|
306
496
|
}
|
|
307
497
|
}
|
|
308
498
|
function walletExists() {
|
|
309
|
-
return
|
|
499
|
+
return existsSync4(WALLET_FILE);
|
|
310
500
|
}
|
|
311
501
|
async function withWalletLock(fn, opts = {}) {
|
|
312
502
|
ensureConfigDir();
|
|
@@ -1067,34 +1257,34 @@ function buildAdminAuthHeader(args) {
|
|
|
1067
1257
|
|
|
1068
1258
|
// src/lib/builder-identity.ts
|
|
1069
1259
|
import {
|
|
1070
|
-
chmodSync as
|
|
1071
|
-
existsSync as
|
|
1072
|
-
mkdirSync as
|
|
1073
|
-
readFileSync as
|
|
1260
|
+
chmodSync as chmodSync4,
|
|
1261
|
+
existsSync as existsSync6,
|
|
1262
|
+
mkdirSync as mkdirSync3,
|
|
1263
|
+
readFileSync as readFileSync6,
|
|
1074
1264
|
renameSync as renameSync4,
|
|
1075
|
-
statSync,
|
|
1265
|
+
statSync as statSync2,
|
|
1076
1266
|
unlinkSync as unlinkSync4,
|
|
1077
|
-
writeFileSync as
|
|
1267
|
+
writeFileSync as writeFileSync6
|
|
1078
1268
|
} from "fs";
|
|
1079
1269
|
import { schnorr as schnorr3 } from "@noble/curves/secp256k1.js";
|
|
1080
1270
|
import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
|
|
1081
1271
|
|
|
1082
1272
|
// src/lib/builder-store.ts
|
|
1083
1273
|
import {
|
|
1084
|
-
chmodSync as
|
|
1085
|
-
existsSync as
|
|
1086
|
-
mkdirSync,
|
|
1087
|
-
readFileSync as
|
|
1274
|
+
chmodSync as chmodSync3,
|
|
1275
|
+
existsSync as existsSync5,
|
|
1276
|
+
mkdirSync as mkdirSync2,
|
|
1277
|
+
readFileSync as readFileSync5,
|
|
1088
1278
|
renameSync as renameSync3,
|
|
1089
1279
|
unlinkSync as unlinkSync3,
|
|
1090
|
-
writeFileSync as
|
|
1280
|
+
writeFileSync as writeFileSync5
|
|
1091
1281
|
} from "fs";
|
|
1092
1282
|
var CURRENT_VERSION = 2;
|
|
1093
1283
|
function loadBuilderStore() {
|
|
1094
|
-
if (!
|
|
1284
|
+
if (!existsSync5(BUILDER_FILE)) {
|
|
1095
1285
|
return { version: CURRENT_VERSION, dvms: {} };
|
|
1096
1286
|
}
|
|
1097
|
-
const raw =
|
|
1287
|
+
const raw = readFileSync5(BUILDER_FILE, "utf-8");
|
|
1098
1288
|
let parsed;
|
|
1099
1289
|
try {
|
|
1100
1290
|
parsed = JSON.parse(raw);
|
|
@@ -1130,7 +1320,7 @@ function loadIdentityPubkey() {
|
|
|
1130
1320
|
return loadBuilderStore().identity_pubkey ?? null;
|
|
1131
1321
|
}
|
|
1132
1322
|
function setIdentityPubkey(pubkey) {
|
|
1133
|
-
|
|
1323
|
+
mkdirSync2(BUILDER_CONFIG_DIR, { recursive: true, mode: 448 });
|
|
1134
1324
|
const store = loadBuilderStore();
|
|
1135
1325
|
store.identity_pubkey = pubkey;
|
|
1136
1326
|
writeBuilderStore(store);
|
|
@@ -1140,7 +1330,7 @@ function writeBuilderStore(store) {
|
|
|
1140
1330
|
backupSecretFile(BUILDER_FILE, content);
|
|
1141
1331
|
const tmp = `${BUILDER_FILE}.tmp`;
|
|
1142
1332
|
try {
|
|
1143
|
-
|
|
1333
|
+
writeFileSync5(tmp, content, { mode: 384 });
|
|
1144
1334
|
renameSync3(tmp, BUILDER_FILE);
|
|
1145
1335
|
} catch (err) {
|
|
1146
1336
|
try {
|
|
@@ -1150,7 +1340,7 @@ function writeBuilderStore(store) {
|
|
|
1150
1340
|
throw err;
|
|
1151
1341
|
}
|
|
1152
1342
|
try {
|
|
1153
|
-
|
|
1343
|
+
chmodSync3(BUILDER_FILE, 384);
|
|
1154
1344
|
} catch {
|
|
1155
1345
|
}
|
|
1156
1346
|
}
|
|
@@ -1186,9 +1376,9 @@ function generateIdentity() {
|
|
|
1186
1376
|
return { pubkey: bytesToHex4(publicKey), secret: bytesToHex4(secretKey) };
|
|
1187
1377
|
}
|
|
1188
1378
|
function loadIdentitySecret() {
|
|
1189
|
-
if (!
|
|
1379
|
+
if (!existsSync6(BUILDER_KEY_FILE)) return null;
|
|
1190
1380
|
warnAndTightenLooseKeyMode();
|
|
1191
|
-
const raw =
|
|
1381
|
+
const raw = readFileSync6(BUILDER_KEY_FILE, "utf-8").trim();
|
|
1192
1382
|
if (!/^[0-9a-f]{64}$/.test(raw)) {
|
|
1193
1383
|
throw new DvmError(
|
|
1194
1384
|
"identity_secret_malformed",
|
|
@@ -1207,9 +1397,9 @@ function writeIdentity(keypair, opts = {}) {
|
|
|
1207
1397
|
"Pass --force to overwrite. Any DVMs deployed under the old key will need to be re-deployed to refresh their /v1/info attestation."
|
|
1208
1398
|
);
|
|
1209
1399
|
}
|
|
1210
|
-
|
|
1400
|
+
mkdirSync3(BUILDER_CONFIG_DIR, { recursive: true, mode: 448 });
|
|
1211
1401
|
const tmp = `${BUILDER_KEY_FILE}.tmp`;
|
|
1212
|
-
|
|
1402
|
+
writeFileSync6(tmp, keypair.secret + "\n", { mode: 384 });
|
|
1213
1403
|
try {
|
|
1214
1404
|
renameSync4(tmp, BUILDER_KEY_FILE);
|
|
1215
1405
|
} catch (err) {
|
|
@@ -1220,7 +1410,7 @@ function writeIdentity(keypair, opts = {}) {
|
|
|
1220
1410
|
throw err;
|
|
1221
1411
|
}
|
|
1222
1412
|
try {
|
|
1223
|
-
|
|
1413
|
+
chmodSync4(BUILDER_KEY_FILE, 384);
|
|
1224
1414
|
} catch {
|
|
1225
1415
|
}
|
|
1226
1416
|
setIdentityPubkey(keypair.pubkey);
|
|
@@ -1254,7 +1444,7 @@ function verifyAttestation(payload, signatureHex, pubkeyHex) {
|
|
|
1254
1444
|
function warnAndTightenLooseKeyMode() {
|
|
1255
1445
|
if (process.platform === "win32") return;
|
|
1256
1446
|
try {
|
|
1257
|
-
const mode =
|
|
1447
|
+
const mode = statSync2(BUILDER_KEY_FILE).mode & 511;
|
|
1258
1448
|
if ((mode & 63) === 0) return;
|
|
1259
1449
|
diagnosticConsole.warn(
|
|
1260
1450
|
{
|
|
@@ -1265,7 +1455,7 @@ function warnAndTightenLooseKeyMode() {
|
|
|
1265
1455
|
mode: mode.toString(8).padStart(3, "0")
|
|
1266
1456
|
}
|
|
1267
1457
|
);
|
|
1268
|
-
|
|
1458
|
+
chmodSync4(BUILDER_KEY_FILE, 384);
|
|
1269
1459
|
} catch {
|
|
1270
1460
|
}
|
|
1271
1461
|
}
|
|
@@ -1285,6 +1475,82 @@ function hexToBytes4(hex) {
|
|
|
1285
1475
|
return out;
|
|
1286
1476
|
}
|
|
1287
1477
|
|
|
1478
|
+
// src/lib/receipt.ts
|
|
1479
|
+
import { schnorr as schnorr4 } from "@noble/curves/secp256k1.js";
|
|
1480
|
+
import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
|
|
1481
|
+
import { bytesToHex as bytesToHex5, hexToBytes as hexToBytes5 } from "@noble/hashes/utils.js";
|
|
1482
|
+
function computeResultHash(summary, artifactContents) {
|
|
1483
|
+
const artifactHashes = artifactContents.map(
|
|
1484
|
+
(content) => bytesToHex5(sha2564(canonicaliseForSigning(content)))
|
|
1485
|
+
);
|
|
1486
|
+
return bytesToHex5(
|
|
1487
|
+
sha2564(
|
|
1488
|
+
canonicaliseForSigning({
|
|
1489
|
+
summary: summary ?? null,
|
|
1490
|
+
artifact_hashes: artifactHashes
|
|
1491
|
+
})
|
|
1492
|
+
)
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
function signReceipt(unsigned, secretHex) {
|
|
1496
|
+
const bytes = canonicaliseForSigning(unsigned);
|
|
1497
|
+
const signature = bytesToHex5(schnorr4.sign(bytes, hexToBytes5(secretHex)));
|
|
1498
|
+
return { ...unsigned, signature };
|
|
1499
|
+
}
|
|
1500
|
+
function verifyReceipt(receipt) {
|
|
1501
|
+
try {
|
|
1502
|
+
const { signature, ...unsigned } = receipt;
|
|
1503
|
+
const bytes = canonicaliseForSigning(unsigned);
|
|
1504
|
+
return schnorr4.verify(hexToBytes5(signature), bytes, hexToBytes5(receipt.receipt_pubkey));
|
|
1505
|
+
} catch {
|
|
1506
|
+
return false;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
function isSignedJobReceipt(value) {
|
|
1510
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1511
|
+
const r = value;
|
|
1512
|
+
const paid = r.paid;
|
|
1513
|
+
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";
|
|
1514
|
+
}
|
|
1515
|
+
function signDrainReceipt(unsigned, secretHex) {
|
|
1516
|
+
const bytes = canonicaliseForSigning(unsigned);
|
|
1517
|
+
const signature = bytesToHex5(schnorr4.sign(bytes, hexToBytes5(secretHex)));
|
|
1518
|
+
return { ...unsigned, signature };
|
|
1519
|
+
}
|
|
1520
|
+
function signFundingReceipt(unsigned, secretHex) {
|
|
1521
|
+
const bytes = canonicaliseForSigning(unsigned);
|
|
1522
|
+
const signature = bytesToHex5(schnorr4.sign(bytes, hexToBytes5(secretHex)));
|
|
1523
|
+
return { ...unsigned, signature };
|
|
1524
|
+
}
|
|
1525
|
+
function isFundingReceipt(value) {
|
|
1526
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1527
|
+
const r = value;
|
|
1528
|
+
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";
|
|
1529
|
+
}
|
|
1530
|
+
function verifyFundingReceipt(receipt) {
|
|
1531
|
+
try {
|
|
1532
|
+
const { signature, ...unsigned } = receipt;
|
|
1533
|
+
const bytes = canonicaliseForSigning(unsigned);
|
|
1534
|
+
return schnorr4.verify(hexToBytes5(signature), bytes, hexToBytes5(receipt.receipt_pubkey));
|
|
1535
|
+
} catch {
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
function isDrainReceipt(value) {
|
|
1540
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1541
|
+
const r = value;
|
|
1542
|
+
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";
|
|
1543
|
+
}
|
|
1544
|
+
function verifyDrainReceipt(receipt) {
|
|
1545
|
+
try {
|
|
1546
|
+
const { signature, ...unsigned } = receipt;
|
|
1547
|
+
const bytes = canonicaliseForSigning(unsigned);
|
|
1548
|
+
return schnorr4.verify(hexToBytes5(signature), bytes, hexToBytes5(receipt.receipt_pubkey));
|
|
1549
|
+
} catch {
|
|
1550
|
+
return false;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1288
1554
|
// src/lib/receipt-verify.ts
|
|
1289
1555
|
function attestedDvmIdentity(builder) {
|
|
1290
1556
|
const builderPubkey = authenticatedBuilderPubkey(builder);
|
|
@@ -1430,90 +1696,6 @@ function weakestReceiptVerdict(statuses) {
|
|
|
1430
1696
|
if (statuses.includes("verified_unattested")) return "verified_unattested";
|
|
1431
1697
|
return "verified";
|
|
1432
1698
|
}
|
|
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
1699
|
function checkJobId(receipt, expectedJobId) {
|
|
1518
1700
|
if (expectedJobId === void 0) return "unchecked";
|
|
1519
1701
|
return receipt.job_id === expectedJobId ? "ok" : "mismatch";
|
|
@@ -1522,12 +1704,6 @@ function checkDrainField(actual, expected) {
|
|
|
1522
1704
|
if (expected === void 0 || actual === void 0) return "unchecked";
|
|
1523
1705
|
return actual === expected ? "ok" : "mismatch";
|
|
1524
1706
|
}
|
|
1525
|
-
var VERDICT_PREFIX = {
|
|
1526
|
-
verified: "Verified receipt",
|
|
1527
|
-
verified_unattested: "Receipt (signed, unattested)",
|
|
1528
|
-
invalid: "UNVERIFIED receipt",
|
|
1529
|
-
absent: "No receipt"
|
|
1530
|
-
};
|
|
1531
1707
|
function checkResultHash(receipt, delivered) {
|
|
1532
1708
|
if (!delivered) return "unchecked";
|
|
1533
1709
|
if (receipt.result_hash === null) return "unchecked";
|
|
@@ -1545,14 +1721,6 @@ function isTrustAnchor(value) {
|
|
|
1545
1721
|
const anchor = value;
|
|
1546
1722
|
return typeof anchor.pubkey === "string" && typeof anchor.signature === "string" && !!anchor.attestation && typeof anchor.attestation === "object" && !Array.isArray(anchor.attestation);
|
|
1547
1723
|
}
|
|
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
1724
|
|
|
1557
1725
|
// src/lib/request-id.ts
|
|
1558
1726
|
var MAX_REQUEST_ID_LENGTH = 128;
|
|
@@ -1589,51 +1757,44 @@ function createMonotonicClock() {
|
|
|
1589
1757
|
};
|
|
1590
1758
|
}
|
|
1591
1759
|
|
|
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
1760
|
export {
|
|
1630
1761
|
toLockPubkey,
|
|
1631
1762
|
isX402SettlementReconciliationReason,
|
|
1632
|
-
|
|
1633
|
-
normaliseMintUrl,
|
|
1763
|
+
IMPLICIT_CREDIT_TTL_MS,
|
|
1634
1764
|
TEMPO_SESSION_UPDATED_REASON,
|
|
1635
|
-
|
|
1636
|
-
|
|
1765
|
+
CONFIG_DIR,
|
|
1766
|
+
CONFIG_FILE,
|
|
1767
|
+
CONFIG_BACKUP_FILE,
|
|
1768
|
+
CONFIG_AUDIT_FILE,
|
|
1769
|
+
CONFIG_PINNED_FILE,
|
|
1770
|
+
JOBS_FILE,
|
|
1771
|
+
REQUEST_INTENTS_DIR,
|
|
1772
|
+
FAVORITES_FILE,
|
|
1773
|
+
MESSAGES_DIR,
|
|
1774
|
+
RECEIPTS_FILE,
|
|
1775
|
+
CREDITS_FILE,
|
|
1776
|
+
X402_CHANNELS_FILE,
|
|
1777
|
+
TEMPO_CHANNELS_FILE,
|
|
1778
|
+
FEEDBACK_NUDGES_FILE,
|
|
1779
|
+
RECOVERY_MNEMONIC_FILE,
|
|
1780
|
+
WALLET_FILE,
|
|
1781
|
+
AGENT_MNEMONIC_FILE,
|
|
1782
|
+
IDENTITIES_FILE,
|
|
1783
|
+
PAIRINGS_DIR,
|
|
1784
|
+
BUDGET_FILE,
|
|
1785
|
+
BTC_RATE_FILE,
|
|
1786
|
+
ensureConfigDir,
|
|
1787
|
+
ensureConfigSubdir,
|
|
1788
|
+
repairConfigDirMode,
|
|
1789
|
+
COINGECKO_SIMPLE_PRICE_URL,
|
|
1790
|
+
fetchCoinGeckoSpot,
|
|
1791
|
+
fetchCoinGeckoBtcUsd,
|
|
1792
|
+
costAnchor,
|
|
1793
|
+
formatUsd,
|
|
1794
|
+
formatUsdcMicrounits,
|
|
1795
|
+
fetchBtcUsdRate,
|
|
1796
|
+
getLastKnownBtcRate,
|
|
1797
|
+
formatStaleness,
|
|
1637
1798
|
backupSecretFile,
|
|
1638
1799
|
readAgentMnemonic,
|
|
1639
1800
|
assertAgentMnemonicReplaceable,
|
|
@@ -1681,6 +1842,16 @@ export {
|
|
|
1681
1842
|
buildAttestation,
|
|
1682
1843
|
signAttestation,
|
|
1683
1844
|
verifyAttestation,
|
|
1845
|
+
computeResultHash,
|
|
1846
|
+
signReceipt,
|
|
1847
|
+
verifyReceipt,
|
|
1848
|
+
isSignedJobReceipt,
|
|
1849
|
+
signDrainReceipt,
|
|
1850
|
+
signFundingReceipt,
|
|
1851
|
+
isFundingReceipt,
|
|
1852
|
+
verifyFundingReceipt,
|
|
1853
|
+
isDrainReceipt,
|
|
1854
|
+
verifyDrainReceipt,
|
|
1684
1855
|
attestedDvmIdentity,
|
|
1685
1856
|
authenticatedBuilderPubkey,
|
|
1686
1857
|
verifyJobReceipt,
|
|
@@ -1688,13 +1859,6 @@ export {
|
|
|
1688
1859
|
verifyDrainReceiptChain,
|
|
1689
1860
|
verifyFundingReceiptChain,
|
|
1690
1861
|
weakestReceiptVerdict,
|
|
1691
|
-
drainReceiptsDisplay,
|
|
1692
|
-
drainReceiptsHint,
|
|
1693
|
-
receiptDisplay,
|
|
1694
|
-
receiptIssuedAtIso,
|
|
1695
|
-
epochMsToIso,
|
|
1696
|
-
receiptHint,
|
|
1697
|
-
receiptFailureDetail,
|
|
1698
1862
|
isPublicRequestId,
|
|
1699
1863
|
parseTraceparent,
|
|
1700
1864
|
createMonotonicClock
|