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.
- package/dist/mcp-server.js +158 -32
- package/dist/wallet.js +175 -46
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -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.
|
|
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:
|
|
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.
|
|
1694
|
-
|
|
1695
|
-
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
1881
|
-
|
|
1882
|
-
|
|
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
|
|
2577
|
+
var handlerLoaders, TOOLS, server;
|
|
2442
2578
|
var init_mcp_server = __esm(() => {
|
|
2443
2579
|
init_run_command();
|
|
2444
2580
|
init_exit_codes();
|
|
2445
|
-
|
|
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:
|
|
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:
|
|
3086
|
-
version:
|
|
3087
|
-
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: ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|