3 Commits

5 changed files with 37 additions and 13 deletions

View File

@@ -57,7 +57,7 @@ npx tsx src/index.ts # TUI
xo-cli mnemonic create
# Import an existing mnemonic seed phrase
xo-cli mnemonic import page pencil stock planet limb cluster assault speak off joke private pioneer
xo-cli mnemonic import oven crop same above under tower promote decrease vocal pretty require slow
# List mnemonic basenames (use with -m)
xo-cli mnemonic list

View File

@@ -7,9 +7,9 @@ import { z } from "zod";
/**
* Converts the CLI args to a key-value object and return the options object along with the other arguments still in the array.\
* eg: `xo-cli mnemonic create page pencil stock planet limb cluster assault speak off joke private pioneer -v -o mnemonic.txt` will return:
* eg: `xo-cli mnemonic create oven crop same above under tower promote decrease vocal pretty require slow -v -o mnemonic.txt` will return:
* {
* args: ["mnemonic", "create", "page", "pencil", "stock", "planet", "limb", "cluster", "assault", "speak", "off", "joke", "private", "pioneer"],
* args: ["mnemonic", "create", "oven", "crop", "same", "above", "under", "tower", "promote", "decrease", "vocal", "pretty", "require", "slow"],
* options: {
* output: "mnemonic.txt",
* verbose: "true",

View File

@@ -19,7 +19,7 @@ import path from 'path';
import { createMnemonicFile } from '../../cli/mnemonic.js';
import { getMnemonicsDir } from '../../utils/paths.js';
import { BCHMnemonicURL } from '../../utils/bch-mnemonic-url.js';
import { encodeBip39Mnemonic } from '@bitauth/libauth';
import { encodeBip39Mnemonic, generateBip39Mnemonic } from '@bitauth/libauth';
/**
* Status message type.
@@ -41,7 +41,7 @@ interface MnemonicFileEntry {
* Focus sections the user can tab between.
* When saved wallets exist the file list is shown first.
*/
type FocusSection = 'files' | 'input' | 'saveCheckbox' | 'button';
type FocusSection = 'files' | 'input' | 'saveCheckbox' | 'generateRandomSeed' | 'button';
/**
* Reads mnemonic-* files from ~/.config/xo-cli/mnemonics/ (same as xo-cli),
@@ -117,8 +117,8 @@ export function SeedInputScreen(): React.ReactElement {
* The ordered list of focusable sections (files section only when entries exist).
*/
const focusSections: FocusSection[] = mnemonicFiles.length > 0
? ['files', 'input', 'saveCheckbox', 'button']
: ['input', 'saveCheckbox', 'button'];
? ['files', 'input', 'generateRandomSeed', 'saveCheckbox', 'button']
: ['input', 'generateRandomSeed', 'saveCheckbox', 'button'];
/**
* Shows a status message with the given type.
@@ -202,7 +202,7 @@ export function SeedInputScreen(): React.ReactElement {
}, [mnemonicFiles, doInitialize]);
// Keyboard navigation
useBlockableInput((_input, key) => {
useBlockableInput((input, key) => {
if (isSubmitting) return;
// Tab / Shift-Tab to cycle focus sections
@@ -219,7 +219,7 @@ export function SeedInputScreen(): React.ReactElement {
// Space or Enter toggles "save mnemonic" when that row is focused
if (focusedSection === 'saveCheckbox') {
if (_input === ' ' || key.return) {
if (input === ' ' || key.return) {
setSaveMnemonicChecked((v) => !v);
return;
}
@@ -241,6 +241,18 @@ export function SeedInputScreen(): React.ReactElement {
}
}
// Ctrl-R generates a random seed phrase and fills it in the input
if (key.ctrl && input === 'r') {
setSeedPhrase(generateBip39Mnemonic());
return;
}
// If pressing enter while the generate random seed section is focused, generate a random seed and fill it in the input
if (key.return && focusedSection === 'generateRandomSeed') {
setSeedPhrase(generateBip39Mnemonic());
return;
}
// Enter on button submits manual seed
if (key.return && focusedSection === 'button') {
handleSubmit();
@@ -358,6 +370,19 @@ export function SeedInputScreen(): React.ReactElement {
/>
</Box>
{/* Generate random seed phrase and fill in the input */}
<Box marginTop={1}>
<Box
paddingX={1}
paddingY={0}
backgroundColor={focusedSection === 'generateRandomSeed' ? colors.focus : colors.bgSelected}
>
<Text color={focusedSection === 'generateRandomSeed' ? colors.bg : colors.text} bold>Generate Random Seed</Text>
</Box>
<Text color={colors.textMuted}> (Ctrl-R)</Text>
</Box>
{/* Save mnemonic checkbox (manual entry only; applies on Continue) */}
<Box
marginTop={1}

View File

@@ -19,8 +19,7 @@ import {
import { BCHMnemonicURL } from "../../src/utils/bch-mnemonic-url";
const TEST_SEED =
"page pencil stock planet limb cluster assault speak off joke private pioneer";
"oven crop same above under tower promote decrease vocal pretty require slow";
describe("mnemonic utilities", () => {
let tempDir: string;
@@ -54,7 +53,7 @@ describe("mnemonic utilities", () => {
test("creates a mnemonic file with auto-generated name", () => {
const filename = createMnemonicFile(tempDir, TEST_SEED);
expect(filename).toMatch(/^mnemonic-page$/);
expect(filename).toMatch(/^mnemonic-oven$/);
expect(existsSync(path.join(tempDir, filename))).toBe(true);
});

View File

@@ -18,7 +18,7 @@ import { MockRatesService } from "./rates-service";
import { RatesService } from "../../../src/services/rates";
export const DEFAULT_SEED =
"page pencil stock planet limb cluster assault speak off joke private pioneer";
"oven crop same above under tower promote decrease vocal pretty require slow";
/**
* Options for creating a fake resource (UTXO) in tests.