├── .gitignore ├── .gitmodules ├── .npmignore ├── .travis.yml ├── Changelog.md ├── Dockerfile ├── LICENSE ├── Makefile ├── Readme.md ├── lib ├── commands.js ├── index.js └── jsonrpc.js ├── package.json └── test ├── config.js ├── index.js ├── missing-commands.js ├── mocha.opts └── ssl.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/config.js 3 | .project 4 | 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/testnet-box"] 2 | path = test/testnet-box 3 | url = https://github.com/freewil/bitcoin-testnet-box 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/ 3 | .project 4 | .gitmodules 5 | .travis.yml 6 | Dockerfile 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # need sudo for docker 2 | sudo: required 3 | 4 | language: node_js 5 | 6 | # skip install step (npm install) 7 | # See http://docs.travis-ci.com/user/customizing-the-build/#Skipping-the-Installation-Step 8 | install: true 9 | 10 | services: 11 | - docker 12 | 13 | before_script: 14 | - git submodule update --init 15 | - docker build -t node-bitcoin . 16 | 17 | script: 18 | - docker run node-bitcoin 19 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # node-bitcoin changelog 2 | 3 | ## v3.0.1 (2015/10/25) 4 | * fix redefinition of already defined variable (does not actually affect behavior) 5 | 6 | ## v3.0.0 (2015/10/18) 7 | * make public domain license explicit 8 | * remove commands dropped in bitcoind v0.11 9 | * `getHashesPerSecond` 10 | * `getHashesPerSec` 11 | * add missing commands for bitcoind v0.11 12 | * `generate` 13 | * `verifyTxOutProof` 14 | 15 | ## v2.4.0 (2015/07/16) 16 | * don't lazy-load http/https modules 17 | * add command for bitcoind v0.11.0: `getTxOutProof` 18 | 19 | ## v2.3.2 (2015/06/26) 20 | * fix bug in test suite that was supposed to detect missing commands 21 | * add missing commands 22 | * `prioritiseTransaction` 23 | * `importWallet` 24 | 25 | ## v2.3.1 (2015/06/24) 26 | * add missing `getMempoolInfo` for bitcoind v0.10 27 | 28 | ## v2.3.0 (2015/02/04) 29 | * drop node v0.8.x support 30 | * update testnet-box 31 | * update devDependencies 32 | * add commands for bitcoind v0.10 33 | * `estimateFee` 34 | * `estimatePriority` 35 | * `getChainTips` 36 | * `importAddress` 37 | 38 | ## v2.2.0 (2014/08/29) 39 | * add commands for bitcoind v0.9.x 40 | * `decodeScript` 41 | * `dumpWallet` 42 | * `getBestBlockHash` 43 | * `getBlockchainInfo` 44 | * `getNetTotals` 45 | * `getNetworkInfo` 46 | * `getNetworkHashPs` 47 | * `getRawChangeAddress` 48 | * `getUnconfirmedBalance` 49 | * `getWalletInfo` 50 | * `ping` 51 | * `verifyChain` 52 | 53 | ## v2.1.2 (2014/04/16) 54 | * lazy load `http`/`https` module 55 | 56 | ## v2.1.1 (2014/03/25) 57 | * change default request timeout from `5000`ms to `30000`ms 58 | 59 | ## v2.1.0 (2014/03/12) 60 | * remove `deprecate` dependency 61 | * add request timeout option (defaults to 5000ms) 62 | * add 3rd parameter to callbacks: response headers 63 | 64 | ## v2.0.1 (2014/01/08) 65 | * default `host` to 'localhost'; `port` to '8332' 66 | 67 | ## v2.0.0 (2013/10/14) 68 | * remove deprecated commands 69 | * `getMemoryPool` 70 | * `getMemorypool` 71 | * remove deprecated functionality 72 | * creating `bitcoin.Client` with more than one argument 73 | 74 | ## v1.7.0 (2013/05/05) 75 | * add missing commands from bitcoind v0.7.0 76 | * `createMultiSig` 77 | * `getBlockTemplate` 78 | * `getTxOut` 79 | * `getTxOutSetInfo` 80 | * `listAddressGroupings` 81 | * `submitBlock` 82 | * deprecate commands 83 | * `getMemoryPool` 84 | * `getMemorypool` 85 | 86 | ## v1.6.2 (2013/03/21) 87 | * shrink package size via .npmignore 88 | 89 | ## v1.6.1 (2013/03/13) 90 | * add node v0.10.x support (rejectUnauthorized defaults to true in 0.10.x) 91 | 92 | ## v1.6.0 (2013/03/08) 93 | * drop node v0.6.x support 94 | * change test runner from `vows` to `mocha` 95 | * upgrade testnet-box 96 | * add commands for bitcoind v0.8.0 97 | * `addNode` 98 | * `getAddedNodeInfo` 99 | * `listLockUnspent` 100 | * `lockUnspent` 101 | * deprecate creating `bitcoin.Client` with more than one argument 102 | * add SSL support 103 | 104 | ## v1.5.0 (2012/10/22) 105 | * remove `getBlockNumber` test 106 | * upgrade testnet-box 107 | * add RPC call batching (multiple RPC calls within one HTTP request) 108 | 109 | ## v1.4.0 (2012/09/09) 110 | * add commands for bitcoind v0.7.0 111 | * `createRawTransaction` 112 | * `decodeRawTransaction` 113 | * `getPeerInfo` 114 | * `getRawMemPool` 115 | * `getRawTransaction` 116 | * `listUnspent` 117 | * `sendRawTransaction` 118 | * `signRawTransaction` 119 | * remove deprecated `getBlockNumber` 120 | 121 | ## v1.3.1 (2012/08/19) 122 | Remove `underscore` dependency 123 | 124 | ## v1.3.0 (2012/07/03) 125 | Change use of http.createClient() (deprecated in node v0.8.x) to http.request() 126 | 127 | ## v1.2.2 (2012/04/26) 128 | Fix callback being called twice when a client and request error 129 | occur on the same command call. 130 | 131 | ## v1.2.1 (2012/04/26) 132 | * add missing `getBlock` command 133 | 134 | ## v1.2.0 (2012/04/25) 135 | * submodule testnet-box for running tests 136 | * err objects should all now be an instance of Error 137 | 138 | ## v1.1.6 (2012/04/11) 139 | * add commands for bitcoind v0.6.0 140 | * `addMultiSigAddress` (only available in testnet) 141 | * `dumpPrivKey` 142 | * `getBlockHash` 143 | * `getMiningInfo` 144 | * `importPrivKey` 145 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for running node-bitcoin tests 2 | FROM freewil/bitcoin-testnet-box 3 | MAINTAINER Sean Lavine 4 | 5 | # install node.js 6 | USER root 7 | RUN apt-get install --yes curl 8 | RUN curl --silent --location https://deb.nodesource.com/setup_0.12 | bash - 9 | RUN apt-get install --yes nodejs 10 | 11 | # set permissions for tester user on project 12 | ADD . /home/tester/node-bitcoin 13 | RUN chown --recursive tester:tester /home/tester/node-bitcoin 14 | 15 | # install module dependencies 16 | USER tester 17 | WORKDIR /home/tester/node-bitcoin 18 | RUN npm install 19 | 20 | # run test suite 21 | CMD ["npm", "test"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MOCHA=./node_modules/.bin/mocha 2 | BOX=test/testnet-box 3 | 4 | test: 5 | $(MAKE) test-ssl-no 6 | sleep 20 7 | $(MAKE) clean 8 | $(MAKE) test-ssl 9 | 10 | test-ssl-no: 11 | $(MAKE) start 12 | sleep 20 13 | $(MAKE) run-test 14 | $(MAKE) stop 15 | 16 | test-ssl: 17 | $(MAKE) start-ssl 18 | sleep 20 19 | $(MAKE) run-test-ssl 20 | $(MAKE) stop-ssl 21 | 22 | start: 23 | $(MAKE) -C $(BOX) start 24 | 25 | start-ssl: 26 | $(MAKE) -C $(BOX) start B1_FLAGS=-rpcssl=1 B2_FLAGS=-rpcssl=1 27 | 28 | stop: 29 | $(MAKE) -C $(BOX) stop 30 | @while ps -C bitcoind > /dev/null; do sleep 1; done 31 | 32 | stop-ssl: 33 | $(MAKE) -C $(BOX) stop B1_FLAGS=-rpcssl=1 B2_FLAGS=-rpcssl=1 34 | 35 | run-test: 36 | $(MOCHA) --invert --grep SSL 37 | 38 | run-test-ssl: 39 | $(MOCHA) --grep SSL 40 | 41 | clean: 42 | $(MAKE) -C $(BOX) clean 43 | 44 | .PHONY: test 45 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # node-bitcoin 2 | [![travis][travis-image]][travis-url] 3 | [![npm][npm-image]][npm-url] 4 | [![downloads][downloads-image]][downloads-url] 5 | [![js-standard-style][standard-image]][standard-url] 6 | 7 | [travis-image]: https://travis-ci.org/freewil/node-bitcoin.svg?branch=master 8 | [travis-url]: https://travis-ci.org/freewil/node-bitcoin 9 | 10 | [npm-image]: https://img.shields.io/npm/v/bitcoin.svg?style=flat 11 | [npm-url]: https://npmjs.org/package/bitcoin 12 | 13 | [downloads-image]: https://img.shields.io/npm/dm/bitcoin.svg?style=flat 14 | [downloads-url]: https://npmjs.org/package/bitcoin 15 | 16 | [standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat 17 | [standard-url]: http://standardjs.com 18 | 19 | node-bitcoin is a simple wrapper for the Bitcoin client's JSON-RPC API. 20 | 21 | The API is equivalent to the API document [here](https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list). 22 | The methods are exposed as lower camelcase methods on the `bitcoin.Client` 23 | object, or you may call the API directly using the `cmd` method. 24 | 25 | ## Install 26 | 27 | `npm install bitcoin` 28 | 29 | ## Examples 30 | 31 | ### Create client 32 | ```js 33 | // all config options are optional 34 | var client = new bitcoin.Client({ 35 | host: 'localhost', 36 | port: 8332, 37 | user: 'username', 38 | pass: 'password', 39 | wallet: 'walletname', 40 | timeout: 30000 41 | }); 42 | ``` 43 | 44 | ### Get balance across all accounts with minimum confirmations of 6 45 | 46 | ```js 47 | client.getBalance('*', 6, function(err, balance, resHeaders) { 48 | if (err) return console.log(err); 49 | console.log('Balance:', balance); 50 | }); 51 | ``` 52 | ### Getting the balance directly using `cmd` 53 | 54 | ```js 55 | client.cmd('getbalance', '*', 6, function(err, balance, resHeaders){ 56 | if (err) return console.log(err); 57 | console.log('Balance:', balance); 58 | }); 59 | ``` 60 | 61 | ### Batch multiple RPC calls into single HTTP request 62 | 63 | ```js 64 | var batch = []; 65 | for (var i = 0; i < 10; ++i) { 66 | batch.push({ 67 | method: 'getnewaddress', 68 | params: ['myaccount'] 69 | }); 70 | } 71 | client.cmd(batch, function(err, address, resHeaders) { 72 | if (err) return console.log(err); 73 | console.log('Address:', address); 74 | }); 75 | ``` 76 | 77 | ## SSL 78 | See [Enabling SSL on original client](https://en.bitcoin.it/wiki/Enabling_SSL_on_original_client_daemon). 79 | 80 | If you're using this to connect to bitcoind across a network it is highly 81 | recommended to enable `ssl`, otherwise an attacker may intercept your RPC credentials 82 | resulting in theft of your bitcoins. 83 | 84 | When enabling `ssl` by setting the configuration option to `true`, the `sslStrict` 85 | option (verifies the server certificate) will also be enabled by default. It is 86 | highly recommended to specify the `sslCa` as well, even if your bitcoind has 87 | a certificate signed by an actual CA, to ensure you are connecting 88 | to your own bitcoind. 89 | 90 | ```js 91 | var client = new bitcoin.Client({ 92 | host: 'localhost', 93 | port: 8332, 94 | user: 'username', 95 | pass: 'password', 96 | ssl: true, 97 | sslStrict: true, 98 | sslCa: fs.readFileSync(__dirname + '/myca.cert') 99 | }); 100 | ``` 101 | -------------------------------------------------------------------------------- /lib/commands.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | addMultiSigAddress: 'addmultisigaddress', 3 | addNode: 'addnode', // bitcoind v0.8.0+ 4 | backupWallet: 'backupwallet', 5 | createMultiSig: 'createmultisig', 6 | createRawTransaction: 'createrawtransaction', // bitcoind v0.7.0+ 7 | decodeRawTransaction: 'decoderawtransaction', // bitcoind v0.7.0+ 8 | decodeScript: 'decodescript', 9 | dumpPrivKey: 'dumpprivkey', 10 | dumpWallet: 'dumpwallet', // bitcoind v0.9.0+ 11 | encryptWallet: 'encryptwallet', 12 | estimateFee: 'estimatefee', // bitcoind v0.10.0x 13 | estimatePriority: 'estimatepriority', // bitcoind v0.10.0+ 14 | generate: 'generate', // bitcoind v0.11.0+ 15 | getAccount: 'getaccount', 16 | getAccountAddress: 'getaccountaddress', 17 | getAddedNodeInfo: 'getaddednodeinfo', // bitcoind v0.8.0+ 18 | getAddressesByAccount: 'getaddressesbyaccount', 19 | getBalance: 'getbalance', 20 | getBestBlockHash: 'getbestblockhash', // bitcoind v0.9.0+ 21 | getBlock: 'getblock', 22 | getBlockchainInfo: 'getblockchaininfo', // bitcoind v0.9.2+ 23 | getBlockCount: 'getblockcount', 24 | getBlockHash: 'getblockhash', 25 | getBlockTemplate: 'getblocktemplate', // bitcoind v0.7.0+ 26 | getChainTips: 'getchaintips', // bitcoind v0.10.0+ 27 | getConnectionCount: 'getconnectioncount', 28 | getDifficulty: 'getdifficulty', 29 | getGenerate: 'getgenerate', 30 | getInfo: 'getinfo', 31 | getMempoolInfo: 'getmempoolinfo', // bitcoind v0.10+ 32 | getMiningInfo: 'getmininginfo', 33 | getNetTotals: 'getnettotals', 34 | getNetworkInfo: 'getnetworkinfo', // bitcoind v0.9.2+ 35 | getNetworkHashPs: 'getnetworkhashps', // bitcoind v0.9.0+ 36 | getNewAddress: 'getnewaddress', 37 | getPeerInfo: 'getpeerinfo', // bitcoind v0.7.0+ 38 | getRawChangeAddress: 'getrawchangeaddress', // bitcoin v0.9+ 39 | getRawMemPool: 'getrawmempool', // bitcoind v0.7.0+ 40 | getRawTransaction: 'getrawtransaction', // bitcoind v0.7.0+ 41 | getReceivedByAccount: 'getreceivedbyaccount', 42 | getReceivedByAddress: 'getreceivedbyaddress', 43 | getTransaction: 'gettransaction', 44 | getTxOut: 'gettxout', // bitcoind v0.7.0+ 45 | getTxOutProof: 'gettxoutproof', // bitcoind v0.11.0+ 46 | getTxOutSetInfo: 'gettxoutsetinfo', // bitcoind v0.7.0+ 47 | getUnconfirmedBalance: 'getunconfirmedbalance', // bitcoind v0.9.0+ 48 | getWalletInfo: 'getwalletinfo', // bitcoind v0.9.2+ 49 | help: 'help', 50 | importAddress: 'importaddress', // bitcoind v0.10.0+ 51 | importPrivKey: 'importprivkey', 52 | importWallet: 'importwallet', // bitcoind v0.9.0+ 53 | keypoolRefill: 'keypoolrefill', 54 | keyPoolRefill: 'keypoolrefill', 55 | listAccounts: 'listaccounts', 56 | listAddressGroupings: 'listaddressgroupings', // bitcoind v0.7.0+ 57 | listLockUnspent: 'listlockunspent', // bitcoind v0.8.0+ 58 | listReceivedByAccount: 'listreceivedbyaccount', 59 | listReceivedByAddress: 'listreceivedbyaddress', 60 | listSinceBlock: 'listsinceblock', 61 | listTransactions: 'listtransactions', 62 | listUnspent: 'listunspent', // bitcoind v0.7.0+ 63 | lockUnspent: 'lockunspent', // bitcoind v0.8.0+ 64 | move: 'move', 65 | ping: 'ping', // bitcoind v0.9.0+ 66 | prioritiseTransaction: 'prioritisetransaction', // bitcoind v0.10.0+ 67 | sendFrom: 'sendfrom', 68 | sendMany: 'sendmany', 69 | sendRawTransaction: 'sendrawtransaction', // bitcoind v0.7.0+ 70 | sendToAddress: 'sendtoaddress', 71 | setAccount: 'setaccount', 72 | setGenerate: 'setgenerate', 73 | setTxFee: 'settxfee', 74 | signMessage: 'signmessage', 75 | signRawTransaction: 'signrawtransaction', // bitcoind v0.7.0+ 76 | stop: 'stop', 77 | submitBlock: 'submitblock', // bitcoind v0.7.0+ 78 | validateAddress: 'validateaddress', 79 | verifyChain: 'verifychain', // bitcoind v0.9.0+ 80 | verifyMessage: 'verifymessage', 81 | verifyTxOutProof: 'verifytxoutproof', // bitcoind v0.11.0+ 82 | walletLock: 'walletlock', 83 | walletPassphrase: 'walletpassphrase', 84 | walletPassphraseChange: 'walletpassphrasechange' 85 | } 86 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var commands = require('./commands') 2 | var rpc = require('./jsonrpc') 3 | 4 | // ===----------------------------------------------------------------------===// 5 | // Client 6 | // ===----------------------------------------------------------------------===// 7 | function Client (opts) { 8 | this.rpc = new rpc.Client(opts) 9 | this.wallet = opts.wallet 10 | } 11 | 12 | // ===----------------------------------------------------------------------===// 13 | // cmd 14 | // ===----------------------------------------------------------------------===// 15 | Client.prototype.cmd = function () { 16 | var args = [].slice.call(arguments) 17 | var cmd = args.shift() 18 | 19 | callRpc(cmd, args, this.rpc, this.wallet) 20 | } 21 | 22 | // ===----------------------------------------------------------------------===// 23 | // callRpc 24 | // ===----------------------------------------------------------------------===// 25 | function callRpc (cmd, args, rpc, wallet) { 26 | var fn = args[args.length - 1] 27 | 28 | // If the last argument is a callback, pop it from the args list 29 | if (typeof fn === 'function') { 30 | args.pop() 31 | } else { 32 | fn = function () {} 33 | } 34 | 35 | rpc.call(cmd, args, function () { 36 | var args = [].slice.call(arguments) 37 | args.unshift(null) 38 | fn.apply(this, args) 39 | }, function (err) { 40 | fn(err) 41 | }, wallet ? `/wallet/${wallet}` : undefined) 42 | } 43 | 44 | // ===----------------------------------------------------------------------===// 45 | // Initialize wrappers 46 | // ===----------------------------------------------------------------------===// 47 | (function () { 48 | for (var protoFn in commands) { 49 | (function (protoFn) { 50 | Client.prototype[protoFn] = function () { 51 | var args = [].slice.call(arguments) 52 | callRpc(commands[protoFn], args, this.rpc, this.wallet) 53 | } 54 | })(protoFn) 55 | } 56 | })() 57 | 58 | // Export! 59 | module.exports.Client = Client 60 | -------------------------------------------------------------------------------- /lib/jsonrpc.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | var https = require('https') 3 | 4 | var Client = function (opts) { 5 | this.opts = opts || {} 6 | this.http = this.opts.ssl ? https : http 7 | } 8 | 9 | Client.prototype.call = function (method, params, callback, errback, path) { 10 | var time = Date.now() 11 | var requestJSON 12 | 13 | if (Array.isArray(method)) { 14 | // multiple rpc batch call 15 | requestJSON = [] 16 | method.forEach(function (batchCall, i) { 17 | requestJSON.push({ 18 | id: time + '-' + i, 19 | method: batchCall.method, 20 | params: batchCall.params 21 | }) 22 | }) 23 | } else { 24 | // single rpc call 25 | requestJSON = { 26 | id: time, 27 | method: method, 28 | params: params 29 | } 30 | } 31 | 32 | // First we encode the request into JSON 33 | requestJSON = JSON.stringify(requestJSON) 34 | 35 | // prepare request options 36 | var requestOptions = { 37 | host: this.opts.host || 'localhost', 38 | port: this.opts.port || 8332, 39 | method: 'POST', 40 | path: path || '/', 41 | headers: { 42 | 'Host': this.opts.host || 'localhost', 43 | 'Content-Length': requestJSON.length 44 | }, 45 | agent: false, 46 | rejectUnauthorized: this.opts.ssl && this.opts.sslStrict !== false 47 | } 48 | 49 | if (this.opts.ssl && this.opts.sslCa) { 50 | requestOptions.ca = this.opts.sslCa 51 | } 52 | 53 | // use HTTP auth if user and password set 54 | if (this.opts.user && this.opts.pass) { 55 | requestOptions.auth = this.opts.user + ':' + this.opts.pass 56 | } 57 | 58 | // Now we'll make a request to the server 59 | var cbCalled = false 60 | var request = this.http.request(requestOptions) 61 | 62 | // start request timeout timer 63 | var reqTimeout = setTimeout(function () { 64 | if (cbCalled) return 65 | cbCalled = true 66 | request.abort() 67 | var err = new Error('ETIMEDOUT') 68 | err.code = 'ETIMEDOUT' 69 | errback(err) 70 | }, this.opts.timeout || 30000) 71 | 72 | // set additional timeout on socket in case of remote freeze after sending headers 73 | request.setTimeout(this.opts.timeout || 30000, function () { 74 | if (cbCalled) return 75 | cbCalled = true 76 | request.abort() 77 | var err = new Error('ESOCKETTIMEDOUT') 78 | err.code = 'ESOCKETTIMEDOUT' 79 | errback(err) 80 | }) 81 | 82 | request.on('error', function (err) { 83 | if (cbCalled) return 84 | cbCalled = true 85 | clearTimeout(reqTimeout) 86 | errback(err) 87 | }) 88 | 89 | request.on('response', function (response) { 90 | clearTimeout(reqTimeout) 91 | 92 | // We need to buffer the response chunks in a nonblocking way. 93 | var buffer = '' 94 | response.on('data', function (chunk) { 95 | buffer = buffer + chunk 96 | }) 97 | // When all the responses are finished, we decode the JSON and 98 | // depending on whether it's got a result or an error, we call 99 | // emitSuccess or emitError on the promise. 100 | response.on('end', function () { 101 | var err 102 | 103 | if (cbCalled) return 104 | cbCalled = true 105 | 106 | try { 107 | var decoded = JSON.parse(buffer) 108 | } catch (e) { 109 | if (response.statusCode !== 200) { 110 | err = new Error('Invalid params, response status code: ' + response.statusCode) 111 | err.code = -32602 112 | errback(err) 113 | } else { 114 | err = new Error('Problem parsing JSON response from server') 115 | err.code = -32603 116 | errback(err) 117 | } 118 | return 119 | } 120 | 121 | if (!Array.isArray(decoded)) { 122 | decoded = [decoded] 123 | } 124 | 125 | // iterate over each response, normally there will be just one 126 | // unless a batch rpc call response is being processed 127 | decoded.forEach(function (decodedResponse, i) { 128 | if (decodedResponse.hasOwnProperty('error') && decodedResponse.error != null) { 129 | if (errback) { 130 | err = new Error(decodedResponse.error.message || '') 131 | if (decodedResponse.error.code) { 132 | err.code = decodedResponse.error.code 133 | } 134 | errback(err) 135 | } 136 | } else if (decodedResponse.hasOwnProperty('result')) { 137 | if (callback) { 138 | callback(decodedResponse.result, response.headers) 139 | } 140 | } else { 141 | if (errback) { 142 | err = new Error(decodedResponse.error.message || '') 143 | if (decodedResponse.error.code) { 144 | err.code = decodedResponse.error.code 145 | } 146 | errback(err) 147 | } 148 | } 149 | }) 150 | }) 151 | }) 152 | request.end(requestJSON) 153 | } 154 | 155 | module.exports.Client = Client 156 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitcoin", 3 | "description": "Communicate with bitcoind via JSON-RPC", 4 | "version": "3.0.3", 5 | "main": "lib/index.js", 6 | "keywords": [ 7 | "bitcoin", 8 | "rpc" 9 | ], 10 | "author": "William Casarin ", 11 | "contributors": [ 12 | "Sean Lavine " 13 | ], 14 | "dependencies": {}, 15 | "devDependencies": { 16 | "clone": "^1.0.2", 17 | "mocha": "^2.3.3", 18 | "standard": "^5.3.1" 19 | }, 20 | "optionalDependencies": {}, 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/jb55/node-bitcoin.git" 24 | }, 25 | "engines": { 26 | "node": ">= 0.10.0" 27 | }, 28 | "scripts": { 29 | "pretest": "standard --verbose", 30 | "test": "make test" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/jb55/node-bitcoin/issues" 34 | }, 35 | "homepage": "https://github.com/jb55/node-bitcoin", 36 | "directories": { 37 | "test": "test" 38 | }, 39 | "license": "Unlicense" 40 | } 41 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | host: 'localhost', 3 | port: 19001, 4 | user: 'admin1', 5 | pass: '123' 6 | }; 7 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | var assert = require('assert') 4 | var clone = require('clone') 5 | var http = require('http') 6 | var bitcoin = require('../') 7 | var config = require('./config') 8 | 9 | var test = { 10 | account: 'test' 11 | } 12 | 13 | var makeClient = function makeClient () { 14 | return new bitcoin.Client(config) 15 | } 16 | 17 | var notEmpty = function notEmpty (data) { 18 | if (data === 0) return 19 | assert.ok(data) 20 | } 21 | 22 | var makeServer = function (port) { 23 | var server = http.createServer() 24 | server.listen(port) 25 | return server 26 | } 27 | 28 | describe('Client', function () { 29 | describe('getAccountAddress()', function () { 30 | it('should be able to get an account address', function (done) { 31 | var client = makeClient() 32 | client.getAccountAddress(test.account, function (err, address) { 33 | assert.ifError(err) 34 | assert.ok(address) 35 | client.getAccount(address, function (err, account) { 36 | assert.ifError(err) 37 | assert.equal(account, test.account) 38 | done() 39 | }) 40 | }) 41 | }) 42 | }) 43 | 44 | describe('listTransactions()', function () { 45 | it('should be able to listTransactions with specific count', function (done) { 46 | var client = makeClient() 47 | client.listTransactions(test.account, 15, function (err, txs) { 48 | assert.ifError(err) 49 | assert.ok(txs) 50 | assert.ok(Array.isArray(txs)) 51 | done() 52 | }) 53 | }) 54 | 55 | it('should be able to listTransactions without specific count', function (done) { 56 | var client = makeClient() 57 | client.listTransactions(test.account, function (err, txs) { 58 | assert.ifError(err) 59 | assert.ok(txs) 60 | assert.ok(Array.isArray(txs)) 61 | done() 62 | }) 63 | }) 64 | }) 65 | 66 | describe('getNewAddress()', function () { 67 | it('should be able to get new address', function (done) { 68 | var client = makeClient() 69 | client.getNewAddress(test.account, function (err, address) { 70 | assert.ifError(err) 71 | client.getAddressesByAccount(test.account, function (err, addresses) { 72 | assert.ifError(err) 73 | assert.ok(addresses && addresses.length > 0) 74 | done() 75 | }) 76 | }) 77 | }) 78 | }) 79 | 80 | describe('getBalance()', function () { 81 | it('should return balance without any args', function (done) { 82 | var client = makeClient() 83 | client.getBalance(function (err, balance) { 84 | assert.ifError(err) 85 | assert.ok(typeof balance === 'number') 86 | done() 87 | }) 88 | }) 89 | }) 90 | 91 | describe('getDifficulty()', function () { 92 | it('should get difficulty', function (done) { 93 | var client = makeClient() 94 | client.getDifficulty(function (err, difficulty) { 95 | assert.ifError(err) 96 | assert.ok(typeof difficulty === 'number') 97 | done() 98 | }) 99 | }) 100 | }) 101 | 102 | describe('getInfo()', function () { 103 | it('should get info', function (done) { 104 | var client = makeClient() 105 | client.getInfo(function (err, info, headers) { 106 | assert.ifError(err) 107 | notEmpty(info) 108 | assert.ok(info.errors === '') 109 | done() 110 | }) 111 | }) 112 | }) 113 | 114 | describe('help()', function () { 115 | it('should return help', function (done) { 116 | var client = makeClient() 117 | client.help(function (err, help) { 118 | assert.ifError(err) 119 | notEmpty(help) 120 | done() 121 | }) 122 | }) 123 | }) 124 | 125 | it('bitcoin related error should be an Error object', function (done) { 126 | var client = makeClient() 127 | client.cmd('nomethod', function (err, expectedValue) { 128 | assert.ok(err instanceof Error) 129 | assert.equal(err.message, 'Method not found') 130 | assert.equal(err.code, -32601) 131 | assert.equal(expectedValue, undefined) 132 | done() 133 | }) 134 | }) 135 | 136 | it('running batch of rpc calls', function (done) { 137 | this.timeout(5000) 138 | var batch = [] 139 | for (var i = 0; i < 10; ++i) { 140 | batch.push({ 141 | method: 'getnewaddress', 142 | params: [test.account] 143 | }) 144 | } 145 | var client = makeClient() 146 | var batchCallbackCount = 0 147 | client.cmd(batch, function (err, address) { 148 | assert.ifError(err) 149 | assert.ok(++batchCallbackCount <= 10) 150 | assert.ok(address) 151 | if (batchCallbackCount === 10) done() 152 | }) 153 | }) 154 | 155 | describe('invalid credentials', function () { 156 | var badCredentials = clone(config) 157 | badCredentials.user = 'baduser' 158 | badCredentials.pass = 'badpwd' 159 | var client = new bitcoin.Client(badCredentials) 160 | 161 | it('should still return client object', function (done) { 162 | assert.ok(client instanceof bitcoin.Client) 163 | done() 164 | }) 165 | 166 | it('should return status 401 with html', function (done) { 167 | client.getDifficulty(function (err, difficulty) { 168 | assert.ok(err instanceof Error) 169 | assert.equal(err.message, 'Invalid params, response status code: 401') 170 | assert.equal(err.code, -32602) 171 | assert.equal(difficulty, undefined) 172 | done() 173 | }) 174 | }) 175 | }) 176 | 177 | describe('creating client on non-listening port', function () { 178 | var badPort = clone(config) 179 | badPort.port = 9897 180 | badPort.user = 'baduser' 181 | badPort.pass = 'badpwd' 182 | var client = new bitcoin.Client(badPort) 183 | 184 | it('will return client object', function (done) { 185 | assert.ok(client instanceof bitcoin.Client) 186 | done() 187 | }) 188 | 189 | it('should not call callback more than once', function (done) { 190 | client.listSinceBlock(function (err, result) { 191 | assert.ok(err instanceof Error) 192 | done() 193 | }) 194 | }) 195 | }) 196 | 197 | describe('request timeouts', function () { 198 | it('should occur by default after 30000ms', function (done) { 199 | this.timeout(31000) 200 | var server = makeServer(19998) 201 | var request // eslint-disable-line no-unused-vars 202 | var response 203 | server.on('request', function (req, res) { 204 | request = req 205 | response = res 206 | }) 207 | var client = new bitcoin.Client({ 208 | host: 'localhost', 209 | port: 19998, 210 | user: 'admin1', 211 | pass: '123' 212 | }) 213 | var start = Date.now() 214 | client.getInfo(function (err, info) { 215 | var delta = Date.now() - start 216 | assert.ok(err instanceof Error) 217 | assert.ok(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') 218 | assert.ok(delta >= 30000, 'delta should be >= 30000: ' + delta) 219 | response.end() 220 | server.close() 221 | done() 222 | }) 223 | }) 224 | 225 | it('should be customizable', function (done) { 226 | this.timeout(4500) 227 | var server = makeServer(19999) 228 | var request // eslint-disable-line no-unused-vars 229 | var response 230 | server.on('request', function (req, res) { 231 | request = req 232 | response = res 233 | }) 234 | var client = new bitcoin.Client({ 235 | host: 'localhost', 236 | port: 19999, 237 | user: 'admin1', 238 | pass: '123', 239 | timeout: 2500 240 | }) 241 | var start = Date.now() 242 | client.getInfo(function (err, info) { 243 | var delta = Date.now() - start 244 | assert.ok(err instanceof Error) 245 | assert.ok(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') 246 | assert.ok(delta >= 2500, 'delta should be >= 2500:' + delta) 247 | response.end() 248 | server.close() 249 | done() 250 | }) 251 | }) 252 | }) 253 | 254 | describe('response headers', function () { 255 | var assertResHeaders = function (resHeaders) { 256 | assert.ok(resHeaders) 257 | assert.ok(resHeaders.server) 258 | assert.ok(/bitcoin/.test(resHeaders.server)) 259 | } 260 | it('should be returned for no parameter calls', function (done) { 261 | var client = makeClient() 262 | client.getInfo(function (err, info, resHeaders) { 263 | assert.ifError(err) 264 | notEmpty(info) 265 | assert.ok(info.errors === '') 266 | assertResHeaders(resHeaders) 267 | done() 268 | }) 269 | }) 270 | 271 | it('should be returned for 1-parameter call', function (done) { 272 | var client = makeClient() 273 | client.getNewAddress(test.account, function (err, address, resHeaders) { 274 | assert.ifError(err) 275 | assert.ok(address) 276 | assertResHeaders(resHeaders) 277 | done() 278 | }) 279 | 280 | it('should be returned for 2-parameter call', function (done) { 281 | var client = makeClient() 282 | client.listTransactions(test.account, 15, function (err, txs, resHeaders) { 283 | assert.ifError(err) 284 | assert.ok(txs) 285 | assert.ok(Array.isArray(txs)) 286 | assertResHeaders(resHeaders) 287 | done() 288 | }) 289 | }) 290 | }) 291 | }) 292 | }) 293 | -------------------------------------------------------------------------------- /test/missing-commands.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | var assert = require('assert') 4 | var bitcoin = require('../') 5 | var config = require('./config') 6 | var commands = require('../lib/commands') 7 | 8 | var getHelpCommands = function (client, cb) { 9 | var commandRegex = /^([a-z]+)/ 10 | client.cmd('help', function (err, commandList) { 11 | if (err) return cb(err) 12 | 13 | var helpCommands = [] 14 | 15 | // split up the command list by newlines 16 | var commandListLines = commandList.split('\n') 17 | var result 18 | for (var i in commandListLines) { 19 | result = commandRegex.exec(commandListLines[i]) 20 | if (!result) continue 21 | helpCommands.push(result[1]) 22 | } 23 | cb(null, helpCommands) 24 | }) 25 | } 26 | 27 | describe('Client Commands', function () { 28 | it('should have all the commands listed by `help`', function (done) { 29 | var client = new bitcoin.Client(config) 30 | getHelpCommands(client, function (err, helpCommands) { 31 | assert.ifError(err) 32 | 33 | for (var i in helpCommands) { 34 | var found = false 35 | for (var j in commands) { 36 | if (commands[j] === helpCommands[i]) { 37 | found = true 38 | break 39 | } 40 | } 41 | assert.ok(found, 'missing command found in `help`: ' + helpCommands[i]) 42 | } 43 | 44 | done() 45 | }) 46 | }) 47 | 48 | it('should not have any commands not listed by `help`', function (done) { 49 | var client = new bitcoin.Client(config) 50 | getHelpCommands(client, function (err, helpCommands) { 51 | assert.ifError(err) 52 | 53 | for (var i in commands) { 54 | var found = false 55 | for (var j in helpCommands) { 56 | if (commands[i] === helpCommands[j]) { 57 | found = true 58 | break 59 | } 60 | } 61 | 62 | // ignore commands not found in help because they are hidden 63 | // if the wallet isn't encrypted 64 | var ignore = ['walletlock', 'walletpassphrase', 'walletpassphrasechange'] 65 | if (~ignore.indexOf(commands[i])) { 66 | assert.ok(!found, 'command found in `help`: ' + commands[i]) 67 | } else { 68 | assert.ok(found, 'command not found in `help`: ' + commands[i]) 69 | } 70 | } 71 | 72 | done() 73 | }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | -------------------------------------------------------------------------------- /test/ssl.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | var assert = require('assert') 4 | var fs = require('fs') 5 | var clone = require('clone') 6 | var bitcoin = require('../') 7 | var config = require('./config') 8 | 9 | var getInfo = function (opts, cb) { 10 | var client = new bitcoin.Client(opts) 11 | client.getInfo(cb) 12 | } 13 | 14 | describe('Client SSL', function () { 15 | it('use sslStrict by default', function (done) { 16 | var opts = clone(config) 17 | opts.ssl = true 18 | getInfo(opts, function (err, info) { 19 | assert.ok(err instanceof Error) 20 | // node v0.11 adds `code` param to this error 21 | // and uses a user-friendly `message` 22 | // continue using err.message for v0.8 and v0.10 23 | assert.equal(err.code || err.message, 'DEPTH_ZERO_SELF_SIGNED_CERT') 24 | done() 25 | }) 26 | }) 27 | 28 | it('strictSSL should fail with self-signed certificate', function (done) { 29 | var opts = clone(config) 30 | opts.ssl = true 31 | opts.sslStrict = true 32 | getInfo(opts, function (err, info) { 33 | assert.ok(err instanceof Error) 34 | // node v0.11 adds `code` param to this error 35 | // and uses a user-friendly `message` 36 | // continue using err.message for v0.8 and v0.10 37 | assert.equal(err.code || err.message, 'DEPTH_ZERO_SELF_SIGNED_CERT') 38 | done() 39 | }) 40 | }) 41 | 42 | it('self-signed certificate with sslStrict false', function (done) { 43 | var opts = clone(config) 44 | opts.ssl = true 45 | opts.sslStrict = false 46 | getInfo(opts, function (err, info) { 47 | assert.ifError(err) 48 | assert.ok(info) 49 | done() 50 | }) 51 | }) 52 | 53 | it('self-signed certificate with sslStrict and CA specified', function (done) { 54 | var opts = clone(config) 55 | opts.ssl = true 56 | opts.sslCa = fs.readFileSync(__dirname + '/testnet-box/1/regtest/server.cert') 57 | getInfo(opts, function (err, info) { 58 | assert.ifError(err) 59 | assert.ok(info) 60 | done() 61 | }) 62 | }) 63 | }) 64 | --------------------------------------------------------------------------------