midnight-wallet-cli 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -172,7 +172,7 @@ var init_network = __esm(() => {
172
172
  });
173
173
 
174
174
  // src/lib/constants.ts
175
- var GENESIS_SEED = "0000000000000000000000000000000000000000000000000000000000000001", NATIVE_TOKEN_TYPE = "0000000000000000000000000000000000000000000000000000000000000000", TOKEN_DECIMALS = 6, TOKEN_MULTIPLIER = 1e6, DUST_COST_OVERHEAD = 1000000000000n, DUST_FEE_BLOCKS_MARGIN = 5, SYNC_TIMEOUT_MS = 300000, PRE_SEND_SYNC_TIMEOUT_MS = 1e4, DUST_TIMEOUT_MS = 120000, PROOF_TIMEOUT_MS = 300000, BALANCE_CHECK_TIMEOUT_MS = 60000, TX_TTL_MINUTES = 10, MAX_RETRY_ATTEMPTS = 3, STALE_UTXO_ERROR_CODE = 115, MIDNIGHT_DIR = ".midnight", DEFAULT_WALLET_FILENAME = "wallet.json", DEFAULT_CONFIG_FILENAME = "config.json", DIR_MODE = 448, FILE_MODE = 384, LOCALNET_DIR_NAME = "localnet";
175
+ var GENESIS_SEED = "0000000000000000000000000000000000000000000000000000000000000001", NATIVE_TOKEN_TYPE = "0000000000000000000000000000000000000000000000000000000000000000", TOKEN_DECIMALS = 6, TOKEN_MULTIPLIER = 1e6, DUST_COST_OVERHEAD = 1000000000000n, DUST_FEE_BLOCKS_MARGIN = 5, SYNC_TIMEOUT_MS = 300000, PRE_SEND_SYNC_TIMEOUT_MS = 1e4, DUST_TIMEOUT_MS = 120000, PROOF_TIMEOUT_MS = 300000, BALANCE_CHECK_TIMEOUT_MS = 60000, TX_TTL_MINUTES = 10, MAX_RETRY_ATTEMPTS = 3, DUST_REGISTRATION_TIMEOUT_MS = 600000, DUST_REGISTRATION_RETRY_DELAY_MS = 15000, STALE_UTXO_ERROR_CODE = 115, MIDNIGHT_DIR = ".midnight", DEFAULT_WALLET_FILENAME = "wallet.json", DEFAULT_CONFIG_FILENAME = "config.json", DIR_MODE = 448, FILE_MODE = 384, LOCALNET_DIR_NAME = "localnet";
176
176
 
177
177
  // src/lib/cli-config.ts
178
178
  import * as fs from "fs";
@@ -1243,27 +1243,98 @@ function validateRecipientAddress(address, networkConfig) {
1243
1243
  ` + `Expected a bech32m address for network "${networkConfig.networkId}"`);
1244
1244
  }
1245
1245
  }
1246
+ function isTransactionRejectedError(err) {
1247
+ let current = err;
1248
+ while (current) {
1249
+ const msg = String(current?.message ?? "").toLowerCase();
1250
+ if (msg.includes("submission error"))
1251
+ return true;
1252
+ if (msg.includes("transaction") && msg.includes("invalid"))
1253
+ return true;
1254
+ if (msg.includes("138"))
1255
+ return true;
1256
+ const tag = current?._tag;
1257
+ if (tag === "TransactionInvalidError" || tag === "SubmissionError")
1258
+ return true;
1259
+ current = current.cause;
1260
+ }
1261
+ return false;
1262
+ }
1263
+ function formatElapsed(ms) {
1264
+ const totalSeconds = Math.round(ms / 1000);
1265
+ if (totalSeconds < 60)
1266
+ return `${totalSeconds}s`;
1267
+ const minutes = Math.floor(totalSeconds / 60);
1268
+ const seconds = totalSeconds % 60;
1269
+ return `${minutes}m ${seconds}s`;
1270
+ }
1271
+ async function submitDustRegistration(bundle, dustUtxos, dustReceiverAddress) {
1272
+ const ttl = new Date(Date.now() + TX_TTL_MINUTES * 60 * 1000);
1273
+ await bundle.facade.dust.waitForSyncedState();
1274
+ const unprovenTx = await bundle.facade.dust.createDustGenerationTransaction(new Date, ttl, dustUtxos, bundle.keystore.getPublicKey(), dustReceiverAddress);
1275
+ const intent = unprovenTx.intents?.get(1);
1276
+ if (!intent) {
1277
+ throw new Error("Dust generation intent not found on transaction");
1278
+ }
1279
+ const signature = bundle.keystore.signData(intent.signatureData(1));
1280
+ const signedTx = await bundle.facade.dust.addDustGenerationSignature(unprovenTx, signature);
1281
+ const finalized = await bundle.facade.finalizeTransaction(signedTx);
1282
+ return await bundle.facade.submitTransaction(finalized);
1283
+ }
1284
+ async function registerNightUtxos(bundle, dustUtxos, dustReceiverAddress, onStatus) {
1285
+ const startTime = Date.now();
1286
+ const deadline = startTime + DUST_REGISTRATION_TIMEOUT_MS;
1287
+ let lastError;
1288
+ const originalWarn = console.warn;
1289
+ const originalError = console.error;
1290
+ const hasRpcNoise = (args) => args.some((a) => String(a).includes("RPC-CORE"));
1291
+ const suppressRpcNoise = () => {
1292
+ console.warn = (...args) => {
1293
+ if (hasRpcNoise(args))
1294
+ return;
1295
+ originalWarn(...args);
1296
+ };
1297
+ console.error = (...args) => {
1298
+ if (hasRpcNoise(args))
1299
+ return;
1300
+ originalError(...args);
1301
+ };
1302
+ };
1303
+ const restoreConsole = () => {
1304
+ console.warn = originalWarn;
1305
+ console.error = originalError;
1306
+ };
1307
+ suppressRpcNoise();
1308
+ try {
1309
+ while (Date.now() < deadline) {
1310
+ try {
1311
+ return await submitDustRegistration(bundle, dustUtxos, dustReceiverAddress);
1312
+ } catch (err) {
1313
+ lastError = err;
1314
+ if (isTransactionRejectedError(err) && Date.now() + DUST_REGISTRATION_RETRY_DELAY_MS < deadline) {
1315
+ const elapsed = formatElapsed(Date.now() - startTime);
1316
+ onStatus?.(`Waiting for dust generation capacity (${elapsed} elapsed, ~5 min on fresh wallets)...`);
1317
+ await new Promise((resolve4) => setTimeout(resolve4, DUST_REGISTRATION_RETRY_DELAY_MS));
1318
+ continue;
1319
+ }
1320
+ throw err;
1321
+ }
1322
+ }
1323
+ throw lastError ?? new Error("Dust registration timed out");
1324
+ } finally {
1325
+ restoreConsole();
1326
+ }
1327
+ }
1246
1328
  async function ensureDust(bundle, onDust) {
1247
1329
  const state = await rx2.firstValueFrom(bundle.facade.state().pipe(rx2.filter((s) => s.isSynced)));
1248
1330
  const nightUtxos = state.unshielded.availableCoins.filter((coin) => coin.meta?.registeredForDustGeneration !== true);
1249
1331
  if (nightUtxos.length > 0) {
1250
1332
  onDust?.(`Registering ${nightUtxos.length} UTXO(s) for dust generation...`);
1251
- const ttl = new Date(Date.now() + TX_TTL_MINUTES * 60 * 1000);
1252
- const dustReceiverAddress = state.dust.dustAddress;
1253
1333
  const dustUtxos = nightUtxos.map((coin) => ({
1254
1334
  ...coin.utxo,
1255
1335
  ctime: new Date(coin.meta.ctime)
1256
1336
  }));
1257
- await bundle.facade.dust.waitForSyncedState();
1258
- const unprovenTx = await bundle.facade.dust.createDustGenerationTransaction(new Date, ttl, dustUtxos, bundle.keystore.getPublicKey(), dustReceiverAddress);
1259
- const intent = unprovenTx.intents?.get(1);
1260
- if (!intent) {
1261
- throw new Error("Dust generation intent not found on transaction");
1262
- }
1263
- const signature = bundle.keystore.signData(intent.signatureData(1));
1264
- const signedTx = await bundle.facade.dust.addDustGenerationSignature(unprovenTx, signature);
1265
- const finalized = await bundle.facade.finalizeTransaction(signedTx);
1266
- await bundle.facade.submitTransaction(finalized);
1337
+ await registerNightUtxos(bundle, dustUtxos, state.dust.dustAddress, onDust);
1267
1338
  } else if (state.dust.availableCoins.length > 0) {
1268
1339
  onDust?.("Dust available");
1269
1340
  return;
@@ -1274,7 +1345,7 @@ async function ensureDust(bundle, onDust) {
1274
1345
  await rx2.firstValueFrom(bundle.facade.state().pipe(rx2.throttleTime(5000), rx2.filter((s) => s.isSynced), rx2.filter((s) => s.dust.walletBalance(new Date) > 0n), rx2.timeout(DUST_TIMEOUT_MS)));
1275
1346
  onDust?.("Dust available");
1276
1347
  }
1277
- async function buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting) {
1348
+ async function buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting, onDust) {
1278
1349
  let lastError;
1279
1350
  for (let attempt = 1;attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
1280
1351
  try {
@@ -1311,6 +1382,12 @@ async function buildAndSubmitTransfer(bundle, recipientAddress, amount, onProvin
1311
1382
  if (isStaleUtxo && attempt < MAX_RETRY_ATTEMPTS) {
1312
1383
  continue;
1313
1384
  }
1385
+ const isDustInsufficient = err?.message?.toLowerCase().includes("not enough dust") || err?.message?.toLowerCase().includes("dust generated");
1386
+ if (isDustInsufficient && attempt < MAX_RETRY_ATTEMPTS) {
1387
+ onDust?.("Waiting for more dust to accumulate...");
1388
+ await new Promise((resolve4) => setTimeout(resolve4, DUST_REGISTRATION_RETRY_DELAY_MS));
1389
+ continue;
1390
+ }
1314
1391
  throw err;
1315
1392
  }
1316
1393
  }
@@ -1361,7 +1438,7 @@ async function executeTransfer(params) {
1361
1438
  await ensureDust(bundle, onDust);
1362
1439
  if (signal?.aborted)
1363
1440
  throw new Error("Operation cancelled");
1364
- const txHash = await buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting);
1441
+ const txHash = await buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting, onDust);
1365
1442
  return { txHash, amountMicroNight: amount };
1366
1443
  } finally {
1367
1444
  signal?.removeEventListener("abort", onAbort);
@@ -1465,7 +1542,13 @@ async function airdropCommand(args, signal) {
1465
1542
  process.stderr.write(`
1466
1543
  ` + divider() + `
1467
1544
  `);
1468
- process.stderr.write(dim(" Verify: midnight balance") + `
1545
+ process.stderr.write(dim(" Verify: midnight balance") + `
1546
+ `);
1547
+ process.stderr.write(dim(" Register dust: midnight dust register") + `
1548
+ `);
1549
+ process.stderr.write(dim(" Note: Dust generation takes a few minutes on a fresh wallet.") + `
1550
+ `);
1551
+ process.stderr.write(dim(" It will happen automatically on your first transfer.") + `
1469
1552
 
1470
1553
  `);
1471
1554
  } catch (err) {
@@ -1684,22 +1767,13 @@ async function dustRegister(bundle, networkName, jsonMode, signal, warningRef) {
1684
1767
  spinner.update("All UTXOs already registered, waiting for dust generation...");
1685
1768
  } else {
1686
1769
  spinner.update(`Registering ${nightUtxos.length} UTXO(s) for dust generation...`);
1687
- const ttl = new Date(Date.now() + TX_TTL_MINUTES * 60 * 1000);
1688
- const dustReceiverAddress = state.dust.dustAddress;
1689
1770
  const dustUtxos = nightUtxos.map((coin) => ({
1690
1771
  ...coin.utxo,
1691
1772
  ctime: new Date(coin.meta.ctime)
1692
1773
  }));
1693
- await bundle.facade.dust.waitForSyncedState();
1694
- const unprovenTx = await bundle.facade.dust.createDustGenerationTransaction(new Date, ttl, dustUtxos, bundle.keystore.getPublicKey(), dustReceiverAddress);
1695
- const intent = unprovenTx.intents?.get(1);
1696
- if (!intent) {
1697
- throw new Error("Dust generation intent not found on transaction");
1698
- }
1699
- const signature = bundle.keystore.signData(intent.signatureData(1));
1700
- const signedTx = await bundle.facade.dust.addDustGenerationSignature(unprovenTx, signature);
1701
- const finalized = await bundle.facade.finalizeTransaction(signedTx);
1702
- txHash = await bundle.facade.submitTransaction(finalized);
1774
+ txHash = await registerNightUtxos(bundle, dustUtxos, state.dust.dustAddress, (status) => {
1775
+ spinner.update(status);
1776
+ });
1703
1777
  spinner.update(`Registration submitted (${txHash.slice(0, 12)}...), waiting for dust...`);
1704
1778
  }
1705
1779
  if (signal?.aborted)
@@ -1795,6 +1869,7 @@ var init_dust = __esm(() => {
1795
1869
  init_wallet_config();
1796
1870
  init_resolve_network();
1797
1871
  init_facade();
1872
+ init_transfer();
1798
1873
  init_format();
1799
1874
  init_spinner();
1800
1875
  });
@@ -2218,7 +2293,6 @@ var init_localnet2 = __esm(() => {
2218
2293
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2219
2294
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2220
2295
  import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
2221
- import { createRequire } from "node:module";
2222
2296
 
2223
2297
  // src/lib/run-command.ts
2224
2298
  async function captureCommand(handler, args, signal) {
@@ -2290,10 +2364,62 @@ function classifyError(err) {
2290
2364
  }
2291
2365
  return { exitCode: EXIT_GENERAL_ERROR, errorCode: ERROR_CODES.UNKNOWN };
2292
2366
  }
2367
+ // package.json
2368
+ var package_default = {
2369
+ name: "midnight-wallet-cli",
2370
+ version: "0.1.7",
2371
+ type: "module",
2372
+ description: "Git-style CLI wallet for the Midnight blockchain",
2373
+ license: "Apache-2.0",
2374
+ bin: {
2375
+ midnight: "dist/wallet.js",
2376
+ mn: "dist/wallet.js",
2377
+ "midnight-wallet-cli": "dist/wallet.js",
2378
+ "midnight-wallet-mcp": "dist/mcp-server.js"
2379
+ },
2380
+ files: [
2381
+ "dist"
2382
+ ],
2383
+ scripts: {
2384
+ wallet: "tsx src/wallet.ts",
2385
+ build: 'bun build src/wallet.ts --outfile dist/wallet.js --target node --format esm --packages external --banner "#!/usr/bin/env node" && bun build src/mcp-server.ts --outfile dist/mcp-server.js --target node --format esm --packages external --banner "#!/usr/bin/env node"',
2386
+ mcp: "tsx src/mcp-server.ts",
2387
+ prepublishOnly: "npm run build && npm run test",
2388
+ test: "vitest run",
2389
+ "test:watch": "vitest",
2390
+ typecheck: "tsc --noEmit"
2391
+ },
2392
+ dependencies: {
2393
+ "@midnight-ntwrk/ledger-v7": "7.0.0",
2394
+ "@midnight-ntwrk/midnight-js-network-id": "3.0.0",
2395
+ "@midnight-ntwrk/midnight-js-types": "3.0.0",
2396
+ "@midnight-ntwrk/wallet-sdk-abstractions": "1.0.0",
2397
+ "@midnight-ntwrk/wallet-sdk-address-format": "3.0.0",
2398
+ "@midnight-ntwrk/wallet-sdk-dust-wallet": "1.0.0",
2399
+ "@midnight-ntwrk/wallet-sdk-facade": "1.0.0",
2400
+ "@midnight-ntwrk/wallet-sdk-hd": "3.0.0",
2401
+ "@midnight-ntwrk/wallet-sdk-shielded": "1.0.0",
2402
+ "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "1.0.0",
2403
+ "@modelcontextprotocol/sdk": "^1.27.1",
2404
+ "@scure/bip39": "^2.0.1",
2405
+ rxjs: "^7.8.1",
2406
+ ws: "^8.19.0"
2407
+ },
2408
+ devDependencies: {
2409
+ "@types/node": "^22.19.13",
2410
+ "@types/ws": "^8.18.1",
2411
+ tsx: "^4.21.0",
2412
+ typescript: "^5.9.3",
2413
+ vitest: "^3.2.4"
2414
+ }
2415
+ };
2416
+
2417
+ // src/lib/pkg.ts
2418
+ var PKG_NAME = package_default.name;
2419
+ var PKG_VERSION = package_default.version;
2420
+ var PKG_DESCRIPTION = package_default.description;
2293
2421
 
2294
2422
  // src/mcp-server.ts
2295
- var require2 = createRequire(import.meta.url);
2296
- var pkg = require2("../package.json");
2297
2423
  function buildArgs(command, params, subcommand) {
2298
2424
  const flags = { json: true };
2299
2425
  const positionals = [];
@@ -2646,7 +2772,7 @@ var TOOLS = [
2646
2772
  }
2647
2773
  }
2648
2774
  ];
2649
- var server = new Server({ name: "midnight-wallet-cli", version: pkg.version }, { capabilities: { tools: {} } });
2775
+ var server = new Server({ name: "midnight-wallet-cli", version: PKG_VERSION }, { capabilities: { tools: {} } });
2650
2776
  server.setRequestHandler(ListToolsRequestSchema, async () => {
2651
2777
  return {
2652
2778
  tools: TOOLS.map((t) => ({
package/dist/wallet.js CHANGED
@@ -69,7 +69,7 @@ function requireFlag(args, name, description) {
69
69
  }
70
70
 
71
71
  // src/lib/constants.ts
72
- var GENESIS_SEED = "0000000000000000000000000000000000000000000000000000000000000001", NATIVE_TOKEN_TYPE = "0000000000000000000000000000000000000000000000000000000000000000", TOKEN_DECIMALS = 6, TOKEN_MULTIPLIER = 1e6, DUST_COST_OVERHEAD = 1000000000000n, DUST_FEE_BLOCKS_MARGIN = 5, SYNC_TIMEOUT_MS = 300000, PRE_SEND_SYNC_TIMEOUT_MS = 1e4, DUST_TIMEOUT_MS = 120000, PROOF_TIMEOUT_MS = 300000, BALANCE_CHECK_TIMEOUT_MS = 60000, TX_TTL_MINUTES = 10, MAX_RETRY_ATTEMPTS = 3, STALE_UTXO_ERROR_CODE = 115, MIDNIGHT_DIR = ".midnight", DEFAULT_WALLET_FILENAME = "wallet.json", DEFAULT_CONFIG_FILENAME = "config.json", DIR_MODE = 448, FILE_MODE = 384, LOCALNET_DIR_NAME = "localnet";
72
+ var GENESIS_SEED = "0000000000000000000000000000000000000000000000000000000000000001", NATIVE_TOKEN_TYPE = "0000000000000000000000000000000000000000000000000000000000000000", TOKEN_DECIMALS = 6, TOKEN_MULTIPLIER = 1e6, DUST_COST_OVERHEAD = 1000000000000n, DUST_FEE_BLOCKS_MARGIN = 5, SYNC_TIMEOUT_MS = 300000, PRE_SEND_SYNC_TIMEOUT_MS = 1e4, DUST_TIMEOUT_MS = 120000, PROOF_TIMEOUT_MS = 300000, BALANCE_CHECK_TIMEOUT_MS = 60000, TX_TTL_MINUTES = 10, MAX_RETRY_ATTEMPTS = 3, DUST_REGISTRATION_TIMEOUT_MS = 600000, DUST_REGISTRATION_RETRY_DELAY_MS = 15000, STALE_UTXO_ERROR_CODE = 115, MIDNIGHT_DIR = ".midnight", DEFAULT_WALLET_FILENAME = "wallet.json", DEFAULT_CONFIG_FILENAME = "config.json", DIR_MODE = 448, FILE_MODE = 384, LOCALNET_DIR_NAME = "localnet";
73
73
 
74
74
  // src/ui/colors.ts
75
75
  function isColorEnabled() {
@@ -310,6 +310,68 @@ function writeJsonError(err, errorCode, exitCode) {
310
310
  }
311
311
  var captureTarget = null;
312
312
 
313
+ // package.json
314
+ var package_default;
315
+ var init_package = __esm(() => {
316
+ package_default = {
317
+ name: "midnight-wallet-cli",
318
+ version: "0.1.7",
319
+ type: "module",
320
+ description: "Git-style CLI wallet for the Midnight blockchain",
321
+ license: "Apache-2.0",
322
+ bin: {
323
+ midnight: "dist/wallet.js",
324
+ mn: "dist/wallet.js",
325
+ "midnight-wallet-cli": "dist/wallet.js",
326
+ "midnight-wallet-mcp": "dist/mcp-server.js"
327
+ },
328
+ files: [
329
+ "dist"
330
+ ],
331
+ scripts: {
332
+ wallet: "tsx src/wallet.ts",
333
+ build: 'bun build src/wallet.ts --outfile dist/wallet.js --target node --format esm --packages external --banner "#!/usr/bin/env node" && bun build src/mcp-server.ts --outfile dist/mcp-server.js --target node --format esm --packages external --banner "#!/usr/bin/env node"',
334
+ mcp: "tsx src/mcp-server.ts",
335
+ prepublishOnly: "npm run build && npm run test",
336
+ test: "vitest run",
337
+ "test:watch": "vitest",
338
+ typecheck: "tsc --noEmit"
339
+ },
340
+ dependencies: {
341
+ "@midnight-ntwrk/ledger-v7": "7.0.0",
342
+ "@midnight-ntwrk/midnight-js-network-id": "3.0.0",
343
+ "@midnight-ntwrk/midnight-js-types": "3.0.0",
344
+ "@midnight-ntwrk/wallet-sdk-abstractions": "1.0.0",
345
+ "@midnight-ntwrk/wallet-sdk-address-format": "3.0.0",
346
+ "@midnight-ntwrk/wallet-sdk-dust-wallet": "1.0.0",
347
+ "@midnight-ntwrk/wallet-sdk-facade": "1.0.0",
348
+ "@midnight-ntwrk/wallet-sdk-hd": "3.0.0",
349
+ "@midnight-ntwrk/wallet-sdk-shielded": "1.0.0",
350
+ "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "1.0.0",
351
+ "@modelcontextprotocol/sdk": "^1.27.1",
352
+ "@scure/bip39": "^2.0.1",
353
+ rxjs: "^7.8.1",
354
+ ws: "^8.19.0"
355
+ },
356
+ devDependencies: {
357
+ "@types/node": "^22.19.13",
358
+ "@types/ws": "^8.18.1",
359
+ tsx: "^4.21.0",
360
+ typescript: "^5.9.3",
361
+ vitest: "^3.2.4"
362
+ }
363
+ };
364
+ });
365
+
366
+ // src/lib/pkg.ts
367
+ var PKG_NAME, PKG_VERSION, PKG_DESCRIPTION;
368
+ var init_pkg = __esm(() => {
369
+ init_package();
370
+ PKG_NAME = package_default.name;
371
+ PKG_VERSION = package_default.version;
372
+ PKG_DESCRIPTION = package_default.description;
373
+ });
374
+
313
375
  // src/lib/run-command.ts
314
376
  async function captureCommand(handler, args, signal) {
315
377
  const chunks = [];
@@ -1430,27 +1492,98 @@ function validateRecipientAddress(address, networkConfig) {
1430
1492
  ` + `Expected a bech32m address for network "${networkConfig.networkId}"`);
1431
1493
  }
1432
1494
  }
1495
+ function isTransactionRejectedError(err) {
1496
+ let current = err;
1497
+ while (current) {
1498
+ const msg = String(current?.message ?? "").toLowerCase();
1499
+ if (msg.includes("submission error"))
1500
+ return true;
1501
+ if (msg.includes("transaction") && msg.includes("invalid"))
1502
+ return true;
1503
+ if (msg.includes("138"))
1504
+ return true;
1505
+ const tag = current?._tag;
1506
+ if (tag === "TransactionInvalidError" || tag === "SubmissionError")
1507
+ return true;
1508
+ current = current.cause;
1509
+ }
1510
+ return false;
1511
+ }
1512
+ function formatElapsed(ms) {
1513
+ const totalSeconds = Math.round(ms / 1000);
1514
+ if (totalSeconds < 60)
1515
+ return `${totalSeconds}s`;
1516
+ const minutes = Math.floor(totalSeconds / 60);
1517
+ const seconds = totalSeconds % 60;
1518
+ return `${minutes}m ${seconds}s`;
1519
+ }
1520
+ async function submitDustRegistration(bundle, dustUtxos, dustReceiverAddress) {
1521
+ const ttl = new Date(Date.now() + TX_TTL_MINUTES * 60 * 1000);
1522
+ await bundle.facade.dust.waitForSyncedState();
1523
+ const unprovenTx = await bundle.facade.dust.createDustGenerationTransaction(new Date, ttl, dustUtxos, bundle.keystore.getPublicKey(), dustReceiverAddress);
1524
+ const intent = unprovenTx.intents?.get(1);
1525
+ if (!intent) {
1526
+ throw new Error("Dust generation intent not found on transaction");
1527
+ }
1528
+ const signature = bundle.keystore.signData(intent.signatureData(1));
1529
+ const signedTx = await bundle.facade.dust.addDustGenerationSignature(unprovenTx, signature);
1530
+ const finalized = await bundle.facade.finalizeTransaction(signedTx);
1531
+ return await bundle.facade.submitTransaction(finalized);
1532
+ }
1533
+ async function registerNightUtxos(bundle, dustUtxos, dustReceiverAddress, onStatus) {
1534
+ const startTime = Date.now();
1535
+ const deadline = startTime + DUST_REGISTRATION_TIMEOUT_MS;
1536
+ let lastError;
1537
+ const originalWarn = console.warn;
1538
+ const originalError = console.error;
1539
+ const hasRpcNoise = (args) => args.some((a) => String(a).includes("RPC-CORE"));
1540
+ const suppressRpcNoise = () => {
1541
+ console.warn = (...args) => {
1542
+ if (hasRpcNoise(args))
1543
+ return;
1544
+ originalWarn(...args);
1545
+ };
1546
+ console.error = (...args) => {
1547
+ if (hasRpcNoise(args))
1548
+ return;
1549
+ originalError(...args);
1550
+ };
1551
+ };
1552
+ const restoreConsole = () => {
1553
+ console.warn = originalWarn;
1554
+ console.error = originalError;
1555
+ };
1556
+ suppressRpcNoise();
1557
+ try {
1558
+ while (Date.now() < deadline) {
1559
+ try {
1560
+ return await submitDustRegistration(bundle, dustUtxos, dustReceiverAddress);
1561
+ } catch (err) {
1562
+ lastError = err;
1563
+ if (isTransactionRejectedError(err) && Date.now() + DUST_REGISTRATION_RETRY_DELAY_MS < deadline) {
1564
+ const elapsed = formatElapsed(Date.now() - startTime);
1565
+ onStatus?.(`Waiting for dust generation capacity (${elapsed} elapsed, ~5 min on fresh wallets)...`);
1566
+ await new Promise((resolve4) => setTimeout(resolve4, DUST_REGISTRATION_RETRY_DELAY_MS));
1567
+ continue;
1568
+ }
1569
+ throw err;
1570
+ }
1571
+ }
1572
+ throw lastError ?? new Error("Dust registration timed out");
1573
+ } finally {
1574
+ restoreConsole();
1575
+ }
1576
+ }
1433
1577
  async function ensureDust(bundle, onDust) {
1434
1578
  const state = await rx2.firstValueFrom(bundle.facade.state().pipe(rx2.filter((s) => s.isSynced)));
1435
1579
  const nightUtxos = state.unshielded.availableCoins.filter((coin) => coin.meta?.registeredForDustGeneration !== true);
1436
1580
  if (nightUtxos.length > 0) {
1437
1581
  onDust?.(`Registering ${nightUtxos.length} UTXO(s) for dust generation...`);
1438
- const ttl = new Date(Date.now() + TX_TTL_MINUTES * 60 * 1000);
1439
- const dustReceiverAddress = state.dust.dustAddress;
1440
1582
  const dustUtxos = nightUtxos.map((coin) => ({
1441
1583
  ...coin.utxo,
1442
1584
  ctime: new Date(coin.meta.ctime)
1443
1585
  }));
1444
- await bundle.facade.dust.waitForSyncedState();
1445
- const unprovenTx = await bundle.facade.dust.createDustGenerationTransaction(new Date, ttl, dustUtxos, bundle.keystore.getPublicKey(), dustReceiverAddress);
1446
- const intent = unprovenTx.intents?.get(1);
1447
- if (!intent) {
1448
- throw new Error("Dust generation intent not found on transaction");
1449
- }
1450
- const signature = bundle.keystore.signData(intent.signatureData(1));
1451
- const signedTx = await bundle.facade.dust.addDustGenerationSignature(unprovenTx, signature);
1452
- const finalized = await bundle.facade.finalizeTransaction(signedTx);
1453
- await bundle.facade.submitTransaction(finalized);
1586
+ await registerNightUtxos(bundle, dustUtxos, state.dust.dustAddress, onDust);
1454
1587
  } else if (state.dust.availableCoins.length > 0) {
1455
1588
  onDust?.("Dust available");
1456
1589
  return;
@@ -1461,7 +1594,7 @@ async function ensureDust(bundle, onDust) {
1461
1594
  await rx2.firstValueFrom(bundle.facade.state().pipe(rx2.throttleTime(5000), rx2.filter((s) => s.isSynced), rx2.filter((s) => s.dust.walletBalance(new Date) > 0n), rx2.timeout(DUST_TIMEOUT_MS)));
1462
1595
  onDust?.("Dust available");
1463
1596
  }
1464
- async function buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting) {
1597
+ async function buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting, onDust) {
1465
1598
  let lastError;
1466
1599
  for (let attempt = 1;attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
1467
1600
  try {
@@ -1498,6 +1631,12 @@ async function buildAndSubmitTransfer(bundle, recipientAddress, amount, onProvin
1498
1631
  if (isStaleUtxo && attempt < MAX_RETRY_ATTEMPTS) {
1499
1632
  continue;
1500
1633
  }
1634
+ const isDustInsufficient = err?.message?.toLowerCase().includes("not enough dust") || err?.message?.toLowerCase().includes("dust generated");
1635
+ if (isDustInsufficient && attempt < MAX_RETRY_ATTEMPTS) {
1636
+ onDust?.("Waiting for more dust to accumulate...");
1637
+ await new Promise((resolve4) => setTimeout(resolve4, DUST_REGISTRATION_RETRY_DELAY_MS));
1638
+ continue;
1639
+ }
1501
1640
  throw err;
1502
1641
  }
1503
1642
  }
@@ -1548,7 +1687,7 @@ async function executeTransfer(params) {
1548
1687
  await ensureDust(bundle, onDust);
1549
1688
  if (signal?.aborted)
1550
1689
  throw new Error("Operation cancelled");
1551
- const txHash = await buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting);
1690
+ const txHash = await buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting, onDust);
1552
1691
  return { txHash, amountMicroNight: amount };
1553
1692
  } finally {
1554
1693
  signal?.removeEventListener("abort", onAbort);
@@ -1652,7 +1791,13 @@ async function airdropCommand(args, signal) {
1652
1791
  process.stderr.write(`
1653
1792
  ` + divider() + `
1654
1793
  `);
1655
- process.stderr.write(dim(" Verify: midnight balance") + `
1794
+ process.stderr.write(dim(" Verify: midnight balance") + `
1795
+ `);
1796
+ process.stderr.write(dim(" Register dust: midnight dust register") + `
1797
+ `);
1798
+ process.stderr.write(dim(" Note: Dust generation takes a few minutes on a fresh wallet.") + `
1799
+ `);
1800
+ process.stderr.write(dim(" It will happen automatically on your first transfer.") + `
1656
1801
 
1657
1802
  `);
1658
1803
  } catch (err) {
@@ -1871,22 +2016,13 @@ async function dustRegister(bundle, networkName, jsonMode, signal, warningRef) {
1871
2016
  spinner.update("All UTXOs already registered, waiting for dust generation...");
1872
2017
  } else {
1873
2018
  spinner.update(`Registering ${nightUtxos.length} UTXO(s) for dust generation...`);
1874
- const ttl = new Date(Date.now() + TX_TTL_MINUTES * 60 * 1000);
1875
- const dustReceiverAddress = state.dust.dustAddress;
1876
2019
  const dustUtxos = nightUtxos.map((coin) => ({
1877
2020
  ...coin.utxo,
1878
2021
  ctime: new Date(coin.meta.ctime)
1879
2022
  }));
1880
- await bundle.facade.dust.waitForSyncedState();
1881
- const unprovenTx = await bundle.facade.dust.createDustGenerationTransaction(new Date, ttl, dustUtxos, bundle.keystore.getPublicKey(), dustReceiverAddress);
1882
- const intent = unprovenTx.intents?.get(1);
1883
- if (!intent) {
1884
- throw new Error("Dust generation intent not found on transaction");
1885
- }
1886
- const signature = bundle.keystore.signData(intent.signatureData(1));
1887
- const signedTx = await bundle.facade.dust.addDustGenerationSignature(unprovenTx, signature);
1888
- const finalized = await bundle.facade.finalizeTransaction(signedTx);
1889
- txHash = await bundle.facade.submitTransaction(finalized);
2023
+ txHash = await registerNightUtxos(bundle, dustUtxos, state.dust.dustAddress, (status) => {
2024
+ spinner.update(status);
2025
+ });
1890
2026
  spinner.update(`Registration submitted (${txHash.slice(0, 12)}...), waiting for dust...`);
1891
2027
  }
1892
2028
  if (signal?.aborted)
@@ -1982,6 +2118,7 @@ var init_dust = __esm(() => {
1982
2118
  init_wallet_config();
1983
2119
  init_resolve_network();
1984
2120
  init_facade();
2121
+ init_transfer();
1985
2122
  init_format();
1986
2123
  init_spinner();
1987
2124
  });
@@ -2406,7 +2543,6 @@ var exports_mcp_server = {};
2406
2543
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2407
2544
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2408
2545
  import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
2409
- import { createRequire } from "node:module";
2410
2546
  function buildArgs(command, params, subcommand) {
2411
2547
  const flags = { json: true };
2412
2548
  const positionals = [];
@@ -2438,12 +2574,11 @@ async function main() {
2438
2574
  const transport = new StdioServerTransport;
2439
2575
  await server.connect(transport);
2440
2576
  }
2441
- var require2, pkg, handlerLoaders, TOOLS, server;
2577
+ var handlerLoaders, TOOLS, server;
2442
2578
  var init_mcp_server = __esm(() => {
2443
2579
  init_run_command();
2444
2580
  init_exit_codes();
2445
- require2 = createRequire(import.meta.url);
2446
- pkg = require2("../package.json");
2581
+ init_pkg();
2447
2582
  handlerLoaders = {
2448
2583
  generate: () => Promise.resolve().then(() => (init_generate(), exports_generate)),
2449
2584
  info: () => Promise.resolve().then(() => (init_info(), exports_info)),
@@ -2769,7 +2904,7 @@ var init_mcp_server = __esm(() => {
2769
2904
  }
2770
2905
  }
2771
2906
  ];
2772
- server = new Server({ name: "midnight-wallet-cli", version: pkg.version }, { capabilities: { tools: {} } });
2907
+ server = new Server({ name: "midnight-wallet-cli", version: PKG_VERSION }, { capabilities: { tools: {} } });
2773
2908
  server.setRequestHandler(ListToolsRequestSchema, async () => {
2774
2909
  return {
2775
2910
  tools: TOOLS.map((t) => ({
@@ -3008,7 +3143,6 @@ __export(exports_help, {
3008
3143
  default: () => helpCommand,
3009
3144
  COMMAND_SPECS: () => COMMAND_SPECS
3010
3145
  });
3011
- import { createRequire as createRequire2 } from "node:module";
3012
3146
  function buildRightColumn() {
3013
3147
  const lines = [];
3014
3148
  for (const wl of WORDMARK_BIG) {
@@ -3078,13 +3212,11 @@ function printCommandHelp(spec) {
3078
3212
  }
3079
3213
  }
3080
3214
  function outputJsonManifest() {
3081
- const require3 = createRequire2(import.meta.url);
3082
- const pkg2 = require3("../../package.json");
3083
3215
  const manifest = {
3084
3216
  cli: {
3085
- name: pkg2.name,
3086
- version: pkg2.version,
3087
- description: pkg2.description,
3217
+ name: PKG_NAME,
3218
+ version: PKG_VERSION,
3219
+ description: PKG_DESCRIPTION,
3088
3220
  bin: ["midnight", "mn"]
3089
3221
  },
3090
3222
  globalFlags: [
@@ -3106,13 +3238,11 @@ function outputJsonManifest() {
3106
3238
  writeJsonResult(manifest);
3107
3239
  }
3108
3240
  function printAgentManual() {
3109
- const require3 = createRequire2(import.meta.url);
3110
- const pkg2 = require3("../../package.json");
3111
3241
  const manual = `
3112
3242
  MIDNIGHT CLI — AI Agent & MCP Reference
3113
3243
  ========================================
3114
3244
 
3115
- Version: ${pkg2.version}
3245
+ Version: ${PKG_VERSION}
3116
3246
 
3117
3247
  STRUCTURED JSON OUTPUT
3118
3248
  ──────────────────────
@@ -3251,6 +3381,7 @@ var init_help = __esm(() => {
3251
3381
  init_format();
3252
3382
  init_animate();
3253
3383
  init_art();
3384
+ init_pkg();
3254
3385
  COMMAND_SPECS = [
3255
3386
  {
3256
3387
  name: "generate",
@@ -3494,7 +3625,7 @@ var init_help = __esm(() => {
3494
3625
  // src/wallet.ts
3495
3626
  init_format();
3496
3627
  init_exit_codes();
3497
- import { createRequire as createRequire3 } from "node:module";
3628
+ init_pkg();
3498
3629
  if (process.argv.includes("--mcp")) {
3499
3630
  await Promise.resolve().then(() => (init_mcp_server(), exports_mcp_server));
3500
3631
  } else {
@@ -3505,9 +3636,7 @@ if (process.argv.includes("--mcp")) {
3505
3636
  const args = parseArgs();
3506
3637
  const jsonMode = hasFlag(args, "json");
3507
3638
  if (hasFlag(args, "version") || hasFlag(args, "v")) {
3508
- const require3 = createRequire3(import.meta.url);
3509
- const { version } = require3("../package.json");
3510
- process.stdout.write(version + `
3639
+ process.stdout.write(PKG_VERSION + `
3511
3640
  `);
3512
3641
  process.exit(0);
3513
3642
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "midnight-wallet-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "Git-style CLI wallet for the Midnight blockchain",
6
6
  "license": "Apache-2.0",