ravensafe-cli 1.0.0
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/Docs.md +262 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/RavenSafe.js +4 -0
- package/assets/ravensafe-cli-brand.png +0 -0
- package/cli.js +4 -0
- package/package.json +55 -0
- package/src/RavenSafe.js +753 -0
- package/src/config/index.js +25 -0
- package/src/core/address.js +67 -0
- package/src/core/network.js +13 -0
- package/src/core/scan.js +218 -0
- package/src/core/session-cache.js +158 -0
- package/src/explorer/zelcore.js +526 -0
- package/src/interactive/index.js +972 -0
- package/src/interactive/ui.js +441 -0
- package/src/ledger/index.js +405 -0
- package/src/tx/builder.js +448 -0
- package/src/tx/signer.js +205 -0
- package/tools/probe-ledger.js +90 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const config = require('../config');
|
|
4
|
+
|
|
5
|
+
const ANSI_PATTERN = /\u001b\[[0-9;]*m/g;
|
|
6
|
+
const USE_COLOR = Boolean(process.stdout.isTTY) &&
|
|
7
|
+
!process.env.NO_COLOR &&
|
|
8
|
+
process.env.TERM !== 'dumb';
|
|
9
|
+
const USE_UNICODE = Boolean(process.stdout.isTTY) &&
|
|
10
|
+
process.env.RAVENSAFE_ASCII !== '1' &&
|
|
11
|
+
process.env.TERM !== 'dumb';
|
|
12
|
+
|
|
13
|
+
const PALETTE = Object.freeze({
|
|
14
|
+
reset: 0,
|
|
15
|
+
bold: 1,
|
|
16
|
+
dim: 2,
|
|
17
|
+
cyan: 36,
|
|
18
|
+
blue: 34,
|
|
19
|
+
green: 32,
|
|
20
|
+
yellow: 33,
|
|
21
|
+
red: 31,
|
|
22
|
+
gray: 90,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const symbols = USE_UNICODE
|
|
26
|
+
? Object.freeze({
|
|
27
|
+
tl: '╭',
|
|
28
|
+
tr: '╮',
|
|
29
|
+
bl: '╰',
|
|
30
|
+
br: '╯',
|
|
31
|
+
h: '─',
|
|
32
|
+
v: '│',
|
|
33
|
+
prompt: '›',
|
|
34
|
+
info: 'i',
|
|
35
|
+
success: '✓',
|
|
36
|
+
warning: '!',
|
|
37
|
+
error: '×',
|
|
38
|
+
section: '◆',
|
|
39
|
+
bullet: '•',
|
|
40
|
+
frames: ['◐', '◓', '◑', '◒'],
|
|
41
|
+
})
|
|
42
|
+
: Object.freeze({
|
|
43
|
+
tl: '+',
|
|
44
|
+
tr: '+',
|
|
45
|
+
bl: '+',
|
|
46
|
+
br: '+',
|
|
47
|
+
h: '-',
|
|
48
|
+
v: '|',
|
|
49
|
+
prompt: '>',
|
|
50
|
+
info: 'i',
|
|
51
|
+
success: '+',
|
|
52
|
+
warning: '!',
|
|
53
|
+
error: 'x',
|
|
54
|
+
section: '*',
|
|
55
|
+
bullet: '-',
|
|
56
|
+
frames: ['-', '\\', '|', '/'],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function color(code, value) {
|
|
60
|
+
const text = String(value);
|
|
61
|
+
if (!USE_COLOR) {
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return `\u001b[${code}m${text}\u001b[0m`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const style = Object.freeze({
|
|
69
|
+
bold: value => color(PALETTE.bold, value),
|
|
70
|
+
dim: value => color(PALETTE.dim, value),
|
|
71
|
+
cyan: value => color(PALETTE.cyan, value),
|
|
72
|
+
blue: value => color(PALETTE.blue, value),
|
|
73
|
+
green: value => color(PALETTE.green, value),
|
|
74
|
+
yellow: value => color(PALETTE.yellow, value),
|
|
75
|
+
red: value => color(PALETTE.red, value),
|
|
76
|
+
gray: value => color(PALETTE.gray, value),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function stripAnsi(value) {
|
|
80
|
+
return String(value).replace(ANSI_PATTERN, '');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function visibleLength(value) {
|
|
84
|
+
return stripAnsi(value).length;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function terminalWidth() {
|
|
88
|
+
if (!process.stdout.isTTY || !Number.isSafeInteger(process.stdout.columns)) {
|
|
89
|
+
return 80;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return Math.max(40, process.stdout.columns);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function repeat(char, count) {
|
|
96
|
+
return char.repeat(Math.max(0, count));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function padRight(value, width) {
|
|
100
|
+
return `${value}${repeat(' ', width - visibleLength(value))}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function hardWrapWord(word, width) {
|
|
104
|
+
if (visibleLength(word) <= width) {
|
|
105
|
+
return [word];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (stripAnsi(word) !== word) {
|
|
109
|
+
return [word];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const chunks = [];
|
|
113
|
+
for (let index = 0; index < word.length; index += width) {
|
|
114
|
+
chunks.push(word.slice(index, index + width));
|
|
115
|
+
}
|
|
116
|
+
return chunks;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function wrapLine(line, width) {
|
|
120
|
+
const text = String(line);
|
|
121
|
+
if (text.length === 0) {
|
|
122
|
+
return [''];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const words = text.split(/\s+/);
|
|
126
|
+
const lines = [];
|
|
127
|
+
let current = '';
|
|
128
|
+
|
|
129
|
+
for (const word of words) {
|
|
130
|
+
const chunks = hardWrapWord(word, width);
|
|
131
|
+
for (const chunk of chunks) {
|
|
132
|
+
if (!current) {
|
|
133
|
+
current = chunk;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (visibleLength(current) + 1 + visibleLength(chunk) <= width) {
|
|
138
|
+
current = `${current} ${chunk}`;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
lines.push(current);
|
|
143
|
+
current = chunk;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (current) {
|
|
148
|
+
lines.push(current);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return lines.length > 0 ? lines : [''];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function normalizeLines(lines) {
|
|
155
|
+
if (Array.isArray(lines)) {
|
|
156
|
+
return lines.map(line => String(line));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return String(lines).split('\n');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function borderColor(colorName, value) {
|
|
163
|
+
if (colorName && style[colorName]) {
|
|
164
|
+
return style[colorName](value);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function box(lines, options = {}) {
|
|
171
|
+
const preferredWidth = options.width || 76;
|
|
172
|
+
const maxWidth = Math.min(92, terminalWidth() - 2);
|
|
173
|
+
const width = Math.max(34, Math.min(preferredWidth, maxWidth));
|
|
174
|
+
const contentWidth = width - 4;
|
|
175
|
+
const title = options.title ? ` ${options.title} ` : '';
|
|
176
|
+
const titleLength = visibleLength(title);
|
|
177
|
+
const titleLeft = title ? 2 : 0;
|
|
178
|
+
const horizontalWidth = width - 2;
|
|
179
|
+
const titleRight = title
|
|
180
|
+
? horizontalWidth - titleLeft - titleLength
|
|
181
|
+
: horizontalWidth;
|
|
182
|
+
const body = [];
|
|
183
|
+
const colorName = options.color || 'cyan';
|
|
184
|
+
const top = title
|
|
185
|
+
? `${borderColor(colorName, `${symbols.tl}${repeat(symbols.h, titleLeft)}`)}${style.bold(title)}${borderColor(colorName, `${repeat(symbols.h, titleRight)}${symbols.tr}`)}`
|
|
186
|
+
: borderColor(colorName, `${symbols.tl}${repeat(symbols.h, horizontalWidth)}${symbols.tr}`);
|
|
187
|
+
const bottom = borderColor(colorName, `${symbols.bl}${repeat(symbols.h, horizontalWidth)}${symbols.br}`);
|
|
188
|
+
|
|
189
|
+
for (const line of normalizeLines(lines)) {
|
|
190
|
+
for (const wrapped of wrapLine(line, contentWidth)) {
|
|
191
|
+
body.push(`${symbols.v} ${padRight(wrapped, contentWidth)} ${symbols.v}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return [
|
|
196
|
+
top,
|
|
197
|
+
...body.map(line => {
|
|
198
|
+
const left = borderColor(colorName, symbols.v);
|
|
199
|
+
const right = borderColor(colorName, symbols.v);
|
|
200
|
+
return `${left}${line.slice(symbols.v.length, -symbols.v.length)}${right}`;
|
|
201
|
+
}),
|
|
202
|
+
bottom,
|
|
203
|
+
].join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function printBox(lines, options = {}) {
|
|
207
|
+
console.log(box(lines, options));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function clearScreen() {
|
|
211
|
+
if (!process.stdout.isTTY) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
process.stdout.write('\u001b[2J\u001b[3J\u001b[H');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function sleep(ms) {
|
|
219
|
+
return new Promise(resolve => {
|
|
220
|
+
setTimeout(resolve, ms);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function bootSequence() {
|
|
225
|
+
if (!process.stdout.isTTY) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const steps = [
|
|
230
|
+
'Preparing guided mode',
|
|
231
|
+
'Loading safety-first interface',
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
for (const step of steps) {
|
|
235
|
+
for (let index = 0; index < 2; index += 1) {
|
|
236
|
+
const frame = symbols.frames[index % symbols.frames.length];
|
|
237
|
+
process.stdout.write(`\r${style.cyan(frame)} ${style.dim(step)} `);
|
|
238
|
+
await sleep(70);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
process.stdout.write('\r\u001b[2K');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function showStartup() {
|
|
246
|
+
clearScreen();
|
|
247
|
+
await bootSequence();
|
|
248
|
+
printBrandHeader();
|
|
249
|
+
printWelcomeCard();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function printBrandHeader() {
|
|
253
|
+
printBox([
|
|
254
|
+
style.bold('RavenSafe CLI'),
|
|
255
|
+
'Secure Ravencoin Ledger Helper',
|
|
256
|
+
style.dim('Ledger signs. Your seed never leaves the device.'),
|
|
257
|
+
], {
|
|
258
|
+
title: 'Guided Wallet Mode',
|
|
259
|
+
color: 'cyan',
|
|
260
|
+
width: 72,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function printWelcomeCard() {
|
|
265
|
+
const rvnDonation = config.branding.donations.rvn;
|
|
266
|
+
|
|
267
|
+
console.log('');
|
|
268
|
+
printBox([
|
|
269
|
+
'Scan balances, prepare receive addresses, and send RVN through a guided Ledger workflow.',
|
|
270
|
+
'This tool never asks for a seed phrase, mnemonic, private key, or passphrase.',
|
|
271
|
+
'',
|
|
272
|
+
`Support RVN donations: ${rvnDonation.address}`,
|
|
273
|
+
], {
|
|
274
|
+
title: 'Welcome',
|
|
275
|
+
color: 'blue',
|
|
276
|
+
width: 72,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function section(title, subtitle) {
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log(`${style.cyan(symbols.section)} ${style.bold(title)}`);
|
|
283
|
+
if (subtitle) {
|
|
284
|
+
console.log(style.dim(` ${subtitle}`));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function line(type, message) {
|
|
289
|
+
const colorByType = {
|
|
290
|
+
info: 'blue',
|
|
291
|
+
success: 'green',
|
|
292
|
+
warning: 'yellow',
|
|
293
|
+
error: 'red',
|
|
294
|
+
};
|
|
295
|
+
const symbolByType = {
|
|
296
|
+
info: symbols.info,
|
|
297
|
+
success: symbols.success,
|
|
298
|
+
warning: symbols.warning,
|
|
299
|
+
error: symbols.error,
|
|
300
|
+
};
|
|
301
|
+
const colorName = colorByType[type] || 'blue';
|
|
302
|
+
const marker = style[colorName](symbolByType[type] || symbols.info);
|
|
303
|
+
console.log(`${marker} ${message}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function info(message) {
|
|
307
|
+
line('info', message);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function success(message) {
|
|
311
|
+
line('success', message);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function warning(message) {
|
|
315
|
+
line('warning', message);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function error(message) {
|
|
319
|
+
line('error', message);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function successBox(title, lines) {
|
|
323
|
+
printBox(lines, {
|
|
324
|
+
title,
|
|
325
|
+
color: 'green',
|
|
326
|
+
width: 76,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function warningBox(title, lines) {
|
|
331
|
+
printBox(lines, {
|
|
332
|
+
title,
|
|
333
|
+
color: 'yellow',
|
|
334
|
+
width: 76,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function errorBox(title, lines) {
|
|
339
|
+
printBox(lines, {
|
|
340
|
+
title,
|
|
341
|
+
color: 'red',
|
|
342
|
+
width: 76,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function infoBox(title, lines) {
|
|
347
|
+
printBox(lines, {
|
|
348
|
+
title,
|
|
349
|
+
color: 'blue',
|
|
350
|
+
width: 76,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function prompt(label, hint) {
|
|
355
|
+
const suffix = hint ? ` ${style.dim(hint)}` : '';
|
|
356
|
+
return `${style.cyan(symbols.prompt)} ${label}${suffix}: `;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function optionRows(items) {
|
|
360
|
+
return items.map(item => {
|
|
361
|
+
const index = style.cyan(String(item.value).padStart(2, ' '));
|
|
362
|
+
const label = style.bold(item.label);
|
|
363
|
+
const description = item.description ? style.dim(` - ${item.description}`) : '';
|
|
364
|
+
return `${index}. ${label}${description}`;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function menu(title, items, footer) {
|
|
369
|
+
const lines = optionRows(items);
|
|
370
|
+
if (footer) {
|
|
371
|
+
lines.push('');
|
|
372
|
+
lines.push(style.dim(footer));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
printBox(lines, {
|
|
376
|
+
title,
|
|
377
|
+
color: 'cyan',
|
|
378
|
+
width: 72,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function keyValueLines(entries) {
|
|
383
|
+
const labelWidth = Math.max(...entries.map(entry => visibleLength(entry.label)));
|
|
384
|
+
return entries.map(entry => {
|
|
385
|
+
const label = style.dim(padRight(entry.label, labelWidth));
|
|
386
|
+
return `${label} ${entry.value}`;
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function keyValueBox(title, entries, options = {}) {
|
|
391
|
+
printBox(keyValueLines(entries), {
|
|
392
|
+
title,
|
|
393
|
+
color: options.color || 'blue',
|
|
394
|
+
width: options.width || 76,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function withSpinner(message, task) {
|
|
399
|
+
if (!process.stdout.isTTY) {
|
|
400
|
+
info(message);
|
|
401
|
+
return task();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let index = 0;
|
|
405
|
+
process.stdout.write(`${style.cyan(symbols.frames[0])} ${style.dim(message)}`);
|
|
406
|
+
const timer = setInterval(() => {
|
|
407
|
+
index = (index + 1) % symbols.frames.length;
|
|
408
|
+
process.stdout.write(`\r${style.cyan(symbols.frames[index])} ${style.dim(message)}`);
|
|
409
|
+
}, 90);
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
return await task();
|
|
413
|
+
} finally {
|
|
414
|
+
clearInterval(timer);
|
|
415
|
+
process.stdout.write('\r\u001b[2K');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
module.exports = {
|
|
420
|
+
box,
|
|
421
|
+
clearScreen,
|
|
422
|
+
error,
|
|
423
|
+
errorBox,
|
|
424
|
+
info,
|
|
425
|
+
infoBox,
|
|
426
|
+
keyValueBox,
|
|
427
|
+
keyValueLines,
|
|
428
|
+
menu,
|
|
429
|
+
optionRows,
|
|
430
|
+
printBox,
|
|
431
|
+
prompt,
|
|
432
|
+
section,
|
|
433
|
+
showStartup,
|
|
434
|
+
style,
|
|
435
|
+
success,
|
|
436
|
+
successBox,
|
|
437
|
+
symbols,
|
|
438
|
+
warning,
|
|
439
|
+
warningBox,
|
|
440
|
+
withSpinner,
|
|
441
|
+
};
|