├── .gitignore ├── README.md ├── bin ├── cat.js ├── echo.js ├── history.js ├── init.js ├── ls.js ├── route.js ├── useradd.js └── whoami.js ├── bitsh.gif ├── bitsh.png ├── cli.js ├── index.js ├── lib ├── get.js ├── key.js └── post.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .bitcom 3 | node_modules 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitsh 2 | 3 | Bitcom Shell 4 | 5 | [What is Bitcom?](https://bitcom.bitdb.network) 6 | 7 | ![bitsh](./bitsh.gif) 8 | 9 | # 1. What? 10 | 11 | bitsh is a shell interface that lets you "sign into" Bitcom, a virtual unix computer on Bitcoin. 12 | 13 | The main use case for Bitcom currently is to manage and publish OP_RETURN based application protocols through the Bitcom decentralized directory. 14 | 15 | Each account effectively represents an application protocol, and you can customize the home folder of your application protocol to globally publish the protocol to the decentralized Bitcom directory. 16 | 17 | Learn more [here](https://bitcom.planaria.network). 18 | 19 | # 2. Install 20 | 21 | Install bitsh globally. 22 | 23 | ``` 24 | npm install -g bitsh 25 | ``` 26 | 27 | # 2. Usage 28 | 29 | Run bitsh 30 | 31 | ``` 32 | bitsh 33 | ``` 34 | 35 | # 3. How is Bitsh related to Bitcom? 36 | 37 | There is already an NPM package named [bitcom](https://bitcom.planaria.network/#/?id=install), which is a precursor of the bitsh module. 38 | 39 | Both packages let you access Bitcom, but bitsh is much more powerful as it's not just a one-off command line application, but an entire shell interface you log into. 40 | 41 | Think of the bitcom package as a one-off command line app like git, whereas bitsh is like Bash, but for Bitcom. 42 | 43 | # 4. Key management 44 | 45 | Bitsh is also a full fledged HD wallet. Bitsh creates a hidden folder named `.bitcom` under your home directory. This folder is used to maintain the generated extended private/public keys you can use to log into [Bitcom](https://bitcom.planaria.network) and publish your application protocols. 46 | 47 | The folder structure looks like this: 48 | 49 | ``` 50 | ~/.bitcom 51 | .seed 52 | /hd 53 | /0 54 | .bit 55 | /1 56 | .bit 57 | .. 58 | /wif 59 | /[Addr1] 60 | .bit 61 | /[Addr2] 62 | .bit 63 | .. 64 | ``` 65 | 66 | 1. The `.bitcom` folder is automatically created in your home directory when you first launch bitsh 67 | 2. The `.bitcom` folder contains a single `.seed` file which contains the seed HD keys from which you will derive your real keys from. 68 | 3. The `.bitcom/hd` folder maintains the generated keys, incrementing the index every time you create a new key. 69 | 4. The `.bitcom/wif` folder maintains NON-HD keys you import. You can import external keys simply by running bitsh with `bitsh [WIF to import]`. 70 | 71 | 72 | # 5. Commands 73 | 74 | You can list the supported commands by typing in 75 | 76 | ``` 77 | help 78 | ``` 79 | 80 | Here are the currently supported commands: 81 | 82 | ## a. cat 83 | 84 | You can either use `>` or `to` to import `bit://`, `b://`, `c://` URIs to a Bitcom local file 85 | 86 | ``` 87 | cat X > Y 88 | ``` 89 | 90 | is equivalent to 91 | 92 | ``` 93 | cat X to Y 94 | ``` 95 | 96 | ## b. echo 97 | 98 | You can assign string content to a Bitcom local file 99 | 100 | ``` 101 | echo "hello world" > description 102 | ``` 103 | 104 | is equivalent to 105 | 106 | ``` 107 | echo "hello world" to description 108 | ``` 109 | 110 | ## c. history 111 | 112 | The history command lists all the Bitcom activities for the account. 113 | 114 | ``` 115 | history 116 | ``` 117 | 118 | Or you can run the command for another account: 119 | 120 | ``` 121 | history ~19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut 122 | ``` 123 | 124 | ## d. ls 125 | 126 | Display all Bitcom local file names 127 | 128 | ``` 129 | ls 130 | ``` 131 | 132 | or display all files for another account: 133 | 134 | ``` 135 | ls ~9HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut 136 | ``` 137 | 138 | ## e. route 139 | 140 | Enable or Add [bit:// routes](https://bit.planaria.network) to application protocols. 141 | 142 | Learn more at [bit:// specification](https://bit.planaria.network). 143 | 144 | ## f. useradd 145 | 146 | Register your account on Bitcom. This signals that your account is taken. 147 | 148 | ``` 149 | useradd 150 | ``` 151 | 152 | ## g. whoami 153 | 154 | Display current account information 155 | 156 | ``` 157 | whoami 158 | ``` 159 | 160 | -------------------------------------------------------------------------------- /bin/cat.js: -------------------------------------------------------------------------------- 1 | /********************************** 2 | * 3 | * cat bit://[..]/[...] to filename 4 | * 5 | **********************************/ 6 | const post = require('../lib/post') 7 | const get = require('../lib/get') 8 | module.exports = function(args, done) { 9 | /** 10 | * 11 | * args: { 12 | * bitfile: [bit:// addressed file URI], 13 | * to: (to|>), 14 | * localfile: [local file name] 15 | * } 16 | * 17 | */ 18 | if (args.to && (args.to === '>' || args.to === 'to') && args.localfile) { 19 | post(["$", "cat", args.bitfile, args.to, args.localfile], function(err, tx) { 20 | done(err, tx) 21 | }) 22 | } else { 23 | done({ 24 | message: "the token must be either '>' or 'to'" 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bin/echo.js: -------------------------------------------------------------------------------- 1 | /********************************** 2 | * 3 | * echo B to name 4 | * 5 | **********************************/ 6 | const post = require('../lib/post') 7 | module.exports = function(args, done) { 8 | /** 9 | * 10 | * args: { 11 | * text: [any string], 12 | * to: (to|>), 13 | * localfile: [local file name] 14 | * } 15 | * 16 | */ 17 | if (args.to === '>' || args.to === 'to') { 18 | post(["$", "echo", args.text, args.to, args.localfile], function(err, tx) { 19 | done(err, tx) 20 | }) 21 | } else { 22 | done({ 23 | message: "\n ## Error: the token must be either '>' or 'to'" 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bin/history.js: -------------------------------------------------------------------------------- 1 | const get = require('../lib/get') 2 | module.exports = function(args, done) { 3 | let query = { 4 | v: 3, 5 | q: { 6 | find: { 7 | "in.e.a": process.env.address, 8 | "out.s1": "$" 9 | }, 10 | sort: { "blk.i": 1 } 11 | } 12 | } 13 | if (args.address && /^~[A-Za-z0-1]+/.test(args.address)) { 14 | query.q.find["in.e.a"] = args.address.slice(1) 15 | } 16 | get(query, function(r) { 17 | let res = [].concat(r.c).concat(r.u) 18 | if (res.length > 0) { 19 | let result = res.map(function(item) { 20 | let out = item.out.filter(function(o) { 21 | return o 22 | })[0] 23 | let keys = Object.keys(out).filter(function(k) { 24 | return /^s[1-9]+/.test(k) 25 | }) 26 | let line = keys.map(function(k) { 27 | return out[k] 28 | }).join(" ") 29 | return line; 30 | }).join("\n") 31 | done(null, result) 32 | } else { 33 | done(null, "") 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /bin/init.js: -------------------------------------------------------------------------------- 1 | const datapay = require('datapay') 2 | const qrcode = require('qrcode-terminal'); 3 | const fs = require('fs') 4 | const createKey = function() { 5 | let privateKey = new datapay.bsv.PrivateKey(); 6 | let address = privateKey.toAddress(); 7 | let pubKey = privateKey.toPublicKey(); 8 | return {PRIVATE: privateKey.toWIF(), ADDRESS: address.toString(), PUBLIC: pubKey.toString()} 9 | } 10 | module.exports = function() { 11 | let _path = process.cwd() + "/.bit" 12 | if (fs.existsSync(_path)) { 13 | console.log("Bitcom already initiated. Use a folder with no .bit file.") 14 | return; 15 | } 16 | let stream = fs.createWriteStream(_path) 17 | let keys = createKey() 18 | stream.once('open', function(fd) { 19 | let content = Object.keys(keys).map(function(key) { 20 | return key + "=" + keys[key] 21 | }).join("\n") 22 | stream.write(content) 23 | stream.end(); 24 | console.log("#################################################") 25 | console.log("##") 26 | console.log("## Welcome to Bitcom, a Bitcoin Unix Computer") 27 | console.log("##") 28 | console.log("## Created a Bitcoin Key Pair + Address") 29 | console.log("## [Look inside .bit file]") 30 | console.log("##") 31 | console.log("## Address: " + keys.ADDRESS) 32 | console.log("##") 33 | console.log("#################################################\n") 34 | qrcode.generate("bitcoin:"+keys.ADDRESS, function(code) { 35 | console.log(code) 36 | datapay.connect('https://bchsvexplorer.com').address(keys.ADDRESS, function(err, info) { 37 | if (err) { 38 | console.log("Error: ", err) 39 | } else { 40 | balance = info.balance 41 | console.log("\nbalance: ", info.balance) 42 | console.log("\nOption 1. Charge the address with QR code, with small amount of Bitcoin SV to get started.\n") 43 | let payload = { 44 | "to": keys.ADDRESS, 45 | "editable": true, 46 | "currency": "USD", 47 | "type": "tip" 48 | } 49 | let str = JSON.stringify(payload); 50 | let b64 = Buffer.from(str).toString("base64"); 51 | let url = "https://button.bitdb.network/#" + b64; 52 | console.log("Option 2. Charge with Moneybutton:\n" + url + "\n"); 53 | } 54 | }); 55 | }) 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /bin/ls.js: -------------------------------------------------------------------------------- 1 | const get = require('../lib/get') 2 | module.exports = function(args, done) { 3 | let query = { 4 | v: 3, 5 | q: { 6 | find: { 7 | "in.e.a": process.env.address, 8 | "out.s1": "$", 9 | "out.s2": { 10 | "$in": ["echo", "cat"] 11 | } 12 | } 13 | } 14 | } 15 | if (args.address && /^~[A-Za-z0-1]+/.test(args.address)) { 16 | query.q.find["in.e.a"] = args.address.slice(1) 17 | } 18 | get(query, function(r) { 19 | let res = [].concat(r.u).concat(r.c) 20 | if (res.length > 0) { 21 | let files = new Set(); 22 | res.forEach(function(item) { 23 | let out = item.out.filter(function(o) { 24 | return o 25 | })[0] 26 | if (out.s5) { 27 | files.add(out.s5) 28 | } 29 | }) 30 | let lines = Array.from(files).join("\n") 31 | let more = "\n< view on bterm: https://bterm.network/#address=" + process.env.address + " >"; 32 | done(null, lines+more) 33 | } else { 34 | done(null, "") 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /bin/route.js: -------------------------------------------------------------------------------- 1 | /********************************** 2 | * 3 | * [Syntax] 4 | * route enable $PATH 5 | * 6 | * [Example] 7 | * route enable /tx/:tx 8 | * 9 | * [Syntax] 10 | * route add $ADDRESS $PATH $ENDPOINT 11 | * 12 | * [Example] 13 | * route add 19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut /:tx https://bico.media/${tx} 14 | * 15 | **********************************/ 16 | const post = require('../lib/post') 17 | const get = require('../lib/get') 18 | const endpoint = "https://babel.bitdb.network/q/1DHDifPvtPgKFPZMRSxmVHhiPvFmxZwbfh/" 19 | const apikey = "1KJPjd3p8khnWZTkjhDYnywLB2yE1w5BmU" 20 | const axios = require('axios') 21 | module.exports = function(args, done) { 22 | /** 23 | * 24 | * Two modes: (add|enable) 25 | * 26 | * Mode 0: ls 27 | * 28 | * args: { 29 | * action: "ls", 30 | * address: <~Bitcom Address>|empty 31 | * } 32 | * 33 | * Mode 1: enable 34 | * 35 | * args: { 36 | * arg0: "enable", 37 | * arg1: [The route path to enable for the current Bitcom user], 38 | * } 39 | * 40 | * Mode 2: add 41 | * 42 | * args: { 43 | * arg0: "add", 44 | * arg1: [The Bitcom address to attach to], 45 | * arg2: [The Bitcom route to attach to. Must have been enabled by the admin], 46 | * arg3: [The service endpoint to register] 47 | * } 48 | * 49 | */ 50 | 51 | if (args.arg0 === 'ls') { 52 | let query = { 53 | v: 3, 54 | q: { 55 | find: { 56 | "out.s1": "$", 57 | "out.s2": "route", 58 | "out.s3": "enable" 59 | } 60 | } 61 | } 62 | if (args.arg1) { 63 | let address = args.arg1; 64 | if (/^~[A-Za-z0-1]+/.test(address)) { 65 | query.q.find["in.e.a"] = address.slice(1) 66 | } else { 67 | done({ 68 | message: "The [address] should be prefixed with '~'" 69 | }) 70 | return; 71 | } 72 | } else { 73 | if (process.env.address) { 74 | query.q.find["in.e.a"] = process.env.address 75 | } 76 | } 77 | get(query, function(r) { 78 | let res = [].concat(r.u).concat(r.c) 79 | if (res.length > 0) { 80 | let result = res.map(function(item) { 81 | return item.out[0].s4 82 | }).join("\n") 83 | done(null, result) 84 | } else { 85 | done({ 86 | message: "No such route exists" 87 | }) 88 | } 89 | }) 90 | } else if (args.arg0 === 'enable') { 91 | post(["$", "route", "enable", args.arg1], function(err, tx) { 92 | done(err, tx) 93 | }) 94 | } else if (args.arg0 === 'add') { 95 | if (process.env.address === args.arg1) { 96 | done({ 97 | message: "it is not recommended for admins to add routes, adding routes are for service providers. If you want to act as a service provider, create a separate account (create a new folder and run 'bit init') and run 'route add' again." 98 | }) 99 | } else { 100 | // check that the route is enabled 101 | let address = args.arg1 102 | let routePath = args.arg2 103 | let routeEndpoint = args.arg3 104 | let query = { 105 | "v": 3, 106 | "q": { 107 | "db": ["c"], 108 | "find": { 109 | "in.e.a": address, 110 | "out.s1": "$", 111 | "out.s2": "route", 112 | "out.s3": "enable", 113 | "out.s4": routePath 114 | }, 115 | "project": { 116 | "out.s1": 1, "out.s2": 1, "out.s3": 1, "out.s4": 1, "out.s5": 1 117 | } 118 | } 119 | }; 120 | let s = JSON.stringify(query); 121 | let b64 = Buffer.from(s).toString('base64'); 122 | let url = endpoint + b64; 123 | let header = { headers: { key: apikey } }; 124 | axios.get(url, header).then(function(r) { 125 | console.log("response = ", r.data) 126 | if (r.data.c && r.data.c.length > 0) { 127 | console.log("route exists:", address, routePath) 128 | post(["$", "route", "add", address, routePath, routeEndpoint], function(err, tx) { 129 | done(err, tx) 130 | }) 131 | } else { 132 | done({ 133 | message: "route doesn't exist " + address + " " + routePath 134 | }) 135 | } 136 | }) 137 | } 138 | } else if (args.arg0 === 'delete') { 139 | if (args.arg1 && args.arg2) { 140 | if (process.env.address === args.arg1) { 141 | done({ 142 | message: "it is not recommended for admins to add routes, adding routes are for service providers. If you want to act as a service provider, create a separate account (create a new folder and run 'bit init') and run 'route add' again." 143 | }) 144 | } else { 145 | post(["$", "route", "delete", args.arg1, args.arg2], function(err, tx) { 146 | done(err, tx) 147 | }) 148 | } 149 | } else { 150 | done({ 151 | message: "Syntax: '$ route delete [address] [path]'" 152 | }) 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /bin/useradd.js: -------------------------------------------------------------------------------- 1 | const post = require('../lib/post') 2 | module.exports = function(args, done) { 3 | post(["$", "useradd", process.env.address], function(err, tx) { 4 | done(err, tx) 5 | }) 6 | } 7 | -------------------------------------------------------------------------------- /bin/whoami.js: -------------------------------------------------------------------------------- 1 | const qrcode = require('qrcode-terminal'); 2 | const datapay = require('datapay') 3 | module.exports = function(args, done) { 4 | if (process.env.address) { 5 | let message = ` 6 | ################################################################################# 7 | ## 8 | ## Welcome to Bitcom, a Bitcoin Unix Computer 9 | ## 10 | ## Here is your Bitcoin Address 11 | ## [Look inside .bitcom/.seed file for Seed HD Keypair] 12 | ## 13 | ## Address: ${process.env.address} 14 | ## BTerm Dashboard: https://bterm.network/#address=${process.env.address} 15 | ## 16 | #################################################################################\n\n`; 17 | qrcode.generate("bitcoin:"+process.env.address, function(code) { 18 | message += code; 19 | datapay.connect('https://bchsvexplorer.com').address(process.env.address, function(err, info) { 20 | if (err) { 21 | done(err, null) 22 | } else { 23 | balance = info.balance 24 | message += ("\n\nbalance: " + info.balance + "\n") 25 | message += ("\nOption 1. Charge the address with QR code, with small amount of Bitcoin SV to get started.\n") 26 | let payload = { 27 | "to": process.env.address, 28 | "editable": true, 29 | "currency": "USD", 30 | "type": "tip" 31 | } 32 | let str = JSON.stringify(payload); 33 | let b64 = Buffer.from(str).toString("base64"); 34 | let url = "https://button.bitdb.network/#" + b64; 35 | message += ("Option 2. Charge with Moneybutton:\n" + url + "\n"); 36 | done(null, message) 37 | } 38 | }); 39 | }) 40 | } else { 41 | done("You haven't generated a keypair. Look inside the .bit file, or generate a new one with 'bit init'", null) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bitsh.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interplanaria/bitsh/b818cd622de86220963658963149ee613b292cbd/bitsh.gif -------------------------------------------------------------------------------- /bitsh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interplanaria/bitsh/b818cd622de86220963658963149ee613b292cbd/bitsh.png -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var v = require('vorpal')() 3 | const path = require('path') 4 | const fs = require('fs') 5 | const get = require('./lib/get') 6 | const homedir = require('os').homedir(); 7 | const bitcomPath = homedir + "/.bitcom" 8 | const programs = {} 9 | 10 | // Router Definition 11 | const router = function(args, callback) { 12 | let cmd = this.commandObject._name; 13 | let self = this; 14 | programs[cmd](args, function(err, result) { 15 | if (err) { 16 | self.log("\n ## Error: " + err.message); 17 | v.exec("help " + cmd) 18 | } else { 19 | self.log(result) 20 | } 21 | callback(); 22 | }) 23 | } 24 | 25 | // Route Commands to Programs 26 | const init = function(type, user_index) { 27 | require('dotenv').config({path: bitcomPath + "/" + type + "/" + user_index + "/.bit"}) 28 | v.command('useradd', 'creates a new account').action(router) 29 | v.command('whoami', 'displays the current user').action(router) 30 | v.command('route [arg1] [arg2] [arg3]', 'route related actions').action(router) 31 | v.command('cat ', 'Writes content to ').action(router) 32 | v.command('echo ', 'Writes to ').action(router) 33 | v.command('ls [address]', 'list files').action(router) 34 | v.command('history [address]', 'display history').action(router) 35 | 36 | // Load Programs 37 | fs.readdir(__dirname + "/bin", async function(err, items) { 38 | for (let i=0; i", "to"] 55 | }, 56 | "out.s5": "name" 57 | } 58 | } 59 | }, function(r) { 60 | let items = [].concat(r.u).concat(r.c) 61 | if (items.length > 0) { 62 | let origin = items[0].out[0].s3; 63 | v.delimiter(origin + "@bitcom$").show(); 64 | } else { 65 | let origin = process.env.address 66 | let shortened = origin.slice(0,7) + "..." 67 | v.delimiter(shortened + '@bitcom$').show(); 68 | } 69 | }) 70 | }) 71 | }); 72 | } 73 | module.exports = { 74 | login: init 75 | } 76 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const fs = require('fs') 3 | const mkdirp = require('mkdirp'); 4 | const term = require( 'terminal-kit' ).terminal; 5 | const cli = require('./cli') 6 | const key = require('./lib/key') 7 | const bip44 = "m/44'/0'/0'/0/" 8 | const homedir = require('os').homedir(); 9 | const bitcomPath = homedir + "/.bitcom" 10 | const hdPath = bitcomPath + "/hd" 11 | const wifPath = bitcomPath + "/wif" 12 | const register = function() { 13 | mkdirp(bitcomPath, async function(err) { 14 | let keys = await key.generate() 15 | console.log(`\ngenerated [${bip44}${keys.index}] ${keys.value.address}`) 16 | console.log("") 17 | cli.login("hd", keys.index) 18 | }) 19 | } 20 | const users = async function() { 21 | console.log("") 22 | console.log("BITCOM LOGIN:") 23 | let hdItems = await key.ls("hd") 24 | let wifItems = await key.ls("wif") 25 | let items = [].concat(hdItems).concat(wifItems) 26 | let list = ["[ + new account ]"].concat(items.map(function(item) { 27 | if (typeof item.index === "undefined") { 28 | // wif 29 | return `[wif] ${item.value}` 30 | } else { 31 | // hd 32 | return `[${bip44}${item.index}] ${item.value}` 33 | } 34 | })) 35 | term.singleColumnMenu(list, function( error , response ) { 36 | if (response.selectedIndex === 0) { 37 | register() 38 | } else { 39 | let item = items[response.selectedIndex-1]; 40 | console.log("\nLogging in: " + item.value + "\n") 41 | if (typeof item.index !== "undefined") { 42 | cli.login("hd", item.index) 43 | } else { 44 | cli.login("wif", item.value) 45 | } 46 | } 47 | }); 48 | } 49 | const init = function() { 50 | if (process.argv.length >= 3) { 51 | // importing 52 | let wif = process.argv[2]; 53 | key.importer(wif).then(function(keys) { 54 | console.log("Imported keys = ", keys) 55 | cli.login("wif", keys.value.address) 56 | }) 57 | } else { 58 | users() 59 | } 60 | } 61 | init() 62 | -------------------------------------------------------------------------------- /lib/get.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const endpoint = "https://babel.bitdb.network/q/1DHDifPvtPgKFPZMRSxmVHhiPvFmxZwbfh/" 3 | const apikey = "1KJPjd3p8khnWZTkjhDYnywLB2yE1w5BmU" 4 | module.exports = function(query, done) { 5 | let s = JSON.stringify(query); 6 | let b64 = Buffer.from(s).toString('base64'); 7 | let url = endpoint + b64; 8 | let header = { headers: { key: apikey } }; 9 | axios.get(url, header).then(function(r) { 10 | done(r.data) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /lib/key.js: -------------------------------------------------------------------------------- 1 | var bsv = require('datapay').bsv; 2 | const mkdir = require('make-dir'); 3 | const mkdirp = require('mkdirp') 4 | const bip44 = "m/44'/0'/0'/0/" 5 | const fs = require('fs') 6 | const homedir = require('os').homedir(); 7 | const bitcomPath = homedir + "/.bitcom" 8 | const hdPath = bitcomPath + "/hd" 9 | const wifPath = bitcomPath + "/wif" 10 | const seed = async function() { 11 | /************************************** 12 | * 13 | * if .seed exists 14 | * create keys and write to .seed 15 | * return xpriv object 16 | * else 17 | * read .seed 18 | * instantiate xpriv object 19 | * return xpriv object 20 | * 21 | **************************************/ 22 | const seedPath = `${bitcomPath}/.seed`; 23 | if (fs.existsSync(seedPath)) { 24 | let xprivStr = fs.readFileSync(seedPath, "utf8") 25 | let xpriv = bsv.HDPrivateKey.fromString(xprivStr); 26 | return xpriv; 27 | } else { 28 | await mkdir(bitcomPath) 29 | let xpriv = bsv.HDPrivateKey.fromRandom() 30 | fs.writeFileSync(seedPath, xpriv.toString()); 31 | return xpriv; 32 | } 33 | } 34 | const keygen = function(xpriv, index) { 35 | let xpriv2 = xpriv.deriveChild(bip44 + index); 36 | let xpub2 = bsv.HDPublicKey.fromHDPrivateKey(xpriv2) 37 | let priv2 = xpriv2.privateKey; 38 | let pub2 = xpriv2.publicKey; 39 | let address2 = xpriv2.privateKey.toAddress(); 40 | return { 41 | xpriv: xpriv2.toString(), 42 | xpub: xpub2.toString(), 43 | priv: priv2.toString(), 44 | pub: pub2.toString(), 45 | address: address2.toString() 46 | } 47 | } 48 | const keyimport = function(wif) { 49 | try { 50 | let privateKey = bsv.PrivateKey.fromWIF(wif); 51 | let address = privateKey.toAddress(); 52 | let pubKey = privateKey.toPublicKey(); 53 | // write to key 54 | return { 55 | priv: privateKey.toWIF(), 56 | pub: pubKey.toString(), 57 | address: address.toString() 58 | } 59 | } catch (e) { 60 | console.log(e) 61 | process.exit(); 62 | } 63 | } 64 | const address = function(folder) { 65 | // get the address for a folder 66 | return new Promise(function(resolve, reject) { 67 | fs.readFile(folder + "/.bit", 'utf8', function(err, contents) { 68 | let addrmatch = contents.match(/address=[13][a-km-zA-HJ-NP-Z0-9]{26,33}$/) 69 | resolve(addrmatch[0].split("=")[1]) 70 | }) 71 | }) 72 | } 73 | const importer = function(wif) { 74 | return new Promise(async function(resolve, reject) { 75 | await mkdir(wifPath) 76 | let keys = keyimport(wif) 77 | let addressPath = wifPath + "/" + keys.address; 78 | if (fs.existsSync(addressPath)) { 79 | resolve({ value: keys }) 80 | } else { 81 | mkdirp(addressPath, function(err) { 82 | if (err) { 83 | console.log(err) 84 | reject() 85 | } else { 86 | let stream = fs.createWriteStream(addressPath + "/.bit") 87 | stream.once('open', function(fd) { 88 | let content = Object.keys(keys).map(function(key) { 89 | return key + "=" + keys[key] 90 | }).join("\n") 91 | stream.write(content) 92 | stream.end(); 93 | resolve({ value: keys }); 94 | }) 95 | } 96 | }) 97 | } 98 | }) 99 | } 100 | const add = function(seed) { 101 | return new Promise(async function(resolve, reject) { 102 | await mkdir(hdPath) 103 | fs.readdir(hdPath, async function(err, items) { 104 | let newIndex; 105 | let newItems = items.filter(function(item) { 106 | return !/^\..+/.test(item) 107 | }).map(function(item) { 108 | return parseInt(item) 109 | }) 110 | if (newItems.length === 0) { 111 | newIndex = 0; 112 | } else { 113 | newItems.sort(function(a, b) { 114 | return b-a 115 | }) 116 | newIndex = newItems[0] + 1 117 | } 118 | mkdirp(hdPath + "/" + newIndex, function(err) { 119 | if (err) { 120 | console.log(err) 121 | reject() 122 | } else { 123 | let stream = fs.createWriteStream(hdPath + "/" + newIndex + "/.bit") 124 | stream.once('open', function(fd) { 125 | let keys = keygen(seed, newIndex) 126 | let content = Object.keys(keys).map(function(key) { 127 | return key + "=" + keys[key] 128 | }).join("\n") 129 | stream.write(content) 130 | stream.end(); 131 | resolve({ 132 | value: keys, 133 | index: newIndex 134 | }); 135 | }) 136 | } 137 | }) 138 | }) 139 | }) 140 | } 141 | const ls = function(type) { 142 | // return the list of addresses from all keys 143 | let p = (type === 'hd' ? hdPath : wifPath) 144 | return new Promise(async function(resolve, reject) { 145 | await mkdir(p); 146 | fs.readdir(p, async function(err, items) { 147 | let newItems = items.filter(function(item) { 148 | return !/^\..+/.test(item) 149 | }) 150 | let ps = Promise.all(newItems.map(async function(item) { 151 | if (type === 'hd') { 152 | let addr = await address(p + "/" + item) 153 | return { 154 | index: item, 155 | value: addr 156 | } 157 | } else { 158 | return { 159 | value: item 160 | } 161 | } 162 | })) 163 | ps.then(function(addrs) { 164 | resolve(addrs) 165 | }) 166 | }) 167 | }) 168 | } 169 | const generate = async function() { 170 | let s = await seed() 171 | let keys = await add(s) 172 | return keys 173 | }; 174 | module.exports = { 175 | ls: ls, 176 | importer: importer, 177 | generate: generate 178 | } 179 | -------------------------------------------------------------------------------- /lib/post.js: -------------------------------------------------------------------------------- 1 | const datapay = require('datapay') 2 | module.exports = function(pushdata, cb) { 3 | if (process.env.address && process.env.address) { 4 | let payload = { 5 | data: pushdata, 6 | pay: { key: process.env.priv } 7 | } 8 | console.log("publishing...") 9 | datapay.send(payload, function(err, tx) { 10 | cb(err, `success: ${tx} \n[ bterm: https://bterm.network/#address=${process.env.address} ]`) 11 | }) 12 | } else { 13 | console.log("no key") 14 | process.exit() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitsh", 3 | "version": "0.1.26", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "bin": { 10 | "bitsh": "./index.js" 11 | }, 12 | "author": "", 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "datapay": "0.0.13", 16 | "dotenv": "^6.2.0", 17 | "make-dir": "^3.0.0", 18 | "mkdirp": "^0.5.1", 19 | "qrcode-terminal": "^0.12.0", 20 | "terminal-kit": "^1.28.0", 21 | "vorpal": "^1.12.0", 22 | "bsv": "^0.26.4" 23 | }, 24 | "license": "ISC" 25 | } 26 | --------------------------------------------------------------------------------