@dvmkit/sdk 0.1.0-rc.4 → 0.1.0-rc.6

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.
@@ -17,24 +17,26 @@ import {
17
17
  verifyWithFacilitator,
18
18
  x402NetworkByCaip2,
19
19
  x402NetworkToCaip2
20
- } from "./chunk-AAJNGQMC.js";
20
+ } from "./chunk-JGGI65I3.js";
21
21
  import {
22
- AGENT_MNEMONIC_FILE,
23
- BUILDER_CONFIG_DIR,
24
- BUILDER_FILE,
25
- BUILDER_KEY_FILE,
26
- DvmError,
27
22
  IMPLICIT_CREDIT_TTL_MS,
28
- WALLET_FILE,
29
23
  costAnchor,
30
- ensureConfigDir,
31
24
  formatStaleness,
32
25
  formatUsd,
33
26
  msatsToUsd,
34
27
  msatsToUsdc,
35
- resolveRpcOverride,
36
28
  usdcToMsats
37
- } from "./chunk-F2L6KIMD.js";
29
+ } from "./chunk-5URG56JJ.js";
30
+ import {
31
+ AGENT_MNEMONIC_FILE,
32
+ BUILDER_CONFIG_DIR,
33
+ BUILDER_FILE,
34
+ BUILDER_KEY_FILE,
35
+ DvmError,
36
+ WALLET_FILE,
37
+ ensureConfigDir,
38
+ resolveRpcOverride
39
+ } from "./chunk-MKI6OVW4.js";
38
40
  import {
39
41
  MemoryCreditLedger
40
42
  } from "./chunk-EXHBXA4U.js";
@@ -50,7 +52,7 @@ import {
50
52
  isZodSchema,
51
53
  signedRequestInput,
52
54
  zodIssuesOf
53
- } from "./chunk-AZBXSXQT.js";
55
+ } from "./chunk-P4RUVDU7.js";
54
56
  import {
55
57
  JobCancelledError,
56
58
  MemoryJobStore,
@@ -1246,8 +1248,115 @@ function backupSecretFile(file, nextContent) {
1246
1248
  throw new Error(`Could not create a backup of ${file}: 100 candidate paths already exist.`);
1247
1249
  }
1248
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
1304
+ import { createHash as createHash3 } from "crypto";
1305
+ import { HDKey } from "@scure/bip32";
1306
+ 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
+ }
1357
+
1249
1358
  // src/lib/wallet-agent/store.ts
1250
- import { existsSync as existsSync2, readFileSync as readFileSync2, renameSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
1359
+ import { existsSync as existsSync3, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
1251
1360
  import lockfile from "proper-lockfile";
1252
1361
  var LOCK_FILE = `${WALLET_FILE}.lock`;
1253
1362
  var DEFAULT_LOCK_STALE_MS = 3e4;
@@ -1255,9 +1364,10 @@ var DEFAULT_LOCK_RETRIES = 30;
1255
1364
  var DEFAULT_LOCK_RETRY_INTERVAL_MS = 1e3;
1256
1365
  var WALLET_VERSION = 8;
1257
1366
  var RECENT_SPENDS_CAP = 10;
1367
+ var DEFAULT_AGENT_MINT_URL = "https://mint.coinos.io";
1258
1368
  function loadAgentWallet() {
1259
- if (!existsSync2(WALLET_FILE)) return null;
1260
- const raw = readFileSync2(WALLET_FILE, "utf-8");
1369
+ if (!existsSync3(WALLET_FILE)) return null;
1370
+ const raw = readFileSync3(WALLET_FILE, "utf-8");
1261
1371
  let parsed;
1262
1372
  try {
1263
1373
  parsed = JSON.parse(raw);
@@ -1322,17 +1432,20 @@ function saveAgentWallet(wallet) {
1322
1432
  ensureConfigDir();
1323
1433
  const tmp = `${WALLET_FILE}.tmp`;
1324
1434
  const data = JSON.stringify(wallet, null, 2) + "\n";
1325
- writeFileSync2(tmp, data, { mode: 384 });
1435
+ writeFileSync3(tmp, data, { mode: 384 });
1326
1436
  try {
1327
- renameSync(tmp, WALLET_FILE);
1437
+ renameSync2(tmp, WALLET_FILE);
1328
1438
  } catch (err) {
1329
1439
  try {
1330
- unlinkSync(tmp);
1440
+ unlinkSync2(tmp);
1331
1441
  } catch {
1332
1442
  }
1333
1443
  throw err;
1334
1444
  }
1335
1445
  }
1446
+ function walletExists() {
1447
+ return existsSync3(WALLET_FILE);
1448
+ }
1336
1449
  async function withWalletLock(fn, opts = {}) {
1337
1450
  ensureConfigDir();
1338
1451
  const stale = opts.stale ?? DEFAULT_LOCK_STALE_MS;
@@ -1363,6 +1476,12 @@ async function withWalletLock(fn, opts = {}) {
1363
1476
  }
1364
1477
  }
1365
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
+ }
1366
1485
  function migrateV1ToV4(parsed) {
1367
1486
  const proofs = Array.isArray(parsed.proofs) ? parsed.proofs : [];
1368
1487
  const pending = Array.isArray(parsed.pending_mints) ? parsed.pending_mints : [];
@@ -1473,6 +1592,98 @@ function isCounterMap(value) {
1473
1592
  return true;
1474
1593
  }
1475
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
+
1476
1687
  // src/lib/cashu/health.ts
1477
1688
  var DEFAULT_REQUIRED_NUTS = [4, 5, 7, 9, 10, 11, 20];
1478
1689
  async function checkMintHealth(mintUrl, opts) {
@@ -1592,6 +1803,12 @@ function mintAmountBounds(nuts) {
1592
1803
  function meltAmountBounds(nuts) {
1593
1804
  return boundsOf(nuts["5"]);
1594
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
+ }
1595
1812
  function boundsOf(node) {
1596
1813
  const method = findBolt11Sat(node);
1597
1814
  return {
@@ -1680,131 +1897,6 @@ function sleep(ms) {
1680
1897
  // src/lib/cashu/wallet.ts
1681
1898
  import { Wallet } from "@cashu/cashu-ts";
1682
1899
  import { mnemonicToSeedSync as mnemonicToSeedSync2 } from "@scure/bip39";
1683
-
1684
- // src/lib/wallet-agent/agent-mnemonic-file.ts
1685
- import {
1686
- chmodSync,
1687
- existsSync as existsSync3,
1688
- readFileSync as readFileSync3,
1689
- renameSync as renameSync2,
1690
- unlinkSync as unlinkSync2,
1691
- writeFileSync as writeFileSync3
1692
- } from "fs";
1693
- function readAgentMnemonic() {
1694
- if (!existsSync3(AGENT_MNEMONIC_FILE)) return null;
1695
- const raw = readFileSync3(AGENT_MNEMONIC_FILE, "utf-8");
1696
- return raw.trim().toLowerCase().split(/\s+/).join(" ");
1697
- }
1698
-
1699
- // src/lib/wallet-agent/mnemonic.ts
1700
- import { createHash as createHash3 } from "crypto";
1701
- import { HDKey } from "@scure/bip32";
1702
- import {
1703
- generateMnemonic,
1704
- mnemonicToEntropy,
1705
- mnemonicToSeedSync,
1706
- validateMnemonic
1707
- } from "@scure/bip39";
1708
- import { wordlist } from "@scure/bip39/wordlists/english.js";
1709
- function mnemonicFingerprint(mnemonic) {
1710
- const normalised = mnemonic.trim().toLowerCase().split(/\s+/).join(" ");
1711
- const entropy = mnemonicToEntropy(normalised, wordlist);
1712
- return createHash3("sha256").update(entropy).digest("hex");
1713
- }
1714
-
1715
- // src/lib/wallet-agent/nut13-counters.ts
1716
- var PersistentCounterSource = class {
1717
- constructor(mintUrl) {
1718
- this.mintUrl = mintUrl;
1719
- }
1720
- mintUrl;
1721
- /**
1722
- * Reserve `n` counters for `keysetId`. Returns `{ start, count: n }`
1723
- * and bumps the persisted `next` to `start + n` under the wallet lock.
1724
- *
1725
- * Per cashu-ts contract, `n === 0` MUST NOT mutate state and is a
1726
- * pure peek — we honour that by returning `{ start, count: 0 }`
1727
- * without acquiring the lock.
1728
- */
1729
- async reserve(keysetId, n) {
1730
- if (n === 0) {
1731
- const wallet = loadAgentWallet();
1732
- const current = wallet?.nut13_counters[this.mintUrl]?.[keysetId] ?? 0;
1733
- return { start: current, count: 0 };
1734
- }
1735
- return withWalletLock(() => {
1736
- const wallet = this.loadOrThrow();
1737
- const current = wallet.nut13_counters[this.mintUrl]?.[keysetId] ?? 0;
1738
- saveAgentWallet(this.withCounter(wallet, keysetId, current + n));
1739
- return { start: current, count: n };
1740
- });
1741
- }
1742
- /**
1743
- * Monotonic bump: ensure the persisted `next` for `keysetId` is at
1744
- * least `minNext`. No-op if the wallet is already ahead. Used by
1745
- * cashu-ts when the mint reports `outputs_already_signed` — the
1746
- * losing wallet rolls forward past the colliding counter.
1747
- */
1748
- async advanceToAtLeast(keysetId, minNext) {
1749
- await withWalletLock(() => {
1750
- const wallet = this.loadOrThrow();
1751
- const current = wallet.nut13_counters[this.mintUrl]?.[keysetId] ?? 0;
1752
- if (current >= minNext) return;
1753
- saveAgentWallet(this.withCounter(wallet, keysetId, minNext));
1754
- });
1755
- }
1756
- /**
1757
- * Snapshot the per-keyset `next` map for this mint. Useful for
1758
- * `dvm wallet show` and test assertions; cashu-ts itself does not
1759
- * depend on `snapshot` being implemented.
1760
- */
1761
- snapshot() {
1762
- const wallet = loadAgentWallet();
1763
- const counters = wallet?.nut13_counters[this.mintUrl] ?? {};
1764
- const out = {};
1765
- for (const [keyset, next] of Object.entries(counters)) {
1766
- if (typeof next === "number") out[keyset] = next;
1767
- }
1768
- return Promise.resolve(out);
1769
- }
1770
- /**
1771
- * Hard-set the persisted `next` for `keysetId`. Used by
1772
- * `dvm wallet restore` to re-anchor counters after walking
1773
- * `batchRestore` per keyset. cashu-ts also exposes this on
1774
- * `wallet.counters.setNext`.
1775
- */
1776
- async setNext(keysetId, next) {
1777
- await withWalletLock(() => {
1778
- const wallet = this.loadOrThrow();
1779
- saveAgentWallet(this.withCounter(wallet, keysetId, next));
1780
- });
1781
- }
1782
- loadOrThrow() {
1783
- const wallet = loadAgentWallet();
1784
- if (!wallet) {
1785
- throw new DvmError(
1786
- "wallet_missing",
1787
- "NUT-13 counter persistence requires an agent wallet at ~/.dvm/wallet.json.",
1788
- "Run `dvm wallet init` or `dvm wallet recover --file <path>` (or pipe the mnemonic via stdin) first."
1789
- );
1790
- }
1791
- return wallet;
1792
- }
1793
- withCounter(wallet, keysetId, next) {
1794
- return {
1795
- ...wallet,
1796
- nut13_counters: {
1797
- ...wallet.nut13_counters,
1798
- [this.mintUrl]: {
1799
- ...wallet.nut13_counters[this.mintUrl] ?? {},
1800
- [keysetId]: next
1801
- }
1802
- }
1803
- };
1804
- }
1805
- };
1806
-
1807
- // src/lib/cashu/wallet.ts
1808
1900
  var walletCache = /* @__PURE__ */ new Map();
1809
1901
  var keysetResidueRegistry = /* @__PURE__ */ new Map();
1810
1902
  var NUT13_MODULUS = BigInt(2 ** 31 - 1);
@@ -4365,7 +4457,7 @@ async function verifyUpfrontPayment(opts) {
4365
4457
  x402Requirements
4366
4458
  });
4367
4459
  }
4368
- const { verifyX402Payment } = await import("./x402-XXFQQAAD.js");
4460
+ const { verifyX402Payment } = await import("./x402-FTG2GRAQ.js");
4369
4461
  const receipt = await verifyX402Payment(
4370
4462
  x402Payment,
4371
4463
  x402Config,
@@ -5285,7 +5377,7 @@ async function verifyIncomingPayment(body, opts, snapshot) {
5285
5377
  snapshot
5286
5378
  });
5287
5379
  }
5288
- const { verifyX402Payment } = await import("./x402-XXFQQAAD.js");
5380
+ const { verifyX402Payment } = await import("./x402-FTG2GRAQ.js");
5289
5381
  const receipt = await verifyX402Payment(
5290
5382
  body.content.x402_payment,
5291
5383
  opts.x402Config,
@@ -5695,7 +5787,7 @@ async function processIncomingPayment(job, body, opts) {
5695
5787
  try {
5696
5788
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5697
5789
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5698
- const { verifyX402Payment } = await import("./x402-XXFQQAAD.js");
5790
+ const { verifyX402Payment } = await import("./x402-FTG2GRAQ.js");
5699
5791
  const receipt = await verifyX402Payment(
5700
5792
  body.content.x402_payment,
5701
5793
  opts.x402Config,
@@ -9098,6 +9190,34 @@ function pickPrimary(credits, currency) {
9098
9190
  return best;
9099
9191
  }
9100
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
+
9101
9221
  // src/lib/receipt-key.ts
9102
9222
  import { schnorr, secp256k1 } from "@noble/curves/secp256k1.js";
9103
9223
  import { hkdf } from "@noble/hashes/hkdf.js";
@@ -10076,14 +10196,57 @@ function formatPaid(msats, rate) {
10076
10196
  return `~$${formatUsd(usd)} (${satsLabel}, ${costAnchor(usd)})`;
10077
10197
  }
10078
10198
 
10079
- // src/lib/wallet-agent/sign.ts
10080
- import { schnorr as schnorr4 } from "@noble/curves/secp256k1.js";
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);
10204
+ }
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) };
10217
+ }
10081
10218
 
10082
10219
  // src/lib/wallet-agent/keypair.ts
10083
10220
  import { secp256k1 as secp256k12 } from "@noble/curves/secp256k1.js";
10084
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
+ );
10237
+ }
10238
+ const pub = secp256k12.getPublicKey(bytes);
10239
+ return bytesToHex4(pub);
10240
+ }
10241
+ function bytesToHex4(bytes) {
10242
+ return Buffer.from(bytes).toString("hex");
10243
+ }
10244
+ function hexToBytes4(hex) {
10245
+ return Uint8Array.from(Buffer.from(hex, "hex"));
10246
+ }
10085
10247
 
10086
10248
  // src/lib/wallet-agent/sign.ts
10249
+ import { schnorr as schnorr4 } from "@noble/curves/secp256k1.js";
10087
10250
  var PUBKEY_COMPRESSED_HEX_RE = /^0[23][0-9a-f]{64}$/i;
10088
10251
  var PUBKEY_XONLY_HEX_RE = /^[0-9a-f]{64}$/i;
10089
10252
  var SIG_HEX_RE = /^[0-9a-f]{128}$/i;
@@ -10102,30 +10265,30 @@ function signChallenge(privkeyHex, challenge) {
10102
10265
  "Pass the SHA-256 digest output (32 bytes)."
10103
10266
  );
10104
10267
  }
10105
- const sig = schnorr4.sign(challenge, hexToBytes4(privkeyHex));
10106
- return bytesToHex4(sig);
10268
+ const sig = schnorr4.sign(challenge, hexToBytes5(privkeyHex));
10269
+ return bytesToHex5(sig);
10107
10270
  }
10108
10271
  function verifyChallenge(pubkeyHex, challenge, sigHex) {
10109
10272
  if (!SIG_HEX_RE.test(sigHex)) return false;
10110
10273
  if (challenge.length !== 32) return false;
10111
10274
  let xonly;
10112
10275
  if (PUBKEY_COMPRESSED_HEX_RE.test(pubkeyHex)) {
10113
- xonly = hexToBytes4(pubkeyHex.slice(2));
10276
+ xonly = hexToBytes5(pubkeyHex.slice(2));
10114
10277
  } else if (PUBKEY_XONLY_HEX_RE.test(pubkeyHex)) {
10115
- xonly = hexToBytes4(pubkeyHex);
10278
+ xonly = hexToBytes5(pubkeyHex);
10116
10279
  } else {
10117
10280
  return false;
10118
10281
  }
10119
10282
  try {
10120
- return schnorr4.verify(hexToBytes4(sigHex), challenge, xonly);
10283
+ return schnorr4.verify(hexToBytes5(sigHex), challenge, xonly);
10121
10284
  } catch {
10122
10285
  return false;
10123
10286
  }
10124
10287
  }
10125
- function hexToBytes4(hex) {
10288
+ function hexToBytes5(hex) {
10126
10289
  return Uint8Array.from(Buffer.from(hex, "hex"));
10127
10290
  }
10128
- function bytesToHex4(bytes) {
10291
+ function bytesToHex5(bytes) {
10129
10292
  return Buffer.from(bytes).toString("hex");
10130
10293
  }
10131
10294
 
@@ -10867,13 +11030,6 @@ function createMonotonicClock() {
10867
11030
  };
10868
11031
  }
10869
11032
 
10870
- // src/lib/request-id.ts
10871
- var MAX_REQUEST_ID_LENGTH = 128;
10872
- var REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
10873
- function isPublicRequestId(value) {
10874
- return typeof value === "string" && value.length > 0 && value.length <= MAX_REQUEST_ID_LENGTH && REQUEST_ID_PATTERN.test(value);
10875
- }
10876
-
10877
11033
  // src/sdk/server/request-envelope.ts
10878
11034
  var REQUEST_ID_FIELD = "request_id";
10879
11035
  var AUTH_STATEMENT_FIELD = "auth_statement";
@@ -15068,19 +15224,6 @@ function shortSha(sha) {
15068
15224
  return base2.slice(0, 7) + (dirty ? DIRTY_SUFFIX : "");
15069
15225
  }
15070
15226
 
15071
- // src/lib/traceparent.ts
15072
- var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
15073
- var ALL_ZERO_TRACE = "00000000000000000000000000000000";
15074
- var ALL_ZERO_SPAN = "0000000000000000";
15075
- function parseTraceparent(header) {
15076
- if (!header) return null;
15077
- const match = TRACEPARENT_RE.exec(header.trim());
15078
- if (!match) return null;
15079
- const [, traceId, parentSpanId, flagsHex] = match;
15080
- if (traceId === ALL_ZERO_TRACE || parentSpanId === ALL_ZERO_SPAN) return null;
15081
- return { traceId, parentSpanId, flags: parseInt(flagsHex, 16) };
15082
- }
15083
-
15084
15227
  // src/sdk/server/admin-credit.ts
15085
15228
  import {
15086
15229
  Amount as Amount3,
@@ -20978,7 +21121,7 @@ function parseNwcUri(uri) {
20978
21121
  if (!/^[0-9a-fA-F]{64}$/.test(secretHex)) {
20979
21122
  throw new NwcError("invalid_uri", "NWC URI secret must be 64 hex characters");
20980
21123
  }
20981
- const secret = hexToBytes5(secretHex);
21124
+ const secret = hexToBytes6(secretHex);
20982
21125
  const clientPubkey = getPublicKey(secret);
20983
21126
  return { walletPubkey, relays, secret, clientPubkey };
20984
21127
  }
@@ -21358,7 +21501,7 @@ function positiveSecondsEnv(name, fallbackSec) {
21358
21501
  if (!Number.isFinite(n) || n <= 0) return fallbackSec * 1e3;
21359
21502
  return Math.floor(n) * 1e3;
21360
21503
  }
21361
- function hexToBytes5(hex) {
21504
+ function hexToBytes6(hex) {
21362
21505
  const bytes = new Uint8Array(hex.length / 2);
21363
21506
  for (let i = 0; i < bytes.length; i++) {
21364
21507
  bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
@@ -21626,12 +21769,29 @@ export {
21626
21769
  rateUnavailablePaymentError,
21627
21770
  backupSecretFile,
21628
21771
  readAgentMnemonic,
21772
+ assertAgentMnemonicReplaceable,
21773
+ writeAgentMnemonic,
21774
+ deleteAgentMnemonic,
21775
+ generateAgentMnemonic,
21776
+ deriveAgentMasterKeypair,
21777
+ validateAgentMnemonic,
21778
+ looksLikeMnemonic,
21779
+ mnemonicFingerprint,
21780
+ WALLET_VERSION,
21629
21781
  RECENT_SPENDS_CAP,
21782
+ DEFAULT_AGENT_MINT_URL,
21630
21783
  loadAgentWallet,
21631
21784
  saveAgentWallet,
21785
+ walletExists,
21632
21786
  withWalletLock,
21787
+ effectiveMints,
21788
+ effectiveFundingMint,
21789
+ PersistentCounterSource,
21633
21790
  checkMintHealth,
21634
21791
  canSwap,
21792
+ meltAmountBounds,
21793
+ amountBoundsVerdict,
21794
+ assertNutSupport,
21635
21795
  getWallet,
21636
21796
  X402_BATCH_CHANNEL_ABI,
21637
21797
  X402_BATCH_SETTLEMENT_NETWORK,
@@ -21656,6 +21816,8 @@ export {
21656
21816
  repairX402ExactSettlementEffect,
21657
21817
  CREDIT_REQUEST_SCHEMA,
21658
21818
  handleCreditRequest,
21819
+ RECOMMENDED_MINTS,
21820
+ isTestMintUrl,
21659
21821
  deriveReceiptSecret,
21660
21822
  receiptPubkeyFromSecret,
21661
21823
  deriveReceiptKeypair,
@@ -21702,6 +21864,10 @@ export {
21702
21864
  epochMsToIso,
21703
21865
  receiptHint,
21704
21866
  receiptFailureDetail,
21867
+ isPublicRequestId,
21868
+ parseTraceparent,
21869
+ PRIVKEY_HEX_RE,
21870
+ derivePubkeyFromPrivkey,
21705
21871
  verifyChallenge,
21706
21872
  ADMIN_STATE_UNAVAILABLE,
21707
21873
  buildAdminChallenge,