midnight-wallet-cli 0.1.6 → 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 +104 -29
- package/dist/wallet.js +104 -29
- 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
|
});
|
|
@@ -2292,7 +2367,7 @@ function classifyError(err) {
|
|
|
2292
2367
|
// package.json
|
|
2293
2368
|
var package_default = {
|
|
2294
2369
|
name: "midnight-wallet-cli",
|
|
2295
|
-
version: "0.1.
|
|
2370
|
+
version: "0.1.7",
|
|
2296
2371
|
type: "module",
|
|
2297
2372
|
description: "Git-style CLI wallet for the Midnight blockchain",
|
|
2298
2373
|
license: "Apache-2.0",
|
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() {
|
|
@@ -315,7 +315,7 @@ var package_default;
|
|
|
315
315
|
var init_package = __esm(() => {
|
|
316
316
|
package_default = {
|
|
317
317
|
name: "midnight-wallet-cli",
|
|
318
|
-
version: "0.1.
|
|
318
|
+
version: "0.1.7",
|
|
319
319
|
type: "module",
|
|
320
320
|
description: "Git-style CLI wallet for the Midnight blockchain",
|
|
321
321
|
license: "Apache-2.0",
|
|
@@ -1492,27 +1492,98 @@ function validateRecipientAddress(address, networkConfig) {
|
|
|
1492
1492
|
` + `Expected a bech32m address for network "${networkConfig.networkId}"`);
|
|
1493
1493
|
}
|
|
1494
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
|
+
}
|
|
1495
1577
|
async function ensureDust(bundle, onDust) {
|
|
1496
1578
|
const state = await rx2.firstValueFrom(bundle.facade.state().pipe(rx2.filter((s) => s.isSynced)));
|
|
1497
1579
|
const nightUtxos = state.unshielded.availableCoins.filter((coin) => coin.meta?.registeredForDustGeneration !== true);
|
|
1498
1580
|
if (nightUtxos.length > 0) {
|
|
1499
1581
|
onDust?.(`Registering ${nightUtxos.length} UTXO(s) for dust generation...`);
|
|
1500
|
-
const ttl = new Date(Date.now() + TX_TTL_MINUTES * 60 * 1000);
|
|
1501
|
-
const dustReceiverAddress = state.dust.dustAddress;
|
|
1502
1582
|
const dustUtxos = nightUtxos.map((coin) => ({
|
|
1503
1583
|
...coin.utxo,
|
|
1504
1584
|
ctime: new Date(coin.meta.ctime)
|
|
1505
1585
|
}));
|
|
1506
|
-
await bundle.
|
|
1507
|
-
const unprovenTx = await bundle.facade.dust.createDustGenerationTransaction(new Date, ttl, dustUtxos, bundle.keystore.getPublicKey(), dustReceiverAddress);
|
|
1508
|
-
const intent = unprovenTx.intents?.get(1);
|
|
1509
|
-
if (!intent) {
|
|
1510
|
-
throw new Error("Dust generation intent not found on transaction");
|
|
1511
|
-
}
|
|
1512
|
-
const signature = bundle.keystore.signData(intent.signatureData(1));
|
|
1513
|
-
const signedTx = await bundle.facade.dust.addDustGenerationSignature(unprovenTx, signature);
|
|
1514
|
-
const finalized = await bundle.facade.finalizeTransaction(signedTx);
|
|
1515
|
-
await bundle.facade.submitTransaction(finalized);
|
|
1586
|
+
await registerNightUtxos(bundle, dustUtxos, state.dust.dustAddress, onDust);
|
|
1516
1587
|
} else if (state.dust.availableCoins.length > 0) {
|
|
1517
1588
|
onDust?.("Dust available");
|
|
1518
1589
|
return;
|
|
@@ -1523,7 +1594,7 @@ async function ensureDust(bundle, onDust) {
|
|
|
1523
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)));
|
|
1524
1595
|
onDust?.("Dust available");
|
|
1525
1596
|
}
|
|
1526
|
-
async function buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting) {
|
|
1597
|
+
async function buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting, onDust) {
|
|
1527
1598
|
let lastError;
|
|
1528
1599
|
for (let attempt = 1;attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
|
|
1529
1600
|
try {
|
|
@@ -1560,6 +1631,12 @@ async function buildAndSubmitTransfer(bundle, recipientAddress, amount, onProvin
|
|
|
1560
1631
|
if (isStaleUtxo && attempt < MAX_RETRY_ATTEMPTS) {
|
|
1561
1632
|
continue;
|
|
1562
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
|
+
}
|
|
1563
1640
|
throw err;
|
|
1564
1641
|
}
|
|
1565
1642
|
}
|
|
@@ -1610,7 +1687,7 @@ async function executeTransfer(params) {
|
|
|
1610
1687
|
await ensureDust(bundle, onDust);
|
|
1611
1688
|
if (signal?.aborted)
|
|
1612
1689
|
throw new Error("Operation cancelled");
|
|
1613
|
-
const txHash = await buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting);
|
|
1690
|
+
const txHash = await buildAndSubmitTransfer(bundle, recipientAddress, amount, onProving, onSubmitting, onDust);
|
|
1614
1691
|
return { txHash, amountMicroNight: amount };
|
|
1615
1692
|
} finally {
|
|
1616
1693
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -1714,7 +1791,13 @@ async function airdropCommand(args, signal) {
|
|
|
1714
1791
|
process.stderr.write(`
|
|
1715
1792
|
` + divider() + `
|
|
1716
1793
|
`);
|
|
1717
|
-
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.") + `
|
|
1718
1801
|
|
|
1719
1802
|
`);
|
|
1720
1803
|
} catch (err) {
|
|
@@ -1933,22 +2016,13 @@ async function dustRegister(bundle, networkName, jsonMode, signal, warningRef) {
|
|
|
1933
2016
|
spinner.update("All UTXOs already registered, waiting for dust generation...");
|
|
1934
2017
|
} else {
|
|
1935
2018
|
spinner.update(`Registering ${nightUtxos.length} UTXO(s) for dust generation...`);
|
|
1936
|
-
const ttl = new Date(Date.now() + TX_TTL_MINUTES * 60 * 1000);
|
|
1937
|
-
const dustReceiverAddress = state.dust.dustAddress;
|
|
1938
2019
|
const dustUtxos = nightUtxos.map((coin) => ({
|
|
1939
2020
|
...coin.utxo,
|
|
1940
2021
|
ctime: new Date(coin.meta.ctime)
|
|
1941
2022
|
}));
|
|
1942
|
-
await bundle.
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
if (!intent) {
|
|
1946
|
-
throw new Error("Dust generation intent not found on transaction");
|
|
1947
|
-
}
|
|
1948
|
-
const signature = bundle.keystore.signData(intent.signatureData(1));
|
|
1949
|
-
const signedTx = await bundle.facade.dust.addDustGenerationSignature(unprovenTx, signature);
|
|
1950
|
-
const finalized = await bundle.facade.finalizeTransaction(signedTx);
|
|
1951
|
-
txHash = await bundle.facade.submitTransaction(finalized);
|
|
2023
|
+
txHash = await registerNightUtxos(bundle, dustUtxos, state.dust.dustAddress, (status) => {
|
|
2024
|
+
spinner.update(status);
|
|
2025
|
+
});
|
|
1952
2026
|
spinner.update(`Registration submitted (${txHash.slice(0, 12)}...), waiting for dust...`);
|
|
1953
2027
|
}
|
|
1954
2028
|
if (signal?.aborted)
|
|
@@ -2044,6 +2118,7 @@ var init_dust = __esm(() => {
|
|
|
2044
2118
|
init_wallet_config();
|
|
2045
2119
|
init_resolve_network();
|
|
2046
2120
|
init_facade();
|
|
2121
|
+
init_transfer();
|
|
2047
2122
|
init_format();
|
|
2048
2123
|
init_spinner();
|
|
2049
2124
|
});
|