From a9230bd5f0644b02148c67da5f38449e1c6aea4d Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Mon, 18 May 2026 17:05:47 -0400 Subject: [PATCH 01/24] bitcore-sh init with base crypto-rpc functionality --- bitcore-sh/.gitignore | 1 + bitcore-sh/package-lock.json | 13 +++++++++++ bitcore-sh/package.json | 14 ++++++++++++ bitcore-sh/src/index.ts | 43 ++++++++++++++++++++++++++++++++++++ bitcore-sh/tsconfig.json | 35 +++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 bitcore-sh/.gitignore create mode 100644 bitcore-sh/package-lock.json create mode 100644 bitcore-sh/package.json create mode 100644 bitcore-sh/src/index.ts create mode 100644 bitcore-sh/tsconfig.json diff --git a/bitcore-sh/.gitignore b/bitcore-sh/.gitignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/bitcore-sh/.gitignore @@ -0,0 +1 @@ +build diff --git a/bitcore-sh/package-lock.json b/bitcore-sh/package-lock.json new file mode 100644 index 0000000000..a76564496d --- /dev/null +++ b/bitcore-sh/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "bitcore-sh", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bitcore-sh", + "version": "1.0.0", + "license": "ISC" + } + } +} diff --git a/bitcore-sh/package.json b/bitcore-sh/package.json new file mode 100644 index 0000000000..38458a068b --- /dev/null +++ b/bitcore-sh/package.json @@ -0,0 +1,14 @@ +{ + "name": "bitcore-sh", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "npm run compile && node build/index.js", + "compile": "rm -rf build && tsc", + "tsc": "tsc" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} diff --git a/bitcore-sh/src/index.ts b/bitcore-sh/src/index.ts new file mode 100644 index 0000000000..48558da68a --- /dev/null +++ b/bitcore-sh/src/index.ts @@ -0,0 +1,43 @@ +import fs from 'fs'; +import repl from 'repl'; +const { CryptoRpc } = require('/home/micah/dev/bitcore/packages/crypto-rpc'); + +const path: string = process.env.BITCORE_CONFIG_PATH || ''; +const config = JSON.parse(fs.readFileSync(path).toString()).bitcoreNode; +config; + +const shell = repl.start('bitcore-sh> '); +shell.defineCommand('cryptorpc', { + async action(_args) { + this.clearBufferedCommand(); + const args = _args.split(' '); + const chain = args[0].toUpperCase(); + const network = args[1]; + const command = args[2]; + args.slice(0, 3); + + const rpcArgs = {}; + for (let i = 0; i < args.length; i++) { + if (!args[i].startsWith('--')) + continue; + rpcArgs[args[i].slice(2)] = args[i + 1]; + args.splice(i, 2); + i--; + } + + const networkConfig = config.chains[chain][network]; + const rpcConfig = networkConfig.rpc || networkConfig.providers[0]; + + const rpc = new CryptoRpc({ + chain, + protocol: rpcConfig.protocol || 'http', + host: rpcConfig.host, + port: rpcConfig.port, + user: rpcConfig.username, + pass: rpcConfig.password + }).get(chain); + + console.log(await rpc[command](rpcArgs)); + this.displayPrompt(); + } +}); diff --git a/bitcore-sh/tsconfig.json b/bitcore-sh/tsconfig.json new file mode 100644 index 0000000000..227261eed0 --- /dev/null +++ b/bitcore-sh/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "ES2020", + "esnext", + "esnext.asynciterable", + "ES2022", + "DOM" + ], + "downlevelIteration": true, + "allowJs": true, + "sourceMap": true, + "outDir": "build", + "strict": true, + "noImplicitAny": false, + "skipLibCheck": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "experimentalDecorators": true + }, + "include": [ + "./src/**/*", + "./scripts/**/*", + "./test/**/*" + ] +} From 795e095d220cc1d55440e2f33e40b25dd437cba9 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Tue, 19 May 2026 15:29:15 -0400 Subject: [PATCH 02/24] switched to readline --- bitcore-sh/src/index.ts | 72 ++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/bitcore-sh/src/index.ts b/bitcore-sh/src/index.ts index 48558da68a..fa9d9011af 100644 --- a/bitcore-sh/src/index.ts +++ b/bitcore-sh/src/index.ts @@ -1,43 +1,47 @@ import fs from 'fs'; -import repl from 'repl'; +import readline from 'readline' const { CryptoRpc } = require('/home/micah/dev/bitcore/packages/crypto-rpc'); const path: string = process.env.BITCORE_CONFIG_PATH || ''; const config = JSON.parse(fs.readFileSync(path).toString()).bitcoreNode; config; -const shell = repl.start('bitcore-sh> '); -shell.defineCommand('cryptorpc', { - async action(_args) { - this.clearBufferedCommand(); - const args = _args.split(' '); - const chain = args[0].toUpperCase(); - const network = args[1]; - const command = args[2]; - args.slice(0, 3); - - const rpcArgs = {}; - for (let i = 0; i < args.length; i++) { - if (!args[i].startsWith('--')) - continue; - rpcArgs[args[i].slice(2)] = args[i + 1]; - args.splice(i, 2); - i--; - } - - const networkConfig = config.chains[chain][network]; - const rpcConfig = networkConfig.rpc || networkConfig.providers[0]; - - const rpc = new CryptoRpc({ - chain, - protocol: rpcConfig.protocol || 'http', - host: rpcConfig.host, - port: rpcConfig.port, - user: rpcConfig.username, - pass: rpcConfig.password - }).get(chain); - - console.log(await rpc[command](rpcArgs)); - this.displayPrompt(); +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +process.stdout.write('> '); + +rl.on('line', async (line) => { + const args = line.split(' '); + const chain = args[0].toUpperCase(); + const network = args[1]; + const command = args[2]; + args.slice(0, 3); + + const rpcArgs = {}; + for (let i = 0; i < args.length; i++) { + if (!args[i].startsWith('--')) + continue; + rpcArgs[args[i].slice(2)] = args[i + 1]; + args.splice(i, 2); + i--; } + + const networkConfig = config.chains[chain][network]; + const rpcConfig = networkConfig.rpc || networkConfig.providers[0]; + + const rpc = new CryptoRpc({ + chain, + protocol: rpcConfig.protocol || 'http', + host: rpcConfig.host, + port: rpcConfig.port, + user: rpcConfig.username, + pass: rpcConfig.password + }).get(chain); + + console.log(await rpc[command](rpcArgs)); + + process.stdout.write('> '); }); From d83838a1f60d281e8ccda34b01d2b36a626674f3 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Tue, 19 May 2026 15:41:08 -0400 Subject: [PATCH 03/24] added use syntax --- bitcore-sh/src/index.ts | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/bitcore-sh/src/index.ts b/bitcore-sh/src/index.ts index fa9d9011af..b9ed3c7ddd 100644 --- a/bitcore-sh/src/index.ts +++ b/bitcore-sh/src/index.ts @@ -2,10 +2,6 @@ import fs from 'fs'; import readline from 'readline' const { CryptoRpc } = require('/home/micah/dev/bitcore/packages/crypto-rpc'); -const path: string = process.env.BITCORE_CONFIG_PATH || ''; -const config = JSON.parse(fs.readFileSync(path).toString()).bitcoreNode; -config; - const rl = readline.createInterface({ input: process.stdin, output: process.stdout @@ -13,8 +9,26 @@ const rl = readline.createInterface({ process.stdout.write('> '); +const path: string = process.env.BITCORE_CONFIG_PATH || ''; +const config = JSON.parse(fs.readFileSync(path).toString()).bitcoreNode; + +const context: string[] = []; + rl.on('line', async (line) => { - const args = line.split(' '); + let args = line.split(' '); + if (args[0] === 'use') { + for (const arg of args.slice(1)) { + if (arg === '..') { + context.pop(); + } else { + context.push(arg); + } + } + end(); + return; + } + args = [...context, ...args]; + const chain = args[0].toUpperCase(); const network = args[1]; const command = args[2]; @@ -42,6 +56,9 @@ rl.on('line', async (line) => { }).get(chain); console.log(await rpc[command](rpcArgs)); - - process.stdout.write('> '); + end(); }); + +function end() { + process.stdout.write(`${context.join(' ')}> `); +} From 02362bb20f1dcccde9638bd2fad4662411873003 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Tue, 19 May 2026 15:42:56 -0400 Subject: [PATCH 04/24] added list command --- bitcore-sh/src/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bitcore-sh/src/index.ts b/bitcore-sh/src/index.ts index b9ed3c7ddd..fd6574ff80 100644 --- a/bitcore-sh/src/index.ts +++ b/bitcore-sh/src/index.ts @@ -27,6 +27,11 @@ rl.on('line', async (line) => { end(); return; } + if (args[0] === 'list') { + console.log(Object.keys(config.chains).join(' ')); + end(); + return; + } args = [...context, ...args]; const chain = args[0].toUpperCase(); From d46a5d31efa354902e68733635ea3b5272b72dfa Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Tue, 19 May 2026 19:09:59 -0400 Subject: [PATCH 05/24] error handling --- bitcore-sh/src/index.ts | 50 ++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/bitcore-sh/src/index.ts b/bitcore-sh/src/index.ts index fd6574ff80..06290d3cb8 100644 --- a/bitcore-sh/src/index.ts +++ b/bitcore-sh/src/index.ts @@ -34,33 +34,37 @@ rl.on('line', async (line) => { } args = [...context, ...args]; - const chain = args[0].toUpperCase(); - const network = args[1]; - const command = args[2]; - args.slice(0, 3); + try { + const chain = args[0].toUpperCase(); + const network = args[1]; + const command = args[2]; + args.slice(0, 3); - const rpcArgs = {}; - for (let i = 0; i < args.length; i++) { - if (!args[i].startsWith('--')) - continue; - rpcArgs[args[i].slice(2)] = args[i + 1]; - args.splice(i, 2); - i--; - } + const rpcArgs = {}; + for (let i = 0; i < args.length; i++) { + if (!args[i].startsWith('--')) + continue; + rpcArgs[args[i].slice(2)] = args[i + 1]; + args.splice(i, 2); + i--; + } - const networkConfig = config.chains[chain][network]; - const rpcConfig = networkConfig.rpc || networkConfig.providers[0]; + const networkConfig = config.chains[chain][network]; + const rpcConfig = networkConfig.rpc || networkConfig.providers[0]; - const rpc = new CryptoRpc({ - chain, - protocol: rpcConfig.protocol || 'http', - host: rpcConfig.host, - port: rpcConfig.port, - user: rpcConfig.username, - pass: rpcConfig.password - }).get(chain); + const rpc = new CryptoRpc({ + chain, + protocol: rpcConfig.protocol || 'http', + host: rpcConfig.host, + port: rpcConfig.port, + user: rpcConfig.username, + pass: rpcConfig.password + }).get(chain); - console.log(await rpc[command](rpcArgs)); + console.log(await rpc[command](rpcArgs)); + } catch (e) { + console.log(e); + } end(); }); From 217a3d8ffaf3d5c449fbb1814383415b88c24770 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Tue, 19 May 2026 19:18:43 -0400 Subject: [PATCH 06/24] added line completions --- bitcore-sh/src/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bitcore-sh/src/index.ts b/bitcore-sh/src/index.ts index 06290d3cb8..0fdd62baad 100644 --- a/bitcore-sh/src/index.ts +++ b/bitcore-sh/src/index.ts @@ -4,7 +4,12 @@ const { CryptoRpc } = require('/home/micah/dev/bitcore/packages/crypto-rpc'); const rl = readline.createInterface({ input: process.stdin, - output: process.stdout + output: process.stdout, + completer: (line: string) => { + const completions = Object.keys(config.chains); + const hits = completions.filter(c => c.startsWith(line.toUpperCase())); + return [hits.length ? hits : completions, line]; + } }); process.stdout.write('> '); From 7a03c803d960439d6802dc9f71825710615e1e0b Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 11:08:12 -0400 Subject: [PATCH 07/24] completer for chain and network --- bitcore-sh/src/index.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bitcore-sh/src/index.ts b/bitcore-sh/src/index.ts index 0fdd62baad..48fa7b0dc5 100644 --- a/bitcore-sh/src/index.ts +++ b/bitcore-sh/src/index.ts @@ -6,8 +6,20 @@ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, completer: (line: string) => { - const completions = Object.keys(config.chains); - const hits = completions.filter(c => c.startsWith(line.toUpperCase())); + const args = [...context, ...line.split(' ')]; + let completions: string[] = []; + let hits: string[] = []; + if (args.length <= 1) { + completions = [...'use list'.split(' '), ...Object.keys(config.chains)]; + hits = completions.filter(c => c.toLowerCase().startsWith(args[0].toLowerCase())); + } else if (args.length === 2) { + if (Object.keys(config.chains).includes(args[0].toUpperCase())) { + completions = Object.keys(config.chains[args[0].toUpperCase()]); + hits = completions.filter(c => c.startsWith(args[1])); + if (hits.length === 1) + hits[0] = `${args[0]} ${hits[0]}` + } + } return [hits.length ? hits : completions, line]; } }); From 6b22fbaaadc3a0855b6789f756cbef5ade1cc42a Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 11:31:00 -0400 Subject: [PATCH 08/24] import crypto-rpc properly --- bitcore-sh/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitcore-sh/src/index.ts b/bitcore-sh/src/index.ts index 48fa7b0dc5..74d65fab27 100644 --- a/bitcore-sh/src/index.ts +++ b/bitcore-sh/src/index.ts @@ -1,6 +1,6 @@ import fs from 'fs'; -import readline from 'readline' -const { CryptoRpc } = require('/home/micah/dev/bitcore/packages/crypto-rpc'); +import readline from 'readline'; +const { CryptoRpc } = require('../../packages/crypto-rpc'); const rl = readline.createInterface({ input: process.stdin, From 93f0c8ff57e12cb76ae75a1a99a4699e15f02203 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 11:34:44 -0400 Subject: [PATCH 09/24] fix prompt deletion bug --- bitcore-sh/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bitcore-sh/src/index.ts b/bitcore-sh/src/index.ts index 74d65fab27..ef83236510 100644 --- a/bitcore-sh/src/index.ts +++ b/bitcore-sh/src/index.ts @@ -86,5 +86,6 @@ rl.on('line', async (line) => { }); function end() { - process.stdout.write(`${context.join(' ')}> `); + rl.setPrompt(`${context.join(' ')}> `); + rl.prompt(); } From 28852040e2a348d5e6f7915aaf1f35895042ee7d Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 11:37:07 -0400 Subject: [PATCH 10/24] fix mistake: moved bitcore-sh to packages --- {bitcore-sh => packages/bitcore-sh}/.gitignore | 0 {bitcore-sh => packages/bitcore-sh}/package-lock.json | 0 {bitcore-sh => packages/bitcore-sh}/package.json | 0 {bitcore-sh => packages/bitcore-sh}/src/index.ts | 2 +- {bitcore-sh => packages/bitcore-sh}/tsconfig.json | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename {bitcore-sh => packages/bitcore-sh}/.gitignore (100%) rename {bitcore-sh => packages/bitcore-sh}/package-lock.json (100%) rename {bitcore-sh => packages/bitcore-sh}/package.json (100%) rename {bitcore-sh => packages/bitcore-sh}/src/index.ts (97%) rename {bitcore-sh => packages/bitcore-sh}/tsconfig.json (100%) diff --git a/bitcore-sh/.gitignore b/packages/bitcore-sh/.gitignore similarity index 100% rename from bitcore-sh/.gitignore rename to packages/bitcore-sh/.gitignore diff --git a/bitcore-sh/package-lock.json b/packages/bitcore-sh/package-lock.json similarity index 100% rename from bitcore-sh/package-lock.json rename to packages/bitcore-sh/package-lock.json diff --git a/bitcore-sh/package.json b/packages/bitcore-sh/package.json similarity index 100% rename from bitcore-sh/package.json rename to packages/bitcore-sh/package.json diff --git a/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/index.ts similarity index 97% rename from bitcore-sh/src/index.ts rename to packages/bitcore-sh/src/index.ts index ef83236510..6f1b8a2b08 100644 --- a/bitcore-sh/src/index.ts +++ b/packages/bitcore-sh/src/index.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import readline from 'readline'; -const { CryptoRpc } = require('../../packages/crypto-rpc'); +const { CryptoRpc } = require('../../crypto-rpc'); const rl = readline.createInterface({ input: process.stdin, diff --git a/bitcore-sh/tsconfig.json b/packages/bitcore-sh/tsconfig.json similarity index 100% rename from bitcore-sh/tsconfig.json rename to packages/bitcore-sh/tsconfig.json From 473c5f814cbff3c4e75c1012ab3208eb0308b0c7 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 12:29:27 -0400 Subject: [PATCH 11/24] rpc command autocomplete --- packages/bitcore-sh/src/index.ts | 42 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/packages/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/index.ts index 6f1b8a2b08..ce57a7e125 100644 --- a/packages/bitcore-sh/src/index.ts +++ b/packages/bitcore-sh/src/index.ts @@ -2,6 +2,9 @@ import fs from 'fs'; import readline from 'readline'; const { CryptoRpc } = require('../../crypto-rpc'); +const rpcMethods = Object.getOwnPropertyNames(CryptoRpc.prototype) + .filter(p => typeof CryptoRpc.prototype[p] === 'function' && p !== 'constructor'); + const rl = readline.createInterface({ input: process.stdin, output: process.stdout, @@ -15,9 +18,13 @@ const rl = readline.createInterface({ } else if (args.length === 2) { if (Object.keys(config.chains).includes(args[0].toUpperCase())) { completions = Object.keys(config.chains[args[0].toUpperCase()]); - hits = completions.filter(c => c.startsWith(args[1])); - if (hits.length === 1) - hits[0] = `${args[0]} ${hits[0]}` + hits = completions.filter(c => c.startsWith(args[1])).map(h => `${args[0]} ${h}`) + } + } else if (args.length === 3) { + const rpc = getRpc(args[0].toUpperCase(), args[1]); + if (rpc) { + completions = rpcMethods; + hits = completions.filter(c => c.startsWith(args[2])).map(h => `${args[0]} ${args[1]} ${h}`); } } return [hits.length ? hits : completions, line]; @@ -66,18 +73,7 @@ rl.on('line', async (line) => { i--; } - const networkConfig = config.chains[chain][network]; - const rpcConfig = networkConfig.rpc || networkConfig.providers[0]; - - const rpc = new CryptoRpc({ - chain, - protocol: rpcConfig.protocol || 'http', - host: rpcConfig.host, - port: rpcConfig.port, - user: rpcConfig.username, - pass: rpcConfig.password - }).get(chain); - + const rpc = getRpc(chain, network); console.log(await rpc[command](rpcArgs)); } catch (e) { console.log(e); @@ -85,6 +81,22 @@ rl.on('line', async (line) => { end(); }); +function getRpc(chain: string, network: string) { + if (!(config && config.chains && config.chains[chain] && config.chains[chain][network])) + return; + const networkConfig = config.chains[chain][network]; + const rpcConfig = networkConfig.rpc || networkConfig.providers[0]; + + return new CryptoRpc({ + chain, + protocol: rpcConfig.protocol || 'http', + host: rpcConfig.host, + port: rpcConfig.port, + user: rpcConfig.username, + pass: rpcConfig.password + }).get(chain); +} + function end() { rl.setPrompt(`${context.join(' ')}> `); rl.prompt(); From d4ab3c033769699705f66d812813dc7b9ffd4972 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 12:54:53 -0400 Subject: [PATCH 12/24] added argument completion --- packages/bitcore-sh/src/index.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/index.ts index ce57a7e125..03e8726d85 100644 --- a/packages/bitcore-sh/src/index.ts +++ b/packages/bitcore-sh/src/index.ts @@ -26,11 +26,29 @@ const rl = readline.createInterface({ completions = rpcMethods; hits = completions.filter(c => c.startsWith(args[2])).map(h => `${args[0]} ${args[1]} ${h}`); } + } else if (args.length === 4) { + const rpc = getRpc(args[0].toUpperCase(), args[1]); + if (rpc) { + completions = getParams(rpc[args[2]]); + hits = completions.filter(c => c.startsWith(args[3])).map(h => `${args[0]} ${args[1]} ${args[2]} ${h}`) + } } return [hits.length ? hits : completions, line]; } }); +function getParams(func) { + const funcStr = func.toString(); + const match = funcStr.match(/\{\s*([^}]+)\s*\}/); + if (!match) return []; + + return match[1] + .split(',') + .map((key: string) => key.trim()) + .map((key: string) => '--' + (key.substring(0, key.indexOf(' ')) || key)) + .filter((key: string) => key); +} + process.stdout.write('> '); const path: string = process.env.BITCORE_CONFIG_PATH || ''; From 9e276938c8b050bfa72b1e3fbe6388fcac93e6c3 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 13:31:20 -0400 Subject: [PATCH 13/24] refactored with config module --- packages/bitcore-sh/src/config.ts | 28 ++++++++++++++++++++++++++++ packages/bitcore-sh/src/index.ts | 16 ++++++---------- 2 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 packages/bitcore-sh/src/config.ts diff --git a/packages/bitcore-sh/src/config.ts b/packages/bitcore-sh/src/config.ts new file mode 100644 index 0000000000..4440f89301 --- /dev/null +++ b/packages/bitcore-sh/src/config.ts @@ -0,0 +1,28 @@ +import fs from 'fs'; + +interface ConfigType { + [chain: string]: { + [network: string]: { + host: string; + port: number | string; + username: string; + password: string; + protocol?: 'http' | 'https' | 'ws' | 'wss' | 'ipc'; + }; + }; +}; + +const path: string = process.env.BITCORE_CONFIG_PATH || ''; +const rawConfig: object = JSON.parse(fs.readFileSync(path).toString()).bitcoreNode.chains; +const config: ConfigType = {}; +for (const chain in rawConfig) { + const chainConfig = rawConfig[chain]; + config[chain] = {}; + for (const network in chainConfig) { + const networkConfig = chainConfig[network]; + const rpcConfig = networkConfig.rpc || networkConfig.provider || networkConfig.providers[0]; + config[chain][network] = rpcConfig; + } +} + +export default config; diff --git a/packages/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/index.ts index 03e8726d85..b06c287b88 100644 --- a/packages/bitcore-sh/src/index.ts +++ b/packages/bitcore-sh/src/index.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; import readline from 'readline'; +import config from './config'; const { CryptoRpc } = require('../../crypto-rpc'); const rpcMethods = Object.getOwnPropertyNames(CryptoRpc.prototype) @@ -13,11 +13,11 @@ const rl = readline.createInterface({ let completions: string[] = []; let hits: string[] = []; if (args.length <= 1) { - completions = [...'use list'.split(' '), ...Object.keys(config.chains)]; + completions = [...'use list'.split(' '), ...Object.keys(config)]; hits = completions.filter(c => c.toLowerCase().startsWith(args[0].toLowerCase())); } else if (args.length === 2) { - if (Object.keys(config.chains).includes(args[0].toUpperCase())) { - completions = Object.keys(config.chains[args[0].toUpperCase()]); + if (Object.keys(config).includes(args[0].toUpperCase())) { + completions = Object.keys(config[args[0].toUpperCase()]); hits = completions.filter(c => c.startsWith(args[1])).map(h => `${args[0]} ${h}`) } } else if (args.length === 3) { @@ -51,9 +51,6 @@ function getParams(func) { process.stdout.write('> '); -const path: string = process.env.BITCORE_CONFIG_PATH || ''; -const config = JSON.parse(fs.readFileSync(path).toString()).bitcoreNode; - const context: string[] = []; rl.on('line', async (line) => { @@ -100,10 +97,9 @@ rl.on('line', async (line) => { }); function getRpc(chain: string, network: string) { - if (!(config && config.chains && config.chains[chain] && config.chains[chain][network])) + if (!config[chain][network]) return; - const networkConfig = config.chains[chain][network]; - const rpcConfig = networkConfig.rpc || networkConfig.providers[0]; + const rpcConfig = config[chain][network]; return new CryptoRpc({ chain, From 80931c7d30a3d3f6e772f21bb581a163aed7c2b3 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 13:47:19 -0400 Subject: [PATCH 14/24] fixed bad replacement logic --- packages/bitcore-sh/src/index.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/index.ts index b06c287b88..be059cd7df 100644 --- a/packages/bitcore-sh/src/index.ts +++ b/packages/bitcore-sh/src/index.ts @@ -4,6 +4,7 @@ const { CryptoRpc } = require('../../crypto-rpc'); const rpcMethods = Object.getOwnPropertyNames(CryptoRpc.prototype) .filter(p => typeof CryptoRpc.prototype[p] === 'function' && p !== 'constructor'); +const context: string[] = []; const rl = readline.createInterface({ input: process.stdin, @@ -18,24 +19,25 @@ const rl = readline.createInterface({ } else if (args.length === 2) { if (Object.keys(config).includes(args[0].toUpperCase())) { completions = Object.keys(config[args[0].toUpperCase()]); - hits = completions.filter(c => c.startsWith(args[1])).map(h => `${args[0]} ${h}`) + hits = completions.filter(c => c.startsWith(args[1])); } } else if (args.length === 3) { const rpc = getRpc(args[0].toUpperCase(), args[1]); if (rpc) { completions = rpcMethods; - hits = completions.filter(c => c.startsWith(args[2])).map(h => `${args[0]} ${args[1]} ${h}`); + hits = completions.filter(c => c.startsWith(args[2])); } } else if (args.length === 4) { const rpc = getRpc(args[0].toUpperCase(), args[1]); if (rpc) { completions = getParams(rpc[args[2]]); - hits = completions.filter(c => c.startsWith(args[3])).map(h => `${args[0]} ${args[1]} ${args[2]} ${h}`) + hits = completions.filter(c => c.startsWith(args[3])); } } - return [hits.length ? hits : completions, line]; + return [hits.length ? hits : completions, args[args.length - 1]]; } }); +nextCommand(); function getParams(func) { const funcStr = func.toString(); @@ -49,9 +51,6 @@ function getParams(func) { .filter((key: string) => key); } -process.stdout.write('> '); - -const context: string[] = []; rl.on('line', async (line) => { let args = line.split(' '); @@ -63,12 +62,12 @@ rl.on('line', async (line) => { context.push(arg); } } - end(); + nextCommand(); return; } if (args[0] === 'list') { console.log(Object.keys(config.chains).join(' ')); - end(); + nextCommand(); return; } args = [...context, ...args]; @@ -77,7 +76,7 @@ rl.on('line', async (line) => { const chain = args[0].toUpperCase(); const network = args[1]; const command = args[2]; - args.slice(0, 3); + args.splice(0, 3); const rpcArgs = {}; for (let i = 0; i < args.length; i++) { @@ -89,11 +88,12 @@ rl.on('line', async (line) => { } const rpc = getRpc(chain, network); - console.log(await rpc[command](rpcArgs)); + if (rpc) + console.log(await rpc[command](rpcArgs)); } catch (e) { console.log(e); } - end(); + nextCommand(); }); function getRpc(chain: string, network: string) { @@ -111,7 +111,7 @@ function getRpc(chain: string, network: string) { }).get(chain); } -function end() { +function nextCommand() { rl.setPrompt(`${context.join(' ')}> `); rl.prompt(); } From 89b9586d18a7165389514e8ffd073c8b0ba9fa03 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 14:05:11 -0400 Subject: [PATCH 15/24] fix use command --- packages/bitcore-sh/src/index.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/index.ts index be059cd7df..331d7f4e96 100644 --- a/packages/bitcore-sh/src/index.ts +++ b/packages/bitcore-sh/src/index.ts @@ -10,27 +10,29 @@ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, completer: (line: string) => { - const args = [...context, ...line.split(' ')]; - let completions: string[] = []; + let args = [...context, ...line.split(' ')]; + if (args.includes('use')) + args = args.filter(arg => arg !== 'use'); + const completions: string[] = line.includes(' ') ? [] : ['use']; let hits: string[] = []; if (args.length <= 1) { - completions = [...'use list'.split(' '), ...Object.keys(config)]; + completions.push(...Object.keys(config)); hits = completions.filter(c => c.toLowerCase().startsWith(args[0].toLowerCase())); } else if (args.length === 2) { if (Object.keys(config).includes(args[0].toUpperCase())) { - completions = Object.keys(config[args[0].toUpperCase()]); + completions.push(...Object.keys(config[args[0].toUpperCase()])); hits = completions.filter(c => c.startsWith(args[1])); } } else if (args.length === 3) { const rpc = getRpc(args[0].toUpperCase(), args[1]); if (rpc) { - completions = rpcMethods; + completions.push(...rpcMethods); hits = completions.filter(c => c.startsWith(args[2])); } } else if (args.length === 4) { const rpc = getRpc(args[0].toUpperCase(), args[1]); if (rpc) { - completions = getParams(rpc[args[2]]); + completions.push(getParams(rpc[args[2]])); hits = completions.filter(c => c.startsWith(args[3])); } } @@ -65,11 +67,6 @@ rl.on('line', async (line) => { nextCommand(); return; } - if (args[0] === 'list') { - console.log(Object.keys(config.chains).join(' ')); - nextCommand(); - return; - } args = [...context, ...args]; try { From 2e8a7d5a2bc5a0698fd8909a829db8c5d8cec073 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 14:08:33 -0400 Subject: [PATCH 16/24] refactored rpc logic into rpc module --- packages/bitcore-sh/src/index.ts | 20 +------------------- packages/bitcore-sh/src/rpc.ts | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 19 deletions(-) create mode 100644 packages/bitcore-sh/src/rpc.ts diff --git a/packages/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/index.ts index 331d7f4e96..545d85dad5 100644 --- a/packages/bitcore-sh/src/index.ts +++ b/packages/bitcore-sh/src/index.ts @@ -1,9 +1,7 @@ import readline from 'readline'; import config from './config'; -const { CryptoRpc } = require('../../crypto-rpc'); +import { getRpc, rpcMethods } from './rpc'; -const rpcMethods = Object.getOwnPropertyNames(CryptoRpc.prototype) - .filter(p => typeof CryptoRpc.prototype[p] === 'function' && p !== 'constructor'); const context: string[] = []; const rl = readline.createInterface({ @@ -53,7 +51,6 @@ function getParams(func) { .filter((key: string) => key); } - rl.on('line', async (line) => { let args = line.split(' '); if (args[0] === 'use') { @@ -93,21 +90,6 @@ rl.on('line', async (line) => { nextCommand(); }); -function getRpc(chain: string, network: string) { - if (!config[chain][network]) - return; - const rpcConfig = config[chain][network]; - - return new CryptoRpc({ - chain, - protocol: rpcConfig.protocol || 'http', - host: rpcConfig.host, - port: rpcConfig.port, - user: rpcConfig.username, - pass: rpcConfig.password - }).get(chain); -} - function nextCommand() { rl.setPrompt(`${context.join(' ')}> `); rl.prompt(); diff --git a/packages/bitcore-sh/src/rpc.ts b/packages/bitcore-sh/src/rpc.ts new file mode 100644 index 0000000000..6aef0154ce --- /dev/null +++ b/packages/bitcore-sh/src/rpc.ts @@ -0,0 +1,20 @@ +import config from './config'; +const { CryptoRpc } = require('../../crypto-rpc'); + +export const getRpc = (chain: string, network: string) => { + if (!config[chain][network]) + return; + const rpcConfig = config[chain][network]; + + return new CryptoRpc({ + chain, + protocol: rpcConfig.protocol || 'http', + host: rpcConfig.host, + port: rpcConfig.port, + user: rpcConfig.username, + pass: rpcConfig.password + }).get(chain); +} + +export const rpcMethods = Object.getOwnPropertyNames(CryptoRpc.prototype) + .filter(p => typeof CryptoRpc.prototype[p] === 'function' && p !== 'constructor'); From d3958752254f4af7fea31e71a701d384f3ee6775 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 14:18:32 -0400 Subject: [PATCH 17/24] refactored parameter finding into rpc module --- packages/bitcore-sh/src/index.ts | 21 +++------------------ packages/bitcore-sh/src/rpc.ts | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/index.ts index 545d85dad5..ce677089c9 100644 --- a/packages/bitcore-sh/src/index.ts +++ b/packages/bitcore-sh/src/index.ts @@ -1,6 +1,6 @@ import readline from 'readline'; import config from './config'; -import { getRpc, rpcMethods } from './rpc'; +import { getRpc, getRpcMethodParams, rpcMethods } from './rpc'; const context: string[] = []; @@ -28,29 +28,14 @@ const rl = readline.createInterface({ hits = completions.filter(c => c.startsWith(args[2])); } } else if (args.length === 4) { - const rpc = getRpc(args[0].toUpperCase(), args[1]); - if (rpc) { - completions.push(getParams(rpc[args[2]])); - hits = completions.filter(c => c.startsWith(args[3])); - } + completions.push(...getRpcMethodParams(args[0], args[1], args[2])); + hits = completions.filter(c => c.startsWith(args[3])); } return [hits.length ? hits : completions, args[args.length - 1]]; } }); nextCommand(); -function getParams(func) { - const funcStr = func.toString(); - const match = funcStr.match(/\{\s*([^}]+)\s*\}/); - if (!match) return []; - - return match[1] - .split(',') - .map((key: string) => key.trim()) - .map((key: string) => '--' + (key.substring(0, key.indexOf(' ')) || key)) - .filter((key: string) => key); -} - rl.on('line', async (line) => { let args = line.split(' '); if (args[0] === 'use') { diff --git a/packages/bitcore-sh/src/rpc.ts b/packages/bitcore-sh/src/rpc.ts index 6aef0154ce..b0c0ecc26c 100644 --- a/packages/bitcore-sh/src/rpc.ts +++ b/packages/bitcore-sh/src/rpc.ts @@ -18,3 +18,18 @@ export const getRpc = (chain: string, network: string) => { export const rpcMethods = Object.getOwnPropertyNames(CryptoRpc.prototype) .filter(p => typeof CryptoRpc.prototype[p] === 'function' && p !== 'constructor'); + +export const getRpcMethodParams = (chain: string, network: string, rpcMethod: string): string[] => { + const rpc = getRpc(chain, network); + if (!rpc || !rpc[rpcMethod]) + return []; + const methodString = rpc[rpcMethod].toString(); + const match = methodString.match(/\{\s*([^}]+)\s*\}/); + if (!match) return []; + + return match[1] + .split(',') + .map((key: string) => key.trim()) + .map((key: string) => '--' + (key.substring(0, key.indexOf(' ')) || key)) + .filter((key: string) => key); +} From 3529170ddccaa0c5c65e56dc80df48687452e268 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 14:23:49 -0400 Subject: [PATCH 18/24] refactored completer out of object instantiation --- packages/bitcore-sh/src/index.ts | 60 ++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/packages/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/index.ts index ce677089c9..86c9742b76 100644 --- a/packages/bitcore-sh/src/index.ts +++ b/packages/bitcore-sh/src/index.ts @@ -2,40 +2,48 @@ import readline from 'readline'; import config from './config'; import { getRpc, getRpcMethodParams, rpcMethods } from './rpc'; +// all the commands prepended by the use command const context: string[] = []; +// complete the chain, network, rpcCommand, and paramers +const completer = (line: string) => { + let args = [...context, ...line.split(' ')]; + if (args.includes('use')) + args = args.filter(arg => arg !== 'use'); + + const completions: string[] = line.includes(' ') ? [] : ['use']; + let hits: string[] = []; + + if (args.length <= 1) { + completions.push(...Object.keys(config)); + hits = completions.filter(c => c.toLowerCase().startsWith(args[0].toLowerCase())); + } else if (args.length === 2) { + if (Object.keys(config).includes(args[0].toUpperCase())) { + completions.push(...Object.keys(config[args[0].toUpperCase()])); + hits = completions.filter(c => c.startsWith(args[1])); + } + } else if (args.length === 3) { + const rpc = getRpc(args[0].toUpperCase(), args[1]); + if (rpc) { + completions.push(...rpcMethods); + hits = completions.filter(c => c.startsWith(args[2])); + } + } else if (args.length === 4) { + completions.push(...getRpcMethodParams(args[0], args[1], args[2])); + hits = completions.filter(c => c.startsWith(args[3])); + } + return [hits.length ? hits : completions, args[args.length - 1]]; +} + +// repl environment const rl = readline.createInterface({ input: process.stdin, output: process.stdout, - completer: (line: string) => { - let args = [...context, ...line.split(' ')]; - if (args.includes('use')) - args = args.filter(arg => arg !== 'use'); - const completions: string[] = line.includes(' ') ? [] : ['use']; - let hits: string[] = []; - if (args.length <= 1) { - completions.push(...Object.keys(config)); - hits = completions.filter(c => c.toLowerCase().startsWith(args[0].toLowerCase())); - } else if (args.length === 2) { - if (Object.keys(config).includes(args[0].toUpperCase())) { - completions.push(...Object.keys(config[args[0].toUpperCase()])); - hits = completions.filter(c => c.startsWith(args[1])); - } - } else if (args.length === 3) { - const rpc = getRpc(args[0].toUpperCase(), args[1]); - if (rpc) { - completions.push(...rpcMethods); - hits = completions.filter(c => c.startsWith(args[2])); - } - } else if (args.length === 4) { - completions.push(...getRpcMethodParams(args[0], args[1], args[2])); - hits = completions.filter(c => c.startsWith(args[3])); - } - return [hits.length ? hits : completions, args[args.length - 1]]; - } + completer }); nextCommand(); +// handle all user commands rl.on('line', async (line) => { let args = line.split(' '); if (args[0] === 'use') { From b85f40047236017cb022e536f50d6d1f38e28834 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 14:36:31 -0400 Subject: [PATCH 19/24] updated package.json and renamed index -> run --- packages/bitcore-sh/package.json | 29 ++++++++++++++------ packages/bitcore-sh/src/{index.ts => run.ts} | 0 2 files changed, 21 insertions(+), 8 deletions(-) rename packages/bitcore-sh/src/{index.ts => run.ts} (100%) diff --git a/packages/bitcore-sh/package.json b/packages/bitcore-sh/package.json index 38458a068b..10fe11616d 100644 --- a/packages/bitcore-sh/package.json +++ b/packages/bitcore-sh/package.json @@ -1,14 +1,27 @@ { "name": "bitcore-sh", + "description": "", + "author": "BitPay, Inc", + "license": "MIT", "version": "1.0.0", - "main": "index.js", "scripts": { - "start": "npm run compile && node build/index.js", - "compile": "rm -rf build && tsc", - "tsc": "tsc" + "start": "npm run compile && node build/run.js", + "compile": "npm run clean && npm run tsc", + "clean": "rm -rf build", + "tsc": "tsc", + "fix:errors": "eslint --fix --quiet .", + "fix:all": "eslint --fix .", + "fix": "npm run fix:errors" }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "" + "keywords": [ + "rpc", + "typescript", + "bitcoin" + ], + "contributors": [ + { + "name": "Micah Maphet", + "email": "mmaphet@bitpay.com" + } + ] } diff --git a/packages/bitcore-sh/src/index.ts b/packages/bitcore-sh/src/run.ts similarity index 100% rename from packages/bitcore-sh/src/index.ts rename to packages/bitcore-sh/src/run.ts From 072f202d14715262931d38e1f2c67d96944964df Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 14:43:13 -0400 Subject: [PATCH 20/24] exit logic --- packages/bitcore-sh/src/run.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/bitcore-sh/src/run.ts b/packages/bitcore-sh/src/run.ts index 86c9742b76..2d895cdb02 100644 --- a/packages/bitcore-sh/src/run.ts +++ b/packages/bitcore-sh/src/run.ts @@ -4,6 +4,7 @@ import { getRpc, getRpcMethodParams, rpcMethods } from './rpc'; // all the commands prepended by the use command const context: string[] = []; +let exiting = false; // complete the chain, network, rpcCommand, and paramers const completer = (line: string) => { @@ -56,6 +57,8 @@ rl.on('line', async (line) => { } nextCommand(); return; + } else if (args[0] === 'exit') { + process.exit(0); } args = [...context, ...args]; @@ -83,7 +86,17 @@ rl.on('line', async (line) => { nextCommand(); }); +rl.on('SIGINT', () => { + if (exiting) { + process.exit(0); + } + rl.setPrompt(`${context.join(' ')}> \n(To exit, press Ctrl+C again or Ctrl+D or type exit)`); + rl.prompt(); + exiting = true; +}); + function nextCommand() { rl.setPrompt(`${context.join(' ')}> `); rl.prompt(); + exiting = false; } From 79f08a5450e8e9ff706694712eb2c830ad50dd11 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 14:45:53 -0400 Subject: [PATCH 21/24] eslint fix --- packages/bitcore-sh/src/rpc.ts | 4 ++-- packages/bitcore-sh/src/run.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/bitcore-sh/src/rpc.ts b/packages/bitcore-sh/src/rpc.ts index b0c0ecc26c..bd9b243d4e 100644 --- a/packages/bitcore-sh/src/rpc.ts +++ b/packages/bitcore-sh/src/rpc.ts @@ -14,7 +14,7 @@ export const getRpc = (chain: string, network: string) => { user: rpcConfig.username, pass: rpcConfig.password }).get(chain); -} +}; export const rpcMethods = Object.getOwnPropertyNames(CryptoRpc.prototype) .filter(p => typeof CryptoRpc.prototype[p] === 'function' && p !== 'constructor'); @@ -32,4 +32,4 @@ export const getRpcMethodParams = (chain: string, network: string, rpcMethod: st .map((key: string) => key.trim()) .map((key: string) => '--' + (key.substring(0, key.indexOf(' ')) || key)) .filter((key: string) => key); -} +}; diff --git a/packages/bitcore-sh/src/run.ts b/packages/bitcore-sh/src/run.ts index 2d895cdb02..45bde43a55 100644 --- a/packages/bitcore-sh/src/run.ts +++ b/packages/bitcore-sh/src/run.ts @@ -34,7 +34,7 @@ const completer = (line: string) => { hits = completions.filter(c => c.startsWith(args[3])); } return [hits.length ? hits : completions, args[args.length - 1]]; -} +}; // repl environment const rl = readline.createInterface({ From 39166ba79fc1f3c8aed148b123d95424365624ca Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 14:57:57 -0400 Subject: [PATCH 22/24] refactored rpc functions into RPC singleton --- packages/bitcore-sh/src/rpc.ts | 62 +++++++++++++++++++--------------- packages/bitcore-sh/src/run.ts | 12 +++---- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/packages/bitcore-sh/src/rpc.ts b/packages/bitcore-sh/src/rpc.ts index bd9b243d4e..1975ef604a 100644 --- a/packages/bitcore-sh/src/rpc.ts +++ b/packages/bitcore-sh/src/rpc.ts @@ -1,35 +1,41 @@ import config from './config'; +// eslint-disable-next-line @typescript-eslint/no-require-imports const { CryptoRpc } = require('../../crypto-rpc'); -export const getRpc = (chain: string, network: string) => { - if (!config[chain][network]) - return; - const rpcConfig = config[chain][network]; +class Rpc { + methods: string[] = Object.getOwnPropertyNames(CryptoRpc.prototype) + .filter(p => typeof CryptoRpc.prototype[p] === 'function' && p !== 'constructor'); - return new CryptoRpc({ - chain, - protocol: rpcConfig.protocol || 'http', - host: rpcConfig.host, - port: rpcConfig.port, - user: rpcConfig.username, - pass: rpcConfig.password - }).get(chain); -}; + get(chain: string, network: string) { + if (!config[chain][network]) + return; + const rpcConfig = config[chain][network]; -export const rpcMethods = Object.getOwnPropertyNames(CryptoRpc.prototype) - .filter(p => typeof CryptoRpc.prototype[p] === 'function' && p !== 'constructor'); + return new CryptoRpc({ + chain, + protocol: rpcConfig.protocol || 'http', + host: rpcConfig.host, + port: rpcConfig.port, + user: rpcConfig.username, + pass: rpcConfig.password + }).get(chain); + }; -export const getRpcMethodParams = (chain: string, network: string, rpcMethod: string): string[] => { - const rpc = getRpc(chain, network); - if (!rpc || !rpc[rpcMethod]) - return []; - const methodString = rpc[rpcMethod].toString(); - const match = methodString.match(/\{\s*([^}]+)\s*\}/); - if (!match) return []; + getMethodParams(chain: string, network: string, rpcMethod: string): string[] { + const rpc = this.get(chain, network); + if (!rpc || !rpc[rpcMethod]) + return []; + const methodString = rpc[rpcMethod].toString(); + const match = methodString.match(/\{\s*([^}]+)\s*\}/); + if (!match) return []; - return match[1] - .split(',') - .map((key: string) => key.trim()) - .map((key: string) => '--' + (key.substring(0, key.indexOf(' ')) || key)) - .filter((key: string) => key); -}; + return match[1] + .split(',') + .map((key: string) => key.trim()) + .map((key: string) => '--' + (key.substring(0, key.indexOf(' ')) || key)) + .filter((key: string) => key); + }; +} + +const RPC = new Rpc(); +export default RPC; diff --git a/packages/bitcore-sh/src/run.ts b/packages/bitcore-sh/src/run.ts index 45bde43a55..8d59c40315 100644 --- a/packages/bitcore-sh/src/run.ts +++ b/packages/bitcore-sh/src/run.ts @@ -1,6 +1,6 @@ import readline from 'readline'; import config from './config'; -import { getRpc, getRpcMethodParams, rpcMethods } from './rpc'; +import RPC from './rpc'; // all the commands prepended by the use command const context: string[] = []; @@ -24,13 +24,13 @@ const completer = (line: string) => { hits = completions.filter(c => c.startsWith(args[1])); } } else if (args.length === 3) { - const rpc = getRpc(args[0].toUpperCase(), args[1]); + const rpc = RPC.get(args[0].toUpperCase(), args[1]); if (rpc) { - completions.push(...rpcMethods); + completions.push(...RPC.methods); hits = completions.filter(c => c.startsWith(args[2])); } } else if (args.length === 4) { - completions.push(...getRpcMethodParams(args[0], args[1], args[2])); + completions.push(...RPC.getMethodParams(args[0], args[1], args[2])); hits = completions.filter(c => c.startsWith(args[3])); } return [hits.length ? hits : completions, args[args.length - 1]]; @@ -77,7 +77,7 @@ rl.on('line', async (line) => { i--; } - const rpc = getRpc(chain, network); + const rpc = RPC.get(chain, network); if (rpc) console.log(await rpc[command](rpcArgs)); } catch (e) { @@ -90,7 +90,7 @@ rl.on('SIGINT', () => { if (exiting) { process.exit(0); } - rl.setPrompt(`${context.join(' ')}> \n(To exit, press Ctrl+C again or Ctrl+D or type exit)`); + rl.setPrompt(`${context.join(' ')}> \n(To exit, press Ctrl+C again or Ctrl+D or type exit)\n${context.join(' ')}> `); rl.prompt(); exiting = true; }); From d973a01f9a6f03fe1e4eff873bc06f0f657deebb Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 15:08:09 -0400 Subject: [PATCH 23/24] add README.md --- packages/bitcore-sh/README.md | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 packages/bitcore-sh/README.md diff --git a/packages/bitcore-sh/README.md b/packages/bitcore-sh/README.md new file mode 100644 index 0000000000..21cccd8bea --- /dev/null +++ b/packages/bitcore-sh/README.md @@ -0,0 +1,56 @@ +# Bitcore SH +A repl environment to interact with crypto-rpc + +## Usage +In packages/bitcore-sh run: +``` +npm start +``` +Starts repl environment +``` +> +``` +Run a command +``` +> BTC regtest getTip +{ + height: 1280, + hash: '1cb2e03e7c644f6346db5d0ca06f48bb989e3dc4088ce348bc0817a89cbfd6aa' +} +``` +Run a command with arguments +``` +> BTC regtest getBlock --hash 1cb2e03e7c644f6346db5d0ca06f48bb989e3dc4088ce348bc0817a89cbfd6aa +{ + hash: '1cb2e03e7c644f6346db5d0ca06f48bb989e3dc4088ce348bc0817a89cbfd6aa', + confirmations: 1, + height: 1280, + version: 536870912, + versionHex: '20000000', + merkleroot: 'b7fe62d037fe3577ba8531676ad772dad5879e0f8fee2d79e4707883bef0bb1d', + time: 1778809084, + mediantime: 1778807940, + nonce: 1, + bits: '207fffff', + difficulty: 4.656542373906925e-10, + chainwork: '0000000000000000000000000000000000000000000000000000000000000a02', + nTx: 1, + previousblockhash: '6d044b0b7b84518f601c8b84c7e65b5387a9afa440968ae1c8bbe9062cf8105a', + strippedsize: 214, + size: 250, + weight: 892, + tx: [ + 'b7fe62d037fe3577ba8531676ad772dad5879e0f8fee2d79e4707883bef0bb1d' + ] +} +``` +The `use` command speeds up execution by appending arguments to the start of the following commands +``` +> use BTC regtest +BTC regtest> getTip +{ + height: 1280, + hash: '1cb2e03e7c644f6346db5d0ca06f48bb989e3dc4088ce348bc0817a89cbfd6aa' +} +``` +`bitcore-sh` includes command completion, try using tab From f4c4f3c23d13d768a19cd91e0bd4f6d1990d2521 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Wed, 20 May 2026 15:29:49 -0400 Subject: [PATCH 24/24] config searching for bitcore-sh --- packages/bitcore-sh/README.md | 2 +- packages/bitcore-sh/src/config.ts | 50 ++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/bitcore-sh/README.md b/packages/bitcore-sh/README.md index 21cccd8bea..243389001f 100644 --- a/packages/bitcore-sh/README.md +++ b/packages/bitcore-sh/README.md @@ -1,5 +1,5 @@ # Bitcore SH -A repl environment to interact with crypto-rpc +A repl environment to interact with `crypto-rpc` using the rpcs in the bitcore config ## Usage In packages/bitcore-sh run: diff --git a/packages/bitcore-sh/src/config.ts b/packages/bitcore-sh/src/config.ts index 4440f89301..0262b98774 100644 --- a/packages/bitcore-sh/src/config.ts +++ b/packages/bitcore-sh/src/config.ts @@ -1,4 +1,6 @@ import fs from 'fs'; +import { homedir } from 'os'; +import path from 'path'; interface ConfigType { [chain: string]: { @@ -12,11 +14,51 @@ interface ConfigType { }; }; -const path: string = process.env.BITCORE_CONFIG_PATH || ''; -const rawConfig: object = JSON.parse(fs.readFileSync(path).toString()).bitcoreNode.chains; +const args = process.argv.slice(2); +const DEFAULT_CONFIG = path.join(__dirname, '../../../../bitcore.config.json'); +let bitcoreConfigPath: string; + +const pathIndex = args.indexOf('--path'); +if (pathIndex !== -1) { + bitcoreConfigPath = args[pathIndex + 1]; +} else { + bitcoreConfigPath = process.env.BITCORE_CONFIG_PATH || DEFAULT_CONFIG +} + +if (bitcoreConfigPath[0] === '~') { + bitcoreConfigPath = bitcoreConfigPath.replace('~', homedir()); +} + +if (!fs.existsSync(bitcoreConfigPath)) { + throw new Error(`No bitcore config exists at ${bitcoreConfigPath}`); +} + +const bitcoreConfigStat = fs.statSync(bitcoreConfigPath); + +if (bitcoreConfigStat.isDirectory()) { + if (!fs.existsSync(path.join(bitcoreConfigPath, 'bitcore.config.json'))) { + throw new Error(`No bitcore config exists in directory ${bitcoreConfigPath}`); + } + bitcoreConfigPath = path.join(bitcoreConfigPath, 'bitcore.config.json'); +} + +let rawBitcoreConfig; +try { + rawBitcoreConfig = fs.readFileSync(bitcoreConfigPath).toString(); +} catch (error) { + throw new Error(`Error in loading bitcore config\nFound file at ${bitcoreConfigPath}\n${error}`); +} + +let bitcoreConfig: object; +try { + bitcoreConfig = JSON.parse(rawBitcoreConfig).bitcoreNode.chains; +} catch (error) { + throw new Error(`Error in parsing bitcore config\nFound and loaded file at ${bitcoreConfigPath}\n${error}`); +} + const config: ConfigType = {}; -for (const chain in rawConfig) { - const chainConfig = rawConfig[chain]; +for (const chain in bitcoreConfig) { + const chainConfig = bitcoreConfig[chain]; config[chain] = {}; for (const network in chainConfig) { const networkConfig = chainConfig[network];