nkamoto 1.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nkamoto contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # nkamoto
2
+
3
+ A terminal Bitcoin wallet CLI built around BIP-39 and BIP-84.
4
+
5
+ > **Security status:** development release. Do not use this implementation to store real BTC until it has been independently reviewed and audited.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js 20.19+.
10
+ - npm.
11
+
12
+ ## Commands
13
+
14
+ Running `nkamoto` without a command displays help.
15
+
16
+ ```text
17
+ nkamoto
18
+ nkamoto generate
19
+ nkamoto validate
20
+ nkamoto balance <bc1...>
21
+ nkamoto --help
22
+ nkamoto --version
23
+ ```
24
+
25
+ ### `nkamoto generate`
26
+
27
+ Generates:
28
+
29
+ - 256 bits of cryptographically secure entropy.
30
+ - A 24-word BIP-39 English mnemonic.
31
+ - A separately generated 12-word BIP-39 passphrase.
32
+ - A Bitcoin mainnet BIP-84 native SegWit address.
33
+ - Terminal QR codes for the mnemonic and passphrase.
34
+
35
+ Derivation path:
36
+
37
+ ```text
38
+ m/84'/0'/0'/0/0
39
+ ```
40
+
41
+ ### `nkamoto validate`
42
+
43
+ Reads the mnemonic interactively rather than accepting it as a command-line argument, reducing the chance of shell-history leakage.
44
+
45
+ ```text
46
+ nkamoto validate
47
+ ```
48
+
49
+ It validates the BIP-39 checksum, accepts the optional BIP-39 passphrase interactively, and derives the first BIP-84 address.
50
+
51
+ ### `nkamoto balance`
52
+
53
+ Checks a public Bitcoin address using the Blockstream Esplora API.
54
+
55
+ ```text
56
+ nkamoto balance bc1q...
57
+ ```
58
+
59
+ Only the public address is sent to the balance service. The command does not accept or require a mnemonic or private key.
60
+
61
+ The provider is isolated behind `BitcoinBalanceProvider` so a self-hosted or alternative backend can be added later.
62
+
63
+ ## Development
64
+
65
+ ```bash
66
+ npm install
67
+ npm run lint
68
+ npm test
69
+ npm run build
70
+ npm start
71
+ ```
72
+
73
+ For direct development:
74
+
75
+ ```bash
76
+ npm run dev
77
+ ```
78
+
79
+ ## Security
80
+
81
+ The mnemonic and BIP-39 passphrase are recovery secrets.
82
+
83
+ Do not:
84
+
85
+ - put them in shell arguments;
86
+ - paste them into websites or chat;
87
+ - commit them to source control;
88
+ - redirect wallet-generation output to files;
89
+ - use generated wallets with real funds before independent security review.
90
+
91
+ The public API intentionally does not return the derived BIP-39 seed.
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,16 @@
1
+ export interface BitcoinBalance {
2
+ readonly address: string;
3
+ readonly confirmedSatoshis: bigint;
4
+ readonly unconfirmedSatoshis: bigint;
5
+ readonly totalSatoshis: bigint;
6
+ }
7
+ export interface BitcoinBalanceProvider {
8
+ getBalance(address: string): Promise<BitcoinBalance>;
9
+ }
10
+ export declare class EsploraBalanceProvider implements BitcoinBalanceProvider {
11
+ private readonly baseUrl;
12
+ constructor(baseUrl?: string);
13
+ getBalance(address: string): Promise<BitcoinBalance>;
14
+ }
15
+ export declare function isBitcoinAddress(address: string): boolean;
16
+ export declare function satoshisToBtc(satoshis: bigint): string;
@@ -0,0 +1,71 @@
1
+ export class EsploraBalanceProvider {
2
+ baseUrl;
3
+ constructor(baseUrl = "https://blockstream.info/api") {
4
+ this.baseUrl = baseUrl;
5
+ }
6
+ async getBalance(address) {
7
+ if (!isBitcoinAddress(address)) {
8
+ throw new Error("Invalid Bitcoin address");
9
+ }
10
+ const response = await fetch(`${this.baseUrl.replace(/\/$/, "")}/address/${encodeURIComponent(address)}`, {
11
+ headers: {
12
+ accept: "application/json",
13
+ },
14
+ });
15
+ if (!response.ok) {
16
+ throw new Error(`Bitcoin balance request failed with HTTP ${response.status}`);
17
+ }
18
+ const data = await response.json();
19
+ const stats = parseAddressStats(data);
20
+ const confirmedSatoshis = stats.chainStats.funded - stats.chainStats.spent;
21
+ const unconfirmedSatoshis = stats.mempoolStats.funded - stats.mempoolStats.spent;
22
+ return {
23
+ address,
24
+ confirmedSatoshis,
25
+ unconfirmedSatoshis,
26
+ totalSatoshis: confirmedSatoshis + unconfirmedSatoshis,
27
+ };
28
+ }
29
+ }
30
+ function parseAddressStats(value) {
31
+ if (!isRecord(value)) {
32
+ throw new Error("Invalid Bitcoin API response");
33
+ }
34
+ const chainStats = parseStats(value.chain_stats);
35
+ const mempoolStats = parseStats(value.mempool_stats);
36
+ return { chainStats, mempoolStats };
37
+ }
38
+ function parseStats(value) {
39
+ if (!isRecord(value)) {
40
+ throw new Error("Invalid Bitcoin API response");
41
+ }
42
+ return {
43
+ funded: parseNonNegativeInteger(value.funded_txo_sum),
44
+ spent: parseNonNegativeInteger(value.spent_txo_sum),
45
+ };
46
+ }
47
+ function parseNonNegativeInteger(value) {
48
+ if (typeof value !== "number" && typeof value !== "string") {
49
+ throw new Error("Invalid Bitcoin API response");
50
+ }
51
+ const text = String(value);
52
+ if (!/^\d+$/.test(text)) {
53
+ throw new Error("Invalid Bitcoin API response");
54
+ }
55
+ return BigInt(text);
56
+ }
57
+ function isRecord(value) {
58
+ return typeof value === "object" && value !== null;
59
+ }
60
+ export function isBitcoinAddress(address) {
61
+ return /^bc1[ac-hj-np-z02-9]{11,87}$/.test(address);
62
+ }
63
+ export function satoshisToBtc(satoshis) {
64
+ const whole = satoshis / 100000000n;
65
+ const fraction = (satoshis % 100000000n)
66
+ .toString()
67
+ .padStart(8, "0")
68
+ .replace(/0+$/, "");
69
+ return fraction ? `${whole}.${fraction}` : `${whole}.00000000`;
70
+ }
71
+ //# sourceMappingURL=balance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balance.js","sourceRoot":"","sources":["../src/balance.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from "node:readline/promises";
3
+ import { stdin as input, stdout as output } from "node:process";
4
+ import { EsploraBalanceProvider, isBitcoinAddress, satoshisToBtc, } from "./balance.js";
5
+ import { renderQr } from "./qr.js";
6
+ import { BIP84_PATH, deriveBitcoinAddress, generateWallet, validateMnemonic, } from "./index.js";
7
+ const VERSION = "0.2.0";
8
+ const HELP = `NKAMOTO — Bitcoin wallet CLI
9
+
10
+ Usage:
11
+ nkamoto <command>
12
+
13
+ Commands:
14
+ generate Generate a new Bitcoin wallet
15
+ qr Generate a new wallet with qr code
16
+ validate Validate a BIP-39 mnemonic
17
+ balance Check a Bitcoin address balance
18
+
19
+ Options:
20
+ -h, --help Show help
21
+ -v, --version Show version
22
+
23
+ Examples:
24
+ nkamoto generate
25
+ nkamoto validate
26
+ nkamoto balance bc1q...
27
+
28
+ Security:
29
+ Never share your BIP-39 mnemonic or passphrase.
30
+ Never put recovery secrets in shell arguments.
31
+ `;
32
+ function printHelp() {
33
+ console.log(HELP);
34
+ }
35
+ function printVersion() {
36
+ console.log(`nkamoto ${VERSION}`);
37
+ }
38
+ async function runGenerate(qr_included) {
39
+ const wallet = generateWallet();
40
+ console.log("");
41
+ console.log("========================================");
42
+ console.log(" NKAMOTO BTC WALLET");
43
+ console.log("========================================");
44
+ console.log("");
45
+ console.log("Network: Bitcoin Mainnet");
46
+ console.log("Standard: BIP-39");
47
+ console.log("Address type: BIP-84 / P2WPKH");
48
+ console.log(`Derivation path: ${BIP84_PATH}`);
49
+ console.log("");
50
+ if (qr_included) {
51
+ await printQr("BIP-39 MNEMONIC QR", wallet.mnemonic);
52
+ await printQr("BIP-39 PASSPHRASE QR", wallet.passphrase);
53
+ }
54
+ console.log("Bitcoin address:");
55
+ console.log(wallet.address);
56
+ console.log("");
57
+ console.log("WARNING");
58
+ console.log("-------");
59
+ console.log("Your mnemonic and passphrase control this wallet.");
60
+ console.log("Never share them with anyone.");
61
+ console.log("Store recovery information offline.");
62
+ console.log("");
63
+ }
64
+ async function runValidate() {
65
+ const rl = createInterface({ input, output });
66
+ try {
67
+ const mnemonic = await rl.question("Enter BIP-39 mnemonic: ");
68
+ if (!validateMnemonic(mnemonic.trim())) {
69
+ console.error("Invalid BIP-39 mnemonic.");
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+ const passphrase = await rl.question("Enter BIP-39 passphrase (leave empty if none): ");
74
+ const address = deriveBitcoinAddress(mnemonic.trim(), passphrase);
75
+ console.log("");
76
+ console.log("Valid BIP-39 mnemonic.");
77
+ console.log(`BIP-84 address: ${address}`);
78
+ console.log("");
79
+ }
80
+ finally {
81
+ rl.close();
82
+ }
83
+ }
84
+ async function runBalance(address) {
85
+ if (!address || !isBitcoinAddress(address)) {
86
+ throw new Error("Usage: nkamoto balance <bc1... Bitcoin address>");
87
+ }
88
+ const provider = new EsploraBalanceProvider();
89
+ const balance = await provider.getBalance(address);
90
+ console.log("");
91
+ console.log(`Address: ${balance.address}`);
92
+ console.log(`Confirmed: ${satoshisToBtc(balance.confirmedSatoshis)} BTC`);
93
+ console.log(`Unconfirmed: ${satoshisToBtc(balance.unconfirmedSatoshis)} BTC`);
94
+ console.log(`Total: ${satoshisToBtc(balance.totalSatoshis)} BTC`);
95
+ console.log("");
96
+ }
97
+ async function printQr(label, value) {
98
+ console.log("");
99
+ console.log(label);
100
+ console.log("");
101
+ console.log(await renderQr(value));
102
+ }
103
+ async function main(argv) {
104
+ const [command, ...args] = argv;
105
+ if (!command || command === "--help" || command === "-h" || command === "help") {
106
+ printHelp();
107
+ return;
108
+ }
109
+ if (command === "--version" || command === "-v" || command === "version") {
110
+ printVersion();
111
+ return;
112
+ }
113
+ switch (command) {
114
+ case "qr":
115
+ if (args.length > 0) {
116
+ throw new Error("qr does not accept arguments");
117
+ }
118
+ await runGenerate(true);
119
+ return;
120
+ case "generate":
121
+ if (args.length > 0) {
122
+ throw new Error("generate does not accept arguments");
123
+ }
124
+ await runGenerate(false);
125
+ return;
126
+ case "validate":
127
+ if (args.length > 0) {
128
+ throw new Error("validate reads the mnemonic interactively; do not pass secrets as arguments");
129
+ }
130
+ await runValidate();
131
+ return;
132
+ case "balance":
133
+ await runBalance(args[0]);
134
+ return;
135
+ default:
136
+ printHelp();
137
+ throw new Error(`Unknown command: ${command}`);
138
+ }
139
+ }
140
+ main(process.argv.slice(2)).catch((error) => {
141
+ const message = error instanceof Error ? error.message : "Unknown error";
142
+ console.error(`nkamoto: ${message}`);
143
+ process.exitCode = 1;
144
+ });
145
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export { BIP84_PATH, deriveBitcoinAddress, mnemonicToSeed, validateMnemonic, } from "./wallet/derive.js";
2
+ export { generateMnemonic, generateWallet, } from "./wallet/generate.js";
3
+ export type { BitcoinWallet, } from "./wallet/generate.js";
4
+ export { generatePassphrase, } from "./wallet/passphrase.js";
5
+ export type { RandomWordSource, } from "./wallet/passphrase.js";
6
+ export { renderQr, } from "./qr.js";
7
+ export type { QrOptions, } from "./qr.js";
8
+ export { EsploraBalanceProvider, isBitcoinAddress, satoshisToBtc, } from "./balance.js";
9
+ export type { BitcoinBalance, BitcoinBalanceProvider, } from "./balance.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { BIP84_PATH, deriveBitcoinAddress, mnemonicToSeed, validateMnemonic, } from "./wallet/derive.js";
2
+ export { generateMnemonic, generateWallet, } from "./wallet/generate.js";
3
+ export { generatePassphrase, } from "./wallet/passphrase.js";
4
+ export { renderQr, } from "./qr.js";
5
+ export { EsploraBalanceProvider, isBitcoinAddress, satoshisToBtc, } from "./balance.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,gBAAgB,EAChB,cAAc,GACf,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EACL,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAMhC,OAAO,EACL,QAAQ,GACT,MAAM,SAAS,CAAC;AAMjB,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,GACd,MAAM,cAAc,CAAC"}
package/dist/qr.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export interface QrOptions {
2
+ readonly errorCorrectionLevel?: "L" | "M" | "Q" | "H";
3
+ readonly margin?: number;
4
+ }
5
+ export declare function renderQr(value: string, options?: QrOptions): Promise<string>;
package/dist/qr.js ADDED
@@ -0,0 +1,9 @@
1
+ import QRCode from "qrcode";
2
+ export async function renderQr(value, options = {}) {
3
+ return QRCode.toString(value, {
4
+ type: "terminal",
5
+ errorCorrectionLevel: options.errorCorrectionLevel ?? "M",
6
+ margin: options.margin ?? 2,
7
+ });
8
+ }
9
+ //# sourceMappingURL=qr.js.map
package/dist/qr.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.js","sourceRoot":"","sources":["../src/qr.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAO5B,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,KAAa,EACb,UAAqB,EAAE;IAEvB,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE;QAC5B,IAAI,EAAE,UAAU;QAChB,oBAAoB,EAAE,OAAO,CAAC,oBAAoB,IAAI,GAAG;QACzD,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC;KAC5B,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare const BIP84_PATH = "m/84'/0'/0'/0/0";
2
+ export declare function validateMnemonic(mnemonic: string): boolean;
3
+ export declare function mnemonicToSeed(mnemonic: string, passphrase: string): Uint8Array;
4
+ export declare function deriveBitcoinAddress(mnemonic: string, passphrase: string): string;
@@ -0,0 +1,28 @@
1
+ import * as bip32 from "@scure/bip32";
2
+ import * as bip39 from "@scure/bip39";
3
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
4
+ import * as btc from "@scure/btc-signer";
5
+ export const BIP84_PATH = "m/84'/0'/0'/0/0";
6
+ export function validateMnemonic(mnemonic) {
7
+ return bip39.validateMnemonic(mnemonic, wordlist);
8
+ }
9
+ export function mnemonicToSeed(mnemonic, passphrase) {
10
+ if (!validateMnemonic(mnemonic)) {
11
+ throw new Error("Invalid BIP-39 mnemonic");
12
+ }
13
+ return bip39.mnemonicToSeedSync(mnemonic, passphrase);
14
+ }
15
+ export function deriveBitcoinAddress(mnemonic, passphrase) {
16
+ const seed = mnemonicToSeed(mnemonic, passphrase);
17
+ const root = bip32.HDKey.fromMasterSeed(seed);
18
+ const child = root.derive(BIP84_PATH);
19
+ if (!child.publicKey) {
20
+ throw new Error("Failed to derive BIP-84 public key");
21
+ }
22
+ const payment = btc.p2wpkh(child.publicKey);
23
+ if (!payment.address) {
24
+ throw new Error("Failed to derive Bitcoin address");
25
+ }
26
+ return payment.address;
27
+ }
28
+ //# sourceMappingURL=derive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.js","sourceRoot":"","sources":["../../src/wallet/derive.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,KAAK,GAAG,MAAM,mBAAmB,CAAC;AAEzC,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAE5C,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,OAAO,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,QAAgB,EAChB,UAAkB;IAElB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,KAAK,CAAC,kBAAkB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,QAAgB,EAChB,UAAkB;IAElB,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAEtC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAE5C,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC;AACzB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { BIP84_PATH } from "./derive.js";
2
+ export interface BitcoinWallet {
3
+ readonly mnemonic: string;
4
+ readonly passphrase: string;
5
+ readonly derivationPath: typeof BIP84_PATH;
6
+ readonly address: string;
7
+ }
8
+ export declare function generateMnemonic(entropy?: Uint8Array): string;
9
+ export declare function generateWallet(): BitcoinWallet;
@@ -0,0 +1,26 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import * as bip39 from "@scure/bip39";
3
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
4
+ import { BIP84_PATH, deriveBitcoinAddress } from "./derive.js";
5
+ import { generatePassphrase } from "./passphrase.js";
6
+ const ENTROPY_BYTES = 32;
7
+ function generateEntropy() {
8
+ return new Uint8Array(randomBytes(ENTROPY_BYTES));
9
+ }
10
+ export function generateMnemonic(entropy = generateEntropy()) {
11
+ if (entropy.length !== ENTROPY_BYTES) {
12
+ throw new RangeError("Entropy must contain exactly 32 bytes");
13
+ }
14
+ return bip39.entropyToMnemonic(entropy, wordlist);
15
+ }
16
+ export function generateWallet() {
17
+ const mnemonic = generateMnemonic();
18
+ const passphrase = generatePassphrase();
19
+ return {
20
+ mnemonic,
21
+ passphrase,
22
+ derivationPath: BIP84_PATH,
23
+ address: deriveBitcoinAddress(mnemonic, passphrase),
24
+ };
25
+ }
26
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/wallet/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAE7D,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,aAAa,GAAG,EAAE,CAAC;AASzB,SAAS,eAAe;IACtB,OAAO,IAAI,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,UAAsB,eAAe,EAAE;IAEvC,IAAI,OAAO,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QACrC,MAAM,IAAI,UAAU,CAAC,uCAAuC,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,KAAK,CAAC,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG,kBAAkB,EAAE,CAAC;IAExC,OAAO;QACL,QAAQ;QACR,UAAU;QACV,cAAc,EAAE,UAAU;QAC1B,OAAO,EAAE,oBAAoB,CAAC,QAAQ,EAAE,UAAU,CAAC;KACpD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export interface RandomWordSource {
2
+ next(maxExclusive: number): number;
3
+ }
4
+ export declare function generatePassphrase(wordCount?: number, random?: RandomWordSource): string;
@@ -0,0 +1,22 @@
1
+ import { randomInt } from "node:crypto";
2
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
3
+ const DEFAULT_WORD_COUNT = 12;
4
+ const cryptoWordSource = {
5
+ next(maxExclusive) {
6
+ return randomInt(0, maxExclusive);
7
+ },
8
+ };
9
+ export function generatePassphrase(wordCount = DEFAULT_WORD_COUNT, random = cryptoWordSource) {
10
+ if (!Number.isInteger(wordCount) || wordCount < 1) {
11
+ throw new RangeError("wordCount must be a positive integer");
12
+ }
13
+ const words = Array.from({ length: wordCount }, () => {
14
+ const index = random.next(wordlist.length);
15
+ if (!Number.isInteger(index) || index < 0 || index >= wordlist.length) {
16
+ throw new Error("Random source returned an invalid word index");
17
+ }
18
+ return wordlist[index];
19
+ });
20
+ return words.join(" ");
21
+ }
22
+ //# sourceMappingURL=passphrase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../../src/wallet/passphrase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAE7D,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAM9B,MAAM,gBAAgB,GAAqB;IACzC,IAAI,CAAC,YAAoB;QACvB,OAAO,SAAS,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IACpC,CAAC;CACF,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAChC,SAAS,GAAG,kBAAkB,EAC9B,SAA2B,gBAAgB;IAE3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,UAAU,CAAC,sCAAsC,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE;QACnD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAE3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAClE,CAAC;QAED,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "nkamoto",
3
+ "version": "1.2.0",
4
+ "description": "NKAMOTO is a security-focused Bitcoin wallet CLI for generating BIP-39 wallets, managing Bitcoin keys, addresses, and QR codes from the terminal.",
5
+ "type": "module",
6
+
7
+ "bin": {
8
+ "nkamoto": "dist/cli.js"
9
+ },
10
+
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+
17
+ "engines": {
18
+ "node": ">=20.19.0"
19
+ },
20
+
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/Coperbyte/nkamoto.app.git"
24
+ },
25
+
26
+ "homepage": "https://nkamoto.app",
27
+
28
+ "bugs": {
29
+ "url": "https://github.com/Coperbyte/nkamoto.app/issues"
30
+ },
31
+
32
+ "license": "MIT",
33
+
34
+ "publishConfig": {
35
+ "access": "public"
36
+ }
37
+ }