-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
300 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
var http = require('http') | ||
, https = require('https') | ||
|
||
var api = require('./commands') | ||
, errors = require('./errors') | ||
|
||
function Client(options) { | ||
this.opts = { | ||
host: 'localhost' | ||
, port: 7142 | ||
, method: 'POST' | ||
, user: '' | ||
, pass: '' | ||
, headers: { | ||
'Host': 'localhost' | ||
, 'Authorization': '' | ||
} | ||
, passphrasecallback: null | ||
, https: false | ||
, ca: null | ||
} | ||
|
||
if (options) { | ||
this.set(options) | ||
} | ||
} | ||
|
||
Client.prototype = { | ||
|
||
invalid:function(command) { | ||
var args = Array.prototype.slice.call(arguments, 1) | ||
, fn = args.pop() | ||
|
||
if (typeof fn !== 'function') { | ||
fn = console.log | ||
} | ||
|
||
return fn(new Error('No such command "' + command + '"')) | ||
}, | ||
|
||
send:function(command) { | ||
var args = Array.prototype.slice.call(arguments, 1) | ||
, self = this | ||
, fn | ||
|
||
if (typeof args[args.length-1] === 'function') { | ||
fn = args.pop().bind(this) | ||
}else { | ||
fn = console.log | ||
} | ||
|
||
var rpcData = JSON.stringify({ | ||
id: new Date().getTime() | ||
, method: command.toLowerCase() | ||
, params: args | ||
}) | ||
|
||
var options = this.opts | ||
options.headers['Content-Length'] = rpcData.length | ||
|
||
var request; | ||
if (options.https === true){ | ||
request = https.request; | ||
} else{ | ||
request = http.request; | ||
} | ||
|
||
var req = request(options, function(res) { | ||
var data = '' | ||
res.setEncoding('utf8') | ||
res.on('data', function(chunk) { | ||
data += chunk | ||
}) | ||
res.on('end', function() { | ||
try { | ||
data = JSON.parse(data) | ||
}catch(exception) { | ||
var errMsg = res.statusCode !== 200 | ||
? 'Invalid params ' + res.statusCode | ||
: 'Failed to parse JSON' | ||
errMsg += ' : '+JSON.stringify(data) | ||
return fn(new Error(errMsg)) | ||
} | ||
if (data.error) { | ||
if (data.error.code === errors.RPC_WALLET_UNLOCK_NEEDED && | ||
options.passphrasecallback) { | ||
return self.unlock(command, args, fn) | ||
} else { | ||
var err = new Error(JSON.stringify(data)) | ||
err.code = data.error.code | ||
return fn(err) | ||
} | ||
} | ||
fn(null, data.result !== null ? data.result : data) | ||
}) | ||
}) | ||
|
||
req.on('error', fn) | ||
req.end(rpcData) | ||
return this | ||
}, | ||
|
||
exec:function(command) { | ||
var func = api.isCommand(command) ? 'send' : 'invalid' | ||
return this[func].apply(this, arguments) | ||
}, | ||
|
||
auth:function(user, pass) { | ||
if (user && pass) { | ||
var authString = ('Basic ') + new Buffer(user+':'+pass).toString('base64') | ||
this.opts.headers['Authorization'] = authString | ||
} | ||
return this | ||
}, | ||
|
||
unlock:function(command, args, fn) { | ||
var self = this | ||
|
||
var retry = function(err) { | ||
if (err) { | ||
fn(err) | ||
} else { | ||
var sendargs = args.slice(); | ||
sendargs.unshift(command); | ||
sendargs.push(fn) | ||
self.send.apply(self, sendargs) | ||
} | ||
} | ||
|
||
this.opts.passphrasecallback(command, args, function(err, passphrase, timeout) { | ||
if (err) { | ||
fn(err) | ||
} else { | ||
self.send('walletpassphrase', passphrase, timeout, retry) | ||
} | ||
}) | ||
}, | ||
|
||
set:function(k, v) { | ||
var type = typeof(k); | ||
|
||
if (typeof(k) === 'object') { | ||
for (var key in k) { | ||
this.set(key, k[key]); | ||
}; | ||
return; | ||
}; | ||
|
||
var opts = this.opts; | ||
var k = k.toLowerCase(); | ||
|
||
if (opts.hasOwnProperty(k)) { | ||
opts[k] = v | ||
if (/^(user|pass)$/.test(k)) { | ||
this.auth(opts.user, opts.pass) | ||
}else if (k === 'host') { | ||
opts.headers['Host'] = v | ||
}else if (k === 'passphrasecallback' || | ||
k === 'https' || | ||
k === 'ca') { | ||
opts[k] = v | ||
} | ||
} | ||
|
||
return this; | ||
}, | ||
|
||
get:function(k) { | ||
//Special case for booleans | ||
if(this.opts[k] === false){ | ||
return false; | ||
}else { | ||
if(this.opts[k] !== false){ | ||
var opt = this.opts[k.toLowerCase()] | ||
} | ||
return opt; //new Error('No such option "'+k+'" exists'); | ||
} | ||
}, | ||
|
||
errors: errors | ||
|
||
} | ||
|
||
api.commands.forEach(function(command) { | ||
var cp = Client.prototype; | ||
var tlc = [command.toLowerCase()]; | ||
|
||
cp[command] = cp[tlc] = function() { | ||
cp.send.apply(this, tlc.concat(([]).slice.call(arguments))); | ||
}; | ||
}) | ||
|
||
module.exports = function(options) { | ||
return new Client(options) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
|
||
var commands = module.exports.commands = [ | ||
'addMultiSigAddress' | ||
, 'backupWallet' | ||
, 'createRawTransaction' | ||
, 'decodeRawTransaction' | ||
, 'dumpPrivKey' | ||
, 'encryptWallet' | ||
, 'getAccount' | ||
, 'getAccountAddress' | ||
, 'getAddressesByAccount' | ||
, 'getBalance' | ||
, 'getBlock' | ||
, 'getBlockCount' | ||
, 'getBlockHash' | ||
, 'getBlockCount' | ||
, 'getConnectionCount' | ||
, 'getDifficulty' | ||
, 'getGenerate' | ||
, 'getHashesPerSec' | ||
, 'getHashesPerSec' | ||
, 'getInfo' | ||
, 'getnetworkhashps' | ||
, 'getMemoryPool' | ||
, 'getMemoryPool' | ||
, 'getMiningInfo' | ||
, 'getNewAddress' | ||
, 'getRawTransaction' | ||
, 'getReceivedByAccount' | ||
, 'getReceivedByAddress' | ||
, 'getTransaction' | ||
, 'getWork' | ||
, 'help' | ||
, 'importPrivKey' | ||
, 'keyPoolRefill' | ||
, 'listAccounts' | ||
, 'listReceivedByAccount' | ||
, 'listReceivedByAddress' | ||
, 'listSinceBlock' | ||
, 'listTransactions' | ||
, 'listUnspent' | ||
, 'move' | ||
, 'sendFrom' | ||
, 'sendMany' | ||
, 'sendRawTransaction' | ||
, 'sendToAddress' | ||
, 'setAccount' | ||
, 'setGenerate' | ||
, 'setTxFee' | ||
, 'signMessage' | ||
, 'signRawTransaction' | ||
, 'stop' | ||
, 'validateAddress' | ||
, 'verifyMessage' | ||
, 'walletLock' | ||
, 'walletPassphrase' | ||
, 'walletPassphraseChange' | ||
] | ||
|
||
module.exports.isCommand = function(command) { | ||
command = command.toLowerCase() | ||
for (var i=0, len=commands.length; i<len; i++) { | ||
if (commands[i].toLowerCase() === command) { | ||
return true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
module.exports = { | ||
RPC_INVALID_REQUEST : -32600, | ||
RPC_METHOD_NOT_FOUND : -32601, | ||
RPC_INVALID_PARAMS : -32602, | ||
RPC_INTERNAL_ERROR : -32603, | ||
RPC_PARSE_ERROR : -32700, | ||
|
||
// General application defined errors | ||
RPC_MISC_ERROR : -1, // std::exception thrown in command handling | ||
RPC_FORBIDDEN_BY_SAFE_MODE : -2, // Server is in safe mode, and command is not allowed in safe mode | ||
RPC_TYPE_ERROR : -3, // Unexpected type was passed as parameter | ||
RPC_INVALID_ADDRESS_OR_KEY : -5, // Invalid address or key | ||
RPC_OUT_OF_MEMORY : -7, // Ran out of memory during operation | ||
RPC_INVALID_PARAMETER : -8, // Invalid, missing or duplicate parameter | ||
RPC_DATABASE_ERROR : -20, // Database error | ||
RPC_DESERIALIZATION_ERROR : -22, // Error parsing or validating structure in raw format | ||
|
||
// P2P client errors | ||
RPC_CLIENT_NOT_CONNECTED : -9, // Bitcoin is not connected | ||
RPC_CLIENT_IN_INITIAL_DOWNLOAD : -10, // Still downloading initial blocks | ||
|
||
// Wallet errors | ||
RPC_WALLET_ERROR : -4, // Unspecified problem with wallet (key not found etc.) | ||
RPC_WALLET_INSUFFICIENT_FUNDS : -6, // Not enough funds in wallet or account | ||
RPC_WALLET_INVALID_ACCOUNT_NAME : -11, // Invalid account name | ||
RPC_WALLET_KEYPOOL_RAN_OUT : -12, // Keypool ran out, call keypoolrefill first | ||
RPC_WALLET_UNLOCK_NEEDED : -13, // Enter the wallet passphrase with walletpassphrase first | ||
RPC_WALLET_PASSPHRASE_INCORRECT : -14, // The wallet passphrase entered was incorrect | ||
RPC_WALLET_WRONG_ENC_STATE : -15, // Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) | ||
RPC_WALLET_ENCRYPTION_FAILED : -16, // Failed to encrypt the wallet | ||
RPC_WALLET_ALREADY_UNLOCKED : -17 // Wallet is already unlocked | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters