Compare commits
7 Commits
a0d9775015
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
d45f1ddeb3
|
|||
| bcc3277cb9 | |||
|
b2ccff5b19
|
|||
| 12b7bde74f | |||
| 42d23fa35e | |||
| b6ee25d1dd | |||
| b4d82b8b1f |
40
package-lock.json
generated
40
package-lock.json
generated
@@ -15,7 +15,7 @@
|
|||||||
"@xo-cash/crypto": "^0.0.1",
|
"@xo-cash/crypto": "^0.0.1",
|
||||||
"@xo-cash/engine": "file:../engine",
|
"@xo-cash/engine": "file:../engine",
|
||||||
"@xo-cash/state": "file:../state",
|
"@xo-cash/state": "file:../state",
|
||||||
"@xo-cash/templates": "^0.0.1",
|
"@xo-cash/templates": "file:../templates",
|
||||||
"@xo-cash/types": "^0.0.1",
|
"@xo-cash/types": "^0.0.1",
|
||||||
"better-sqlite3": "^12.6.2",
|
"better-sqlite3": "^12.6.2",
|
||||||
"clipboardy": "^5.1.0",
|
"clipboardy": "^5.1.0",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"@electrum-cash/network": "^4.2.2",
|
"@electrum-cash/network": "^4.2.2",
|
||||||
"@electrum-cash/protocol": "^2.3.1",
|
"@electrum-cash/protocol": "^2.3.1",
|
||||||
"@electrum-cash/servers": "^3.1.0",
|
"@electrum-cash/servers": "^3.1.0",
|
||||||
"@xo-cash/crypto": "0.0.1",
|
"@xo-cash/crypto": "file:../crypto",
|
||||||
"@xo-cash/primitives": "0.0.1",
|
"@xo-cash/primitives": "0.0.1",
|
||||||
"@xo-cash/state": "0.0.2",
|
"@xo-cash/state": "0.0.2",
|
||||||
"@xo-cash/templates": "0.0.1",
|
"@xo-cash/templates": "0.0.1",
|
||||||
@@ -113,6 +113,33 @@
|
|||||||
"vitest": "^4.0.17"
|
"vitest": "^4.0.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"../templates": {
|
||||||
|
"name": "@xo-cash/templates",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@xo-cash/types": "0.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@chalp/eslint-airbnb": "^1.3.0",
|
||||||
|
"@generalprotocols/cspell-dictionary": "^1.0.1",
|
||||||
|
"@stylistic/eslint-plugin": "^5.7.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||||
|
"@typescript-eslint/parser": "^8.53.1",
|
||||||
|
"@vitest/coverage-v8": "^4.0.17",
|
||||||
|
"@viz-kit/esbuild-analyzer": "^1.0.0",
|
||||||
|
"@xo-cash/eslint-config": "1.0.1",
|
||||||
|
"cspell": "^9.6.0",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"tsdown": "^0.20.0-beta.4",
|
||||||
|
"typedoc": "^0.28.16",
|
||||||
|
"typedoc-plugin-coverage": "^4.0.2",
|
||||||
|
"typescript": "^5.3.2",
|
||||||
|
"typescript-eslint": "^8.53.1",
|
||||||
|
"vitest": "^4.0.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@alcalzone/ansi-tokenize": {
|
"node_modules/@alcalzone/ansi-tokenize": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.4.tgz",
|
||||||
@@ -929,13 +956,8 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@xo-cash/templates": {
|
"node_modules/@xo-cash/templates": {
|
||||||
"version": "0.0.1",
|
"resolved": "../templates",
|
||||||
"resolved": "https://registry.npmjs.org/@xo-cash/templates/-/templates-0.0.1.tgz",
|
"link": true
|
||||||
"integrity": "sha512-v5f0YeH9Bw6lNThdE0fI878T4L2jbM8RI1quxdKxnvqHn9hu2jzebqvveEB2TfJWG3sP1GpE1go0Yn87R4sXfw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@xo-cash/types": "0.0.1"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/@xo-cash/types": {
|
"node_modules/@xo-cash/types": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"@xo-cash/crypto": "^0.0.1",
|
"@xo-cash/crypto": "^0.0.1",
|
||||||
"@xo-cash/engine": "file:../engine",
|
"@xo-cash/engine": "file:../engine",
|
||||||
"@xo-cash/state": "file:../state",
|
"@xo-cash/state": "file:../state",
|
||||||
"@xo-cash/templates": "^0.0.1",
|
"@xo-cash/templates": "file:../templates",
|
||||||
"@xo-cash/types": "^0.0.1",
|
"@xo-cash/types": "^0.0.1",
|
||||||
"better-sqlite3": "^12.6.2",
|
"better-sqlite3": "^12.6.2",
|
||||||
"clipboardy": "^5.1.0",
|
"clipboardy": "^5.1.0",
|
||||||
|
|||||||
23
readme.md
23
readme.md
@@ -9,7 +9,7 @@ mkdir xo-terminal && cd xo-terminal
|
|||||||
|
|
||||||
# ----- Start Engine Setup -----
|
# ----- Start Engine Setup -----
|
||||||
# Clone the Engine Repo (Note, this uses harvey's fork of the engine repo to access the cli-test branch)
|
# Clone the Engine Repo (Note, this uses harvey's fork of the engine repo to access the cli-test branch)
|
||||||
git clone git@gitlab.com:Harvmaster/engine.git
|
git clone https://gitlab.com/Harvmaster/engine.git
|
||||||
|
|
||||||
# Move into teh engine directory
|
# Move into teh engine directory
|
||||||
cd engine
|
cd engine
|
||||||
@@ -29,7 +29,7 @@ cd ..
|
|||||||
|
|
||||||
# ----- Start State Setup -----
|
# ----- Start State Setup -----
|
||||||
# Clone the State Repo
|
# Clone the State Repo
|
||||||
git clone git@gitlab.com:Harvmaster/state.git
|
git clone https://gitlab.com/Harvmaster/state.git
|
||||||
|
|
||||||
# Move into the state directory
|
# Move into the state directory
|
||||||
cd state
|
cd state
|
||||||
@@ -46,9 +46,26 @@ npm run build
|
|||||||
# Move back to the top level directory
|
# Move back to the top level directory
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
# ----- Start Template Setup ----
|
||||||
|
# Clone the Template repo
|
||||||
|
git clone https://gitlab.com/Harvmaster/templates.git
|
||||||
|
|
||||||
|
# Move into themplates directory
|
||||||
|
cd templates
|
||||||
|
|
||||||
|
# Install deps
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
#build the templates
|
||||||
|
npm run build
|
||||||
|
# ----- End Templates Setup ----
|
||||||
|
|
||||||
|
# Move back to the top level directory
|
||||||
|
cd ..
|
||||||
|
|
||||||
# ----- Start CLI Setup -----
|
# ----- Start CLI Setup -----
|
||||||
# Clone the CLI Repo
|
# Clone the CLI Repo
|
||||||
git clone git@git.harvmaster.com:Harvmaster/xo-cli.git
|
git clone https://git.harvmaster.com/Harvmaster/xo-cli.git
|
||||||
|
|
||||||
# Move into the cli directory
|
# Move into the cli directory
|
||||||
cd xo-cli
|
cd xo-cli
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ npx tsx src/index.ts # TUI
|
|||||||
xo-cli mnemonic create
|
xo-cli mnemonic create
|
||||||
|
|
||||||
# Import an existing mnemonic seed phrase
|
# 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)
|
# List mnemonic basenames (use with -m)
|
||||||
xo-cli mnemonic list
|
xo-cli mnemonic list
|
||||||
|
|||||||
@@ -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.\
|
* 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: {
|
* options: {
|
||||||
* output: "mnemonic.txt",
|
* output: "mnemonic.txt",
|
||||||
* verbose: "true",
|
* verbose: "true",
|
||||||
|
|||||||
@@ -83,20 +83,33 @@ export class AppService extends EventEmitter<AppEventMap> {
|
|||||||
// Import the default P2PKH template
|
// Import the default P2PKH template
|
||||||
const { templateIdentifier } = await engine.importTemplate(p2pkhTemplate);
|
const { templateIdentifier } = await engine.importTemplate(p2pkhTemplate);
|
||||||
|
|
||||||
engine
|
// engine
|
||||||
.subscribeToLockingBytecodesForTemplate(templateIdentifier)
|
// .subscribeToLockingBytecodesForTemplate(templateIdentifier)
|
||||||
.catch((err) =>
|
// .catch((err) =>
|
||||||
console.error(
|
// console.error(
|
||||||
`Error subscribing to locking bytecodes for template ${templateIdentifier}: ${err}`,
|
// `Error subscribing to locking bytecodes for template ${templateIdentifier}: ${err}`,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
engine
|
// engine
|
||||||
.updateUnspentOutputsForTemplate(templateIdentifier)
|
// .updateUnspentOutputsForTemplate(templateIdentifier)
|
||||||
.catch((err) =>
|
// .catch((err) =>
|
||||||
console.error(
|
// console.error(
|
||||||
`Error updating unspent outputs for template ${templateIdentifier}: ${err}`,
|
// `Error updating unspent outputs for template ${templateIdentifier}: ${err}`,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
|
|
||||||
|
// Update all the unspents for every template, and subscribe to the locking bytecodes for changes
|
||||||
|
// TODO: Remove the above lines that do the same thing. Minimising changes for BLISS.
|
||||||
|
const updateTemplates = async () => {
|
||||||
|
const templates = await engine.listImportedTemplates();
|
||||||
|
|
||||||
|
templates.forEach(async (template) => {
|
||||||
|
// engine.updateUnspentOutputsForTemplate(generateTemplateIdentifier(template));
|
||||||
|
engine.subscribeToLockingBytecodesForTemplate(generateTemplateIdentifier(template));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTemplates();
|
||||||
|
|
||||||
// Set default locking parameters for P2PKH
|
// Set default locking parameters for P2PKH
|
||||||
// To my knowledge, this doesnt generate any lockscript, so discovery of funds will not work automatically.
|
// To my knowledge, this doesnt generate any lockscript, so discovery of funds will not work automatically.
|
||||||
|
|||||||
@@ -96,6 +96,12 @@ export class HistoryService {
|
|||||||
private invitations: Invitation[],
|
private invitations: Invitation[],
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I Might swap this over to invitation based history before the event to make it a bit more evident... Really not happy with the UTXO for demo purposes
|
||||||
|
* But for the actual usage, UTXO is easier to follow - just not good for demo
|
||||||
|
* Long term, this is intended to be in the Engine, so we will just be a consumer of history state.
|
||||||
|
*/
|
||||||
async getHistory(): Promise<WalletHistoryItem[]> {
|
async getHistory(): Promise<WalletHistoryItem[]> {
|
||||||
const allUtxos = await this.engine.listUnspentOutputsData();
|
const allUtxos = await this.engine.listUnspentOutputsData();
|
||||||
const metadataIndex = await this.buildWalletMetadataIndex(allUtxos);
|
const metadataIndex = await this.buildWalletMetadataIndex(allUtxos);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import path from 'path';
|
|||||||
import { createMnemonicFile } from '../../cli/mnemonic.js';
|
import { createMnemonicFile } from '../../cli/mnemonic.js';
|
||||||
import { getMnemonicsDir } from '../../utils/paths.js';
|
import { getMnemonicsDir } from '../../utils/paths.js';
|
||||||
import { BCHMnemonicURL } from '../../utils/bch-mnemonic-url.js';
|
import { BCHMnemonicURL } from '../../utils/bch-mnemonic-url.js';
|
||||||
import { encodeBip39Mnemonic } from '@bitauth/libauth';
|
import { encodeBip39Mnemonic, generateBip39Mnemonic } from '@bitauth/libauth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status message type.
|
* Status message type.
|
||||||
@@ -41,7 +41,7 @@ interface MnemonicFileEntry {
|
|||||||
* Focus sections the user can tab between.
|
* Focus sections the user can tab between.
|
||||||
* When saved wallets exist the file list is shown first.
|
* 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),
|
* 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).
|
* The ordered list of focusable sections (files section only when entries exist).
|
||||||
*/
|
*/
|
||||||
const focusSections: FocusSection[] = mnemonicFiles.length > 0
|
const focusSections: FocusSection[] = mnemonicFiles.length > 0
|
||||||
? ['files', 'input', 'saveCheckbox', 'button']
|
? ['files', 'input', 'generateRandomSeed', 'saveCheckbox', 'button']
|
||||||
: ['input', 'saveCheckbox', 'button'];
|
: ['input', 'generateRandomSeed', 'saveCheckbox', 'button'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a status message with the given type.
|
* Shows a status message with the given type.
|
||||||
@@ -202,7 +202,7 @@ export function SeedInputScreen(): React.ReactElement {
|
|||||||
}, [mnemonicFiles, doInitialize]);
|
}, [mnemonicFiles, doInitialize]);
|
||||||
|
|
||||||
// Keyboard navigation
|
// Keyboard navigation
|
||||||
useBlockableInput((_input, key) => {
|
useBlockableInput((input, key) => {
|
||||||
if (isSubmitting) return;
|
if (isSubmitting) return;
|
||||||
|
|
||||||
// Tab / Shift-Tab to cycle focus sections
|
// 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
|
// Space or Enter toggles "save mnemonic" when that row is focused
|
||||||
if (focusedSection === 'saveCheckbox') {
|
if (focusedSection === 'saveCheckbox') {
|
||||||
if (_input === ' ' || key.return) {
|
if (input === ' ' || key.return) {
|
||||||
setSaveMnemonicChecked((v) => !v);
|
setSaveMnemonicChecked((v) => !v);
|
||||||
return;
|
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
|
// Enter on button submits manual seed
|
||||||
if (key.return && focusedSection === 'button') {
|
if (key.return && focusedSection === 'button') {
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
@@ -358,6 +370,19 @@ export function SeedInputScreen(): React.ReactElement {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</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) */}
|
{/* Save mnemonic checkbox (manual entry only; applies on Continue) */}
|
||||||
<Box
|
<Box
|
||||||
marginTop={1}
|
marginTop={1}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ import {
|
|||||||
import { BCHMnemonicURL } from "../../src/utils/bch-mnemonic-url";
|
import { BCHMnemonicURL } from "../../src/utils/bch-mnemonic-url";
|
||||||
|
|
||||||
const TEST_SEED =
|
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", () => {
|
describe("mnemonic utilities", () => {
|
||||||
let tempDir: string;
|
let tempDir: string;
|
||||||
|
|
||||||
@@ -54,7 +53,7 @@ describe("mnemonic utilities", () => {
|
|||||||
test("creates a mnemonic file with auto-generated name", () => {
|
test("creates a mnemonic file with auto-generated name", () => {
|
||||||
const filename = createMnemonicFile(tempDir, TEST_SEED);
|
const filename = createMnemonicFile(tempDir, TEST_SEED);
|
||||||
|
|
||||||
expect(filename).toMatch(/^mnemonic-page$/);
|
expect(filename).toMatch(/^mnemonic-oven$/);
|
||||||
expect(existsSync(path.join(tempDir, filename))).toBe(true);
|
expect(existsSync(path.join(tempDir, filename))).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { MockRatesService } from "./rates-service";
|
|||||||
import { RatesService } from "../../../src/services/rates";
|
import { RatesService } from "../../../src/services/rates";
|
||||||
|
|
||||||
export const DEFAULT_SEED =
|
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.
|
* Options for creating a fake resource (UTXO) in tests.
|
||||||
|
|||||||
Reference in New Issue
Block a user