blocktrail /
blocktrail-sdk-nodejs
| 1 | /* globals onLoadWorkerLoadAsmCrypto */ |
||
| 2 | |||
| 3 | var _ = require('lodash'), |
||
| 4 | q = require('q'), |
||
| 5 | bitcoin = require('bitcoinjs-lib'), |
||
| 6 | bitcoinMessage = require('bitcoinjs-message'), |
||
| 7 | |||
| 8 | bip39 = require("bip39"), |
||
| 9 | Wallet = require('./wallet'), |
||
| 10 | BtccomConverter = require('./btccom.convert'), |
||
| 11 | BlocktrailConverter = require('./blocktrail.convert'), |
||
| 12 | RestClient = require('./rest_client'), |
||
| 13 | Encryption = require('./encryption'), |
||
| 14 | KeyDerivation = require('./keyderivation'), |
||
| 15 | EncryptionMnemonic = require('./encryption_mnemonic'), |
||
| 16 | blocktrail = require('./blocktrail'), |
||
| 17 | randomBytes = require('randombytes'), |
||
| 18 | CryptoJS = require('crypto-js'), |
||
| 19 | webworkifier = require('./webworkifier'); |
||
| 20 | |||
| 21 | /** |
||
| 22 | * |
||
| 23 | * @param opt |
||
| 24 | * @returns {*} |
||
| 25 | */ |
||
| 26 | function networkFromOptions(opt) { |
||
| 27 | if (opt.bitcoinCash) { |
||
| 28 | if (opt.regtest) { |
||
| 29 | return bitcoin.networks.bitcoincashregtest; |
||
| 30 | } else if (opt.testnet) { |
||
| 31 | return bitcoin.networks.bitcoincashtestnet; |
||
| 32 | } else { |
||
| 33 | return bitcoin.networks.bitcoincash; |
||
| 34 | } |
||
| 35 | } else { |
||
| 36 | if (opt.regtest) { |
||
| 37 | return bitcoin.networks.regtest; |
||
| 38 | } else if (opt.testnet) { |
||
| 39 | return bitcoin.networks.testnet; |
||
| 40 | } else { |
||
| 41 | return bitcoin.networks.bitcoin; |
||
| 42 | } |
||
| 43 | } |
||
| 44 | } |
||
| 45 | |||
| 46 | var useWebWorker = require('./use-webworker')(); |
||
| 47 | |||
| 48 | |||
| 49 | /** |
||
| 50 | * helper to wrap a promise so that the callback get's called when it succeeds or fails |
||
| 51 | * |
||
| 52 | * @param promise {q.Promise} |
||
| 53 | * @param cb function |
||
| 54 | * @return q.Promise |
||
| 55 | */ |
||
| 56 | function callbackify(promise, cb) { |
||
| 57 | // add a .then to trigger the cb for people using callbacks |
||
| 58 | if (cb) { |
||
| 59 | promise |
||
| 60 | .then(function(res) { |
||
| 61 | // use q.nextTick for asyncness |
||
| 62 | q.nextTick(function() { |
||
| 63 | cb(null, res); |
||
| 64 | }); |
||
| 65 | }, function(err) { |
||
| 66 | // use q.nextTick for asyncness |
||
| 67 | q.nextTick(function() { |
||
| 68 | cb(err, null); |
||
| 69 | }); |
||
| 70 | }); |
||
| 71 | } |
||
| 72 | |||
| 73 | // return the promise for people using promises |
||
| 74 | return promise; |
||
| 75 | } |
||
| 76 | |||
| 77 | /** |
||
| 78 | * Bindings to consume the BlockTrail API |
||
| 79 | * |
||
| 80 | * @param options object{ |
||
| 81 | * apiKey: 'API_KEY', |
||
| 82 | * apiSecret: 'API_SECRET', |
||
| 83 | * host: 'defaults to api.blocktrail.com', |
||
| 84 | * network: 'BTC|LTC', |
||
| 85 | * testnet: true|false |
||
| 86 | * } |
||
| 87 | * @constructor |
||
| 88 | */ |
||
| 89 | var APIClient = function(options) { |
||
| 90 | var self = this; |
||
| 91 | |||
| 92 | // handle constructor call without 'new' |
||
| 93 | if (!(this instanceof APIClient)) { |
||
| 94 | return new APIClient(options); |
||
| 95 | } |
||
| 96 | |||
| 97 | var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options); |
||
| 98 | options.network = normalizedNetwork[0]; |
||
| 99 | options.testnet = normalizedNetwork[1]; |
||
| 100 | options.regtest = normalizedNetwork[2]; |
||
| 101 | // apiNetwork we allow to be customized for debugging purposes |
||
| 102 | options.apiNetwork = options.apiNetwork || normalizedNetwork[3]; |
||
| 103 | |||
| 104 | self.bitcoinCash = options.network === "BCC"; |
||
| 105 | self.regtest = options.regtest; |
||
| 106 | self.testnet = options.testnet; |
||
| 107 | self.network = networkFromOptions(self); |
||
| 108 | self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true; |
||
| 109 | self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200; |
||
| 110 | |||
| 111 | options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase()); |
||
| 112 | |||
| 113 | if (typeof options.btccom === "undefined") { |
||
| 114 | options.btccom = true; |
||
| 115 | } |
||
| 116 | |||
| 117 | /** |
||
| 118 | * @type RestClient |
||
| 119 | */ |
||
| 120 | var dataOptions = _.omit(options, 'host'); |
||
| 121 | self.dataClient = APIClient.initRestClient(dataOptions); |
||
| 122 | /** |
||
| 123 | * @type RestClient |
||
| 124 | */ |
||
| 125 | self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false})); |
||
| 126 | |||
| 127 | if (options.btccom) { |
||
| 128 | self.converter = new BtccomConverter(self.network, true); |
||
|
0 ignored issues
–
show
Best Practice
introduced
by
Loading history...
|
|||
| 129 | } else { |
||
| 130 | self.converter = new BlocktrailConverter(); |
||
|
0 ignored issues
–
show
|
|||
| 131 | } |
||
| 132 | |||
| 133 | }; |
||
| 134 | |||
| 135 | APIClient.normalizeNetworkFromOptions = function(options) { |
||
| 136 | /* jshint -W071, -W074 */ |
||
| 137 | var network = 'BTC'; |
||
| 138 | var testnet = false; |
||
| 139 | var regtest = false; |
||
| 140 | var apiNetwork = "BTC"; |
||
|
0 ignored issues
–
show
|
|||
| 141 | |||
| 142 | var prefix; |
||
| 143 | var done = false; |
||
| 144 | |||
| 145 | if (options.network) { |
||
| 146 | var lower = options.network.toLowerCase(); |
||
| 147 | |||
| 148 | var m = lower.match(/^([rt])?(btc|bch|bcc)$/); |
||
| 149 | if (!m) { |
||
| 150 | throw new Error("Invalid network [" + options.network + "]"); |
||
| 151 | } |
||
| 152 | |||
| 153 | if (m[2] === 'btc') { |
||
| 154 | network = "BTC"; |
||
| 155 | } else { |
||
| 156 | network = "BCC"; |
||
| 157 | } |
||
| 158 | |||
| 159 | prefix = m[1]; |
||
| 160 | if (prefix) { |
||
| 161 | // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after |
||
| 162 | done = true; |
||
| 163 | if (prefix === 'r') { |
||
| 164 | testnet = true; |
||
| 165 | regtest = true; |
||
| 166 | } else if (prefix === 't') { |
||
| 167 | testnet = true; |
||
| 168 | } |
||
| 169 | } |
||
| 170 | } |
||
| 171 | |||
| 172 | // if we're not already done then apply options.regtest and options.testnet |
||
| 173 | if (!done) { |
||
| 174 | if (options.regtest) { |
||
| 175 | testnet = true; |
||
| 176 | regtest = true; |
||
| 177 | prefix = "r"; |
||
| 178 | } else if (options.testnet) { |
||
| 179 | testnet = true; |
||
| 180 | prefix = "t"; |
||
| 181 | } |
||
| 182 | } |
||
| 183 | |||
| 184 | apiNetwork = (prefix || "") + network; |
||
| 185 | |||
| 186 | return [network, testnet, regtest, apiNetwork]; |
||
| 187 | }; |
||
| 188 | |||
| 189 | APIClient.updateHostOptions = function(options) { |
||
| 190 | /* jshint -W071, -W074 */ |
||
| 191 | // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development |
||
| 192 | if (!options.btccom && process.env.BLOCKTRAIL_SDK_API_ENDPOINT) { |
||
| 193 | options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT; |
||
| 194 | } |
||
| 195 | if (options.btccom && process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT) { |
||
| 196 | options.host = process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT; |
||
| 197 | } |
||
| 198 | |||
| 199 | if (options.btccom && process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM) { |
||
| 200 | options.throttleRequestsTimeout = process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM; |
||
| 201 | } |
||
| 202 | |||
| 203 | if (options.btccom) { |
||
| 204 | if (!options.host) { |
||
| 205 | options.host = options.btccomhost || (options.network === 'BCC' ? 'bch-chain.api.btc.com' : 'chain.api.btc.com'); |
||
| 206 | } |
||
| 207 | |||
| 208 | if (options.testnet && !options.host.match(/tchain/)) { |
||
| 209 | options.host = options.host.replace(/chain/, 'tchain'); |
||
| 210 | } |
||
| 211 | |||
| 212 | if (!options.endpoint) { |
||
| 213 | options.endpoint = options.btccomendpoint || ("/" + (options.apiVersion || "v3")); |
||
| 214 | } |
||
| 215 | } else { |
||
| 216 | if (!options.host) { |
||
| 217 | options.host = 'api.blocktrail.com'; |
||
| 218 | } |
||
| 219 | |||
| 220 | if (!options.endpoint) { |
||
| 221 | options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : ""); |
||
| 222 | } |
||
| 223 | } |
||
| 224 | |||
| 225 | // trim off leading https?:// |
||
| 226 | if (options.host && options.host.indexOf("https://") === 0) { |
||
| 227 | options.https = true; |
||
| 228 | options.host = options.host.substr(8); |
||
| 229 | } else if (options.host && options.host.indexOf("http://") === 0) { |
||
| 230 | options.https = false; |
||
| 231 | options.host = options.host.substr(7); |
||
| 232 | } |
||
| 233 | |||
| 234 | if (typeof options.https === "undefined") { |
||
| 235 | options.https = true; |
||
| 236 | } |
||
| 237 | |||
| 238 | if (!options.port) { |
||
| 239 | options.port = options.https ? 443 : 80; |
||
| 240 | } |
||
| 241 | |||
| 242 | return options; |
||
| 243 | }; |
||
| 244 | |||
| 245 | APIClient.initRestClient = function(options) { |
||
| 246 | options = APIClient.updateHostOptions(options); |
||
| 247 | return new RestClient(options); |
||
| 248 | }; |
||
| 249 | |||
| 250 | var determineDataStorageV2_3 = function(options) { |
||
| 251 | return q.when(options) |
||
| 252 | .then(function(options) { |
||
| 253 | // legacy |
||
| 254 | if (options.storePrimaryMnemonic) { |
||
| 255 | options.storeDataOnServer = options.storePrimaryMnemonic; |
||
| 256 | } |
||
| 257 | |||
| 258 | // storeDataOnServer=false when primarySeed is provided |
||
| 259 | if (typeof options.storeDataOnServer === "undefined") { |
||
| 260 | options.storeDataOnServer = !options.primarySeed; |
||
| 261 | } |
||
| 262 | |||
| 263 | return options; |
||
| 264 | }); |
||
| 265 | }; |
||
| 266 | |||
| 267 | var produceEncryptedDataV2 = function(options, notify) { |
||
| 268 | return q.when(options) |
||
| 269 | .then(function(options) { |
||
| 270 | if (options.storeDataOnServer) { |
||
| 271 | if (!options.secret) { |
||
| 272 | if (!options.passphrase) { |
||
| 273 | throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase"); |
||
| 274 | } |
||
| 275 | |||
| 276 | notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET); |
||
| 277 | |||
| 278 | options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase |
||
| 279 | options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string |
||
| 280 | } |
||
| 281 | |||
| 282 | notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY); |
||
| 283 | |||
| 284 | options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret) |
||
| 285 | .toString(CryptoJS.format.OpenSSL); // 'base64' string |
||
| 286 | options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase |
||
| 287 | |||
| 288 | notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY); |
||
| 289 | |||
| 290 | options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret) |
||
| 291 | .toString(CryptoJS.format.OpenSSL); // 'base64' string |
||
| 292 | } |
||
| 293 | |||
| 294 | return options; |
||
| 295 | }); |
||
| 296 | }; |
||
| 297 | |||
| 298 | APIClient.prototype.promisedEncrypt = function(pt, pw, iter) { |
||
| 299 | if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") { |
||
| 300 | // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers |
||
| 301 | var saltBuf = Encryption.generateSalt(); |
||
| 302 | var iv = Encryption.generateIV(); |
||
| 303 | |||
| 304 | return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() { |
||
| 305 | return require('./webworker'); |
||
| 306 | }, onLoadWorkerLoadAsmCrypto, { |
||
| 307 | method: 'Encryption.encryptWithSaltAndIV', |
||
| 308 | pt: pt, |
||
| 309 | pw: pw, |
||
| 310 | saltBuf: saltBuf, |
||
| 311 | iv: iv, |
||
| 312 | iterations: iter |
||
| 313 | }) |
||
| 314 | .then(function(data) { |
||
| 315 | return Buffer.from(data.cipherText.buffer); |
||
|
0 ignored issues
–
show
The variable
Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. Loading history...
|
|||
| 316 | }); |
||
| 317 | } else { |
||
| 318 | try { |
||
| 319 | return q.when(Encryption.encrypt(pt, pw, iter)); |
||
| 320 | } catch (e) { |
||
| 321 | return q.reject(e); |
||
| 322 | } |
||
| 323 | } |
||
| 324 | }; |
||
| 325 | |||
| 326 | APIClient.prototype.promisedDecrypt = function(ct, pw) { |
||
| 327 | if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") { |
||
| 328 | return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() { |
||
| 329 | return require('./webworker'); |
||
| 330 | }, onLoadWorkerLoadAsmCrypto, { |
||
| 331 | method: 'Encryption.decrypt', |
||
| 332 | ct: ct, |
||
| 333 | pw: pw |
||
| 334 | }) |
||
| 335 | .then(function(data) { |
||
| 336 | return Buffer.from(data.plainText.buffer); |
||
|
0 ignored issues
–
show
The variable
Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. Loading history...
|
|||
| 337 | }); |
||
| 338 | } else { |
||
| 339 | try { |
||
| 340 | return q.when(Encryption.decrypt(ct, pw)); |
||
| 341 | } catch (e) { |
||
| 342 | return q.reject(e); |
||
| 343 | } |
||
| 344 | } |
||
| 345 | }; |
||
| 346 | |||
| 347 | APIClient.prototype.produceEncryptedDataV3 = function(options, notify) { |
||
| 348 | var self = this; |
||
| 349 | |||
| 350 | return q.when(options) |
||
| 351 | .then(function(options) { |
||
| 352 | if (options.storeDataOnServer) { |
||
| 353 | return q.when() |
||
| 354 | .then(function() { |
||
| 355 | if (!options.secret) { |
||
| 356 | if (!options.passphrase) { |
||
| 357 | throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase"); |
||
| 358 | } |
||
| 359 | |||
| 360 | notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET); |
||
| 361 | |||
| 362 | // -> now a buffer |
||
| 363 | options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8); |
||
| 364 | |||
| 365 | // -> now a buffer |
||
| 366 | return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations) |
||
|
0 ignored issues
–
show
The variable
Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. Loading history...
|
|||
| 367 | .then(function(encryptedSecret) { |
||
| 368 | options.encryptedSecret = encryptedSecret; |
||
| 369 | }); |
||
| 370 | } else { |
||
| 371 | if (!(options.secret instanceof Buffer)) { |
||
|
0 ignored issues
–
show
The variable
Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. Loading history...
There is no return statement if
!(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.
This check looks for functions where a Consider this little piece of code function isBig(a) {
if (a > 5000) {
return "yes";
}
}
console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined
The function This behaviour may not be what you had intended. In any case, you can add a
Loading history...
|
|||
| 372 | throw new Error('Secret must be a buffer'); |
||
| 373 | } |
||
| 374 | } |
||
| 375 | }) |
||
| 376 | .then(function() { |
||
| 377 | notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY); |
||
| 378 | |||
| 379 | return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations) |
||
| 380 | .then(function(encryptedPrimarySeed) { |
||
| 381 | options.encryptedPrimarySeed = encryptedPrimarySeed; |
||
| 382 | }); |
||
| 383 | }) |
||
| 384 | .then(function() { |
||
| 385 | // skip generating recovery secret when explicitly set to false |
||
| 386 | if (options.recoverySecret === false) { |
||
| 387 | return; |
||
| 388 | } |
||
| 389 | |||
| 390 | notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY); |
||
| 391 | if (!options.recoverySecret) { |
||
| 392 | options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8); |
||
| 393 | } |
||
| 394 | |||
| 395 | return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations) |
||
| 396 | .then(function(recoveryEncryptedSecret) { |
||
| 397 | options.recoveryEncryptedSecret = recoveryEncryptedSecret; |
||
| 398 | }); |
||
| 399 | }) |
||
| 400 | .then(function() { |
||
| 401 | return options; |
||
| 402 | }); |
||
| 403 | } else { |
||
| 404 | return options; |
||
| 405 | } |
||
| 406 | }); |
||
| 407 | }; |
||
| 408 | |||
| 409 | var doRemainingWalletDataV2_3 = function(options, network, notify) { |
||
| 410 | return q.when(options) |
||
| 411 | .then(function(options) { |
||
| 412 | if (!options.backupPublicKey) { |
||
| 413 | options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8); |
||
| 414 | } |
||
| 415 | |||
| 416 | notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY); |
||
| 417 | |||
| 418 | options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network); |
||
| 419 | |||
| 420 | notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP); |
||
| 421 | |||
| 422 | if (!options.backupPublicKey) { |
||
| 423 | options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network); |
||
| 424 | options.backupPublicKey = options.backupPrivateKey.neutered(); |
||
| 425 | } |
||
| 426 | |||
| 427 | options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered(); |
||
| 428 | |||
| 429 | notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT); |
||
| 430 | |||
| 431 | return options; |
||
| 432 | }); |
||
| 433 | }; |
||
| 434 | |||
| 435 | APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) { |
||
| 436 | var self = this; |
||
| 437 | |||
| 438 | var deferred = q.defer(); |
||
| 439 | deferred.promise.spreadNodeify(cb); |
||
| 440 | |||
| 441 | deferred.resolve(q.fcall(function() { |
||
| 442 | return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) { |
||
| 443 | return bitcoin.HDNode.fromSeedHex(seedHex, self.network); |
||
| 444 | }); |
||
| 445 | })); |
||
| 446 | |||
| 447 | return deferred.promise; |
||
| 448 | }; |
||
| 449 | |||
| 450 | APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) { |
||
| 451 | var self = this; |
||
| 452 | |||
| 453 | if (useWebWorker) { |
||
| 454 | return webworkifier.workify(self.mnemonicToSeedHex, function() { |
||
| 455 | return require('./webworker'); |
||
| 456 | }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase}) |
||
| 457 | .then(function(data) { |
||
| 458 | return data.seed; |
||
| 459 | }); |
||
| 460 | } else { |
||
| 461 | try { |
||
| 462 | return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase)); |
||
| 463 | } catch (e) { |
||
| 464 | return q.reject(e); |
||
| 465 | } |
||
| 466 | } |
||
| 467 | }; |
||
| 468 | |||
| 469 | APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) { |
||
| 470 | var self = this; |
||
| 471 | |||
| 472 | var deferred = q.defer(); |
||
| 473 | deferred.promise.nodeify(cb); |
||
| 474 | |||
| 475 | try { |
||
| 476 | // avoid conflicting options |
||
| 477 | if (options.passphrase && options.password) { |
||
| 478 | throw new blocktrail.WalletCreateError("Can't specify passphrase and password"); |
||
| 479 | } |
||
| 480 | // normalize passphrase/password |
||
| 481 | options.passphrase = options.passphrase || options.password; |
||
| 482 | delete options.password; |
||
| 483 | |||
| 484 | // avoid conflicting options |
||
| 485 | if (options.primaryMnemonic && options.primarySeed) { |
||
| 486 | throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed"); |
||
| 487 | } |
||
| 488 | |||
| 489 | // avoid deprecated options |
||
| 490 | if (options.primaryPrivateKey) { |
||
| 491 | throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey"); |
||
| 492 | } |
||
| 493 | |||
| 494 | // make sure we have at least one thing to use |
||
| 495 | if (!options.primaryMnemonic && !options.primarySeed) { |
||
| 496 | throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed"); |
||
| 497 | } |
||
| 498 | |||
| 499 | if (options.primarySeed) { |
||
| 500 | self.primarySeed = options.primarySeed; |
||
| 501 | options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network); |
||
| 502 | deferred.resolve(options); |
||
| 503 | } else { |
||
| 504 | if (!options.passphrase) { |
||
| 505 | throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase"); |
||
| 506 | } |
||
| 507 | |||
| 508 | self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase) |
||
| 509 | .then(function(seedHex) { |
||
| 510 | try { |
||
| 511 | options.primarySeed = new Buffer(seedHex, 'hex'); |
||
|
0 ignored issues
–
show
The variable
Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. Loading history...
|
|||
| 512 | options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network); |
||
| 513 | deferred.resolve(options); |
||
| 514 | } catch (e) { |
||
| 515 | deferred.reject(e); |
||
| 516 | } |
||
| 517 | }, function(e) { |
||
| 518 | deferred.reject(e); |
||
| 519 | }); |
||
| 520 | } |
||
| 521 | } catch (e) { |
||
| 522 | deferred.reject(e); |
||
| 523 | } |
||
| 524 | |||
| 525 | return deferred.promise; |
||
| 526 | }; |
||
| 527 | |||
| 528 | APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) { |
||
| 529 | var self = this; |
||
| 530 | |||
| 531 | var deferred = q.defer(); |
||
| 532 | deferred.promise.nodeify(cb); |
||
| 533 | |||
| 534 | try { |
||
| 535 | // avoid conflicting options |
||
| 536 | if (options.backupMnemonic && options.backupPublicKey) { |
||
| 537 | throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey"); |
||
| 538 | } |
||
| 539 | |||
| 540 | // make sure we have at least one thing to use |
||
| 541 | if (!options.backupMnemonic && !options.backupPublicKey) { |
||
| 542 | throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey"); |
||
| 543 | } |
||
| 544 | |||
| 545 | if (options.backupPublicKey) { |
||
| 546 | if (options.backupPublicKey instanceof bitcoin.HDNode) { |
||
| 547 | deferred.resolve(options); |
||
| 548 | } else { |
||
| 549 | options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network); |
||
| 550 | deferred.resolve(options); |
||
| 551 | } |
||
| 552 | } else { |
||
| 553 | self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) { |
||
| 554 | options.backupPublicKey = backupPrivateKey.neutered(); |
||
| 555 | deferred.resolve(options); |
||
| 556 | }, function(e) { |
||
| 557 | deferred.reject(e); |
||
| 558 | }); |
||
| 559 | } |
||
| 560 | } catch (e) { |
||
| 561 | deferred.reject(e); |
||
| 562 | } |
||
| 563 | |||
| 564 | return deferred.promise; |
||
| 565 | }; |
||
| 566 | |||
| 567 | APIClient.prototype.debugAuth = function(cb) { |
||
| 568 | var self = this; |
||
| 569 | |||
| 570 | return self.dataClient.get("/debug/http-signature", null, true, cb); |
||
| 571 | }; |
||
| 572 | |||
| 573 | /** |
||
| 574 | * get a single address |
||
| 575 | * |
||
| 576 | * @param address string address hash |
||
| 577 | * @param [cb] function callback function to call when request is complete |
||
| 578 | * @return q.Promise |
||
| 579 | */ |
||
| 580 | APIClient.prototype.address = function(address, cb) { |
||
| 581 | var self = this; |
||
| 582 | |||
| 583 | return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null) |
||
| 584 | .then(function(data) { |
||
| 585 | return self.converter.handleErros(self, data); |
||
| 586 | }) |
||
| 587 | .then(function(data) { |
||
| 588 | if (data === null) { |
||
| 589 | return data; |
||
| 590 | } else { |
||
| 591 | return self.converter.convertAddress(data); |
||
| 592 | } |
||
| 593 | }), cb); |
||
| 594 | }; |
||
| 595 | |||
| 596 | APIClient.prototype.addresses = function(addresses, cb) { |
||
| 597 | var self = this; |
||
| 598 | |||
| 599 | return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb); |
||
| 600 | }; |
||
| 601 | |||
| 602 | |||
| 603 | /** |
||
| 604 | * get all transactions for an address (paginated) |
||
| 605 | * |
||
| 606 | * @param address string address hash |
||
| 607 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 608 | * @param [cb] function callback function to call when request is complete |
||
| 609 | * @return q.Promise |
||
| 610 | */ |
||
| 611 | APIClient.prototype.addressTransactions = function(address, params, cb) { |
||
| 612 | |||
| 613 | var self = this; |
||
| 614 | |||
| 615 | if (typeof params === "function") { |
||
| 616 | cb = params; |
||
| 617 | params = null; |
||
| 618 | } |
||
| 619 | |||
| 620 | return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params)) |
||
| 621 | .then(function(data) { |
||
| 622 | return self.converter.handleErros(self, data); |
||
| 623 | }) |
||
| 624 | .then(function(data) { |
||
| 625 | return data.data === null ? data : self.converter.convertAddressTxs(data); |
||
| 626 | }), cb); |
||
| 627 | }; |
||
| 628 | |||
| 629 | /** |
||
| 630 | * get all transactions for a batch of addresses (paginated) |
||
| 631 | * |
||
| 632 | * @param addresses array address hashes |
||
| 633 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 634 | * @param [cb] function callback function to call when request is complete |
||
| 635 | * @return q.Promise |
||
| 636 | */ |
||
| 637 | APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) { |
||
| 638 | var self = this; |
||
| 639 | |||
| 640 | if (typeof params === "function") { |
||
| 641 | cb = params; |
||
| 642 | params = null; |
||
| 643 | } |
||
| 644 | |||
| 645 | var deferred = q.defer(); |
||
| 646 | |||
| 647 | var promise = q(); |
||
| 648 | |||
| 649 | addresses.forEach(function(address) { |
||
| 650 | promise = promise.then(function(hasTxs) { |
||
| 651 | if (hasTxs) { |
||
| 652 | return hasTxs; |
||
| 653 | } |
||
| 654 | |||
| 655 | return q(address) |
||
| 656 | .then(function(address) { |
||
| 657 | console.log(address); |
||
|
0 ignored issues
–
show
|
|||
| 658 | return self.addressTransactions(address, params) |
||
| 659 | .then(function(res) { |
||
| 660 | // err_no=1 is no txs found |
||
| 661 | if (res.err_no === 1) { |
||
| 662 | return false; |
||
| 663 | } else if (res.err_no) { |
||
| 664 | throw new Error("err: " + res.err_msg); |
||
| 665 | } |
||
| 666 | |||
| 667 | return res.data && res.data.length > 0; |
||
| 668 | }); |
||
| 669 | }); |
||
| 670 | }); |
||
| 671 | }); |
||
| 672 | |||
| 673 | promise.then(function(hasTxs) { |
||
| 674 | deferred.resolve({has_transactions: hasTxs}); |
||
| 675 | }, function(err) { |
||
| 676 | deferred.reject(err); |
||
| 677 | }); |
||
| 678 | |||
| 679 | return callbackify(deferred.promise, cb); |
||
| 680 | }; |
||
| 681 | |||
| 682 | /** |
||
| 683 | * get all unconfirmed transactions for an address (paginated) |
||
| 684 | * |
||
| 685 | * @param address string address hash |
||
| 686 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 687 | * @param [cb] function callback function to call when request is complete |
||
| 688 | * @return q.Promise |
||
| 689 | */ |
||
| 690 | APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) { |
||
| 691 | var self = this; |
||
| 692 | |||
| 693 | if (typeof params === "function") { |
||
| 694 | cb = params; |
||
| 695 | params = null; |
||
| 696 | } |
||
| 697 | |||
| 698 | return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params)) |
||
| 699 | .then(function(data) { |
||
| 700 | return self.converter.handleErros(self, data); |
||
| 701 | }) |
||
| 702 | .then(function(data) { |
||
| 703 | if (data.data === null) { |
||
| 704 | return data; |
||
| 705 | } |
||
| 706 | |||
| 707 | var res = self.converter.convertAddressTxs(data); |
||
| 708 | res.data = res.data.filter(function(tx) { |
||
| 709 | return !tx.confirmations; |
||
| 710 | }); |
||
| 711 | |||
| 712 | return res; |
||
| 713 | }), cb); |
||
| 714 | }; |
||
| 715 | |||
| 716 | /** |
||
| 717 | * get all unspent outputs for an address (paginated) |
||
| 718 | * |
||
| 719 | * @param address string address hash |
||
| 720 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 721 | * @param [cb] function callback function to call when request is complete |
||
| 722 | * @return q.Promise |
||
| 723 | */ |
||
| 724 | APIClient.prototype.addressUnspentOutputs = function(address, params, cb) { |
||
| 725 | var self = this; |
||
| 726 | |||
| 727 | if (typeof params === "function") { |
||
| 728 | cb = params; |
||
| 729 | params = null; |
||
| 730 | } |
||
| 731 | |||
| 732 | return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params)) |
||
| 733 | .then(function(data) { |
||
| 734 | return self.converter.handleErros(self, data); |
||
| 735 | }) |
||
| 736 | .then(function(data) { |
||
| 737 | return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address); |
||
| 738 | }), cb); |
||
| 739 | }; |
||
| 740 | |||
| 741 | /** |
||
| 742 | * get all unspent outputs for a batch of addresses (paginated) |
||
| 743 | * |
||
| 744 | * @param addresses array address hashes |
||
| 745 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 746 | * @param [cb] function callback function to call when request is complete |
||
| 747 | * @return q.Promise |
||
| 748 | */ |
||
| 749 | APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) { |
||
| 750 | var self = this; |
||
| 751 | |||
| 752 | if (self.converter instanceof BtccomConverter) { |
||
| 753 | return callbackify(self.dataClient.get(self.converter.getUrlForBatchAddressUnspent(addresses), self.converter.paginationParams(params)) |
||
| 754 | .then(function(data) { |
||
| 755 | return self.converter.handleErros(self, data); |
||
| 756 | }) |
||
| 757 | .then(function(data) { |
||
| 758 | return data.data === null ? data : self.converter.convertBatchAddressUnspentOutputs(data); |
||
| 759 | }), cb); |
||
| 760 | } else { |
||
| 761 | |||
| 762 | if (typeof params === "function") { |
||
| 763 | cb = params; |
||
| 764 | params = null; |
||
| 765 | } |
||
| 766 | |||
| 767 | return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb); |
||
| 768 | } |
||
| 769 | }; |
||
| 770 | |||
| 771 | /** |
||
| 772 | * verify ownership of an address |
||
| 773 | * |
||
| 774 | * @param address string address hash |
||
| 775 | * @param signature string a signed message (the address hash) using the private key of the address |
||
| 776 | * @param [cb] function callback function to call when request is complete |
||
| 777 | * @return q.Promise |
||
| 778 | */ |
||
| 779 | APIClient.prototype.verifyAddress = function(address, signature, cb) { |
||
| 780 | var self = this; |
||
| 781 | |||
| 782 | return self.verifyMessage(address, address, signature, cb); |
||
| 783 | }; |
||
| 784 | |||
| 785 | /** |
||
| 786 | * |
||
| 787 | * get all blocks (paginated) |
||
| 788 | * ASK |
||
| 789 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 790 | * @param [cb] function callback function to call when request is complete |
||
| 791 | * @return q.Promise |
||
| 792 | */ |
||
| 793 | APIClient.prototype.allBlocks = function(params, cb) { |
||
| 794 | var self = this; |
||
| 795 | |||
| 796 | if (typeof params === "function") { |
||
| 797 | cb = params; |
||
| 798 | params = null; |
||
| 799 | } |
||
| 800 | |||
| 801 | return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params)) |
||
| 802 | .then(function(data) { |
||
| 803 | return self.converter.handleErros(self, data); |
||
| 804 | }) |
||
| 805 | .then(function(data) { |
||
| 806 | return data.data === null ? data : self.converter.convertBlocks(data); |
||
| 807 | }), cb); |
||
| 808 | }; |
||
| 809 | |||
| 810 | /** |
||
| 811 | * get a block |
||
| 812 | * |
||
| 813 | * @param block string|int a block hash or a block height |
||
| 814 | * @param [cb] function callback function to call when request is complete |
||
| 815 | * @return q.Promise |
||
| 816 | */ |
||
| 817 | APIClient.prototype.block = function(block, cb) { |
||
| 818 | var self = this; |
||
| 819 | |||
| 820 | return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null) |
||
| 821 | .then(function(data) { |
||
| 822 | return self.converter.handleErros(self, data); |
||
| 823 | }) |
||
| 824 | .then(function(data) { |
||
| 825 | return data.data === null ? data : self.converter.convertBlock(data.data); |
||
| 826 | }), cb); |
||
| 827 | }; |
||
| 828 | |||
| 829 | /** |
||
| 830 | * get the latest block |
||
| 831 | * |
||
| 832 | * @param [cb] function callback function to call when request is complete |
||
| 833 | * @return q.Promise |
||
| 834 | */ |
||
| 835 | APIClient.prototype.blockLatest = function(cb) { |
||
| 836 | var self = this; |
||
| 837 | |||
| 838 | return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null) |
||
| 839 | .then(function(data) { |
||
| 840 | return self.converter.handleErros(self, data); |
||
| 841 | }) |
||
| 842 | .then(function(data) { |
||
| 843 | return data.data === null ? data : self.converter.convertBlock(data.data); |
||
| 844 | }), cb); |
||
| 845 | }; |
||
| 846 | |||
| 847 | /** |
||
| 848 | * get all transactions for a block (paginated) |
||
| 849 | * |
||
| 850 | * @param block string|int a block hash or a block height |
||
| 851 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 852 | * @param [cb] function callback function to call when request is complete |
||
| 853 | * @return q.Promise |
||
| 854 | */ |
||
| 855 | APIClient.prototype.blockTransactions = function(block, params, cb) { |
||
| 856 | var self = this; |
||
| 857 | |||
| 858 | if (typeof params === "function") { |
||
| 859 | cb = params; |
||
| 860 | params = null; |
||
| 861 | } |
||
| 862 | |||
| 863 | return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params)) |
||
| 864 | .then(function(data) { |
||
| 865 | return self.converter.handleErros(self, data); |
||
| 866 | }) |
||
| 867 | .then(function(data) { |
||
| 868 | return data.data === null ? data : self.converter.convertBlockTxs(data); |
||
| 869 | }), cb); |
||
| 870 | }; |
||
| 871 | |||
| 872 | /** |
||
| 873 | * get a single transaction |
||
| 874 | * |
||
| 875 | * @param tx string transaction hash |
||
| 876 | * @param [cb] function callback function to call when request is complete |
||
| 877 | * @return q.Promise |
||
| 878 | */ |
||
| 879 | APIClient.prototype.transaction = function(tx, cb) { |
||
| 880 | var self = this; |
||
| 881 | |||
| 882 | return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null) |
||
| 883 | .then(function(data) { |
||
| 884 | return self.converter.handleErros(self, data); |
||
| 885 | }) |
||
| 886 | .then(function(data) { |
||
| 887 | if (data.data === null) { |
||
| 888 | return data; |
||
| 889 | } else { |
||
| 890 | // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint |
||
| 891 | if (self.converter instanceof BtccomConverter) { |
||
| 892 | return self.dataClient.get(self.converter.getUrlForRawTransaction(tx), null) |
||
| 893 | .then(function(rawData) { |
||
| 894 | return [data, rawData.data]; |
||
| 895 | }) |
||
| 896 | .then(function(dataAndTx) { |
||
| 897 | if (dataAndTx !== null) { |
||
| 898 | var data = dataAndTx[0]; |
||
| 899 | var rawTx = dataAndTx[1]; |
||
| 900 | return self.converter.convertTx(data, rawTx); |
||
| 901 | } else { |
||
| 902 | return dataAndTx; |
||
| 903 | } |
||
| 904 | }); |
||
| 905 | } else { |
||
| 906 | return self.converter.convertTx(data); |
||
| 907 | } |
||
| 908 | } |
||
| 909 | }), cb); |
||
| 910 | }; |
||
| 911 | |||
| 912 | /** |
||
| 913 | * get a batch of transactions |
||
| 914 | * |
||
| 915 | * @param txs string[] list of transaction hashes (txId) |
||
| 916 | * @param [cb] function callback function to call when request is complete |
||
| 917 | * @return q.Promise |
||
| 918 | */ |
||
| 919 | APIClient.prototype.transactions = function(txs, cb) { |
||
| 920 | var self = this; |
||
| 921 | |||
| 922 | if (self.converter instanceof BtccomConverter) { |
||
| 923 | return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null) |
||
| 924 | .then(function(data) { |
||
| 925 | return self.converter.handleErros(self, data); |
||
| 926 | }) |
||
| 927 | .then(function(data) { |
||
| 928 | if (data.data === null) { |
||
| 929 | return data; |
||
| 930 | } else { |
||
| 931 | return self.converter.convertTxs(data); |
||
| 932 | } |
||
| 933 | }), cb); |
||
| 934 | } else { |
||
| 935 | return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb); |
||
| 936 | } |
||
| 937 | }; |
||
| 938 | |||
| 939 | /** |
||
| 940 | * get a paginated list of all webhooks associated with the api user |
||
| 941 | * |
||
| 942 | * @param [params] object pagination: {page: 1, limit: 20} |
||
| 943 | * @param [cb] function callback function to call when request is complete |
||
| 944 | * @return q.Promise |
||
| 945 | */ |
||
| 946 | APIClient.prototype.allWebhooks = function(params, cb) { |
||
| 947 | var self = this; |
||
| 948 | |||
| 949 | if (typeof params === "function") { |
||
| 950 | cb = params; |
||
| 951 | params = null; |
||
| 952 | } |
||
| 953 | |||
| 954 | return self.blocktrailClient.get("/webhooks", params, cb); |
||
| 955 | }; |
||
| 956 | |||
| 957 | /** |
||
| 958 | * create a new webhook |
||
| 959 | * |
||
| 960 | * @param url string the url to receive the webhook events |
||
| 961 | * @param [identifier] string a unique identifier associated with the webhook |
||
| 962 | * @param [cb] function callback function to call when request is complete |
||
| 963 | * @return q.Promise |
||
| 964 | */ |
||
| 965 | APIClient.prototype.setupWebhook = function(url, identifier, cb) { |
||
| 966 | var self = this; |
||
| 967 | |||
| 968 | if (typeof identifier === "function") { |
||
| 969 | //mimic function overloading |
||
| 970 | cb = identifier; |
||
| 971 | identifier = null; |
||
| 972 | } |
||
| 973 | |||
| 974 | return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb); |
||
| 975 | }; |
||
| 976 | |||
| 977 | /** |
||
| 978 | * Converts a cash address to the legacy (base58) format |
||
| 979 | * @param {string} input |
||
| 980 | * @returns {string} |
||
| 981 | */ |
||
| 982 | APIClient.prototype.getLegacyBitcoinCashAddress = function(input) { |
||
| 983 | if (this.network === bitcoin.networks.bitcoincash || |
||
| 984 | this.network === bitcoin.networks.bitcoincashtestnet || |
||
| 985 | this.network === bitcoin.networks.bitcoincashregtest) { |
||
| 986 | var address; |
||
| 987 | try { |
||
| 988 | bitcoin.address.fromBase58Check(input, this.network); |
||
| 989 | return input; |
||
| 990 | } catch (e) {} |
||
|
0 ignored issues
–
show
Coding Style
Comprehensibility
Best Practice
introduced
by
|
|||
| 991 | |||
| 992 | address = bitcoin.address.fromCashAddress(input, this.network); |
||
| 993 | var prefix; |
||
| 994 | if (address.version === bitcoin.script.types.P2PKH) { |
||
| 995 | prefix = this.network.pubKeyHash; |
||
| 996 | } else if (address.version === bitcoin.script.types.P2SH) { |
||
| 997 | prefix = this.network.scriptHash; |
||
| 998 | } else { |
||
| 999 | throw new Error("Unsupported address type"); |
||
| 1000 | } |
||
| 1001 | |||
| 1002 | return bitcoin.address.toBase58Check(address.hash, prefix); |
||
| 1003 | } |
||
| 1004 | |||
| 1005 | throw new Error("Cash addresses only work on bitcoin cash"); |
||
| 1006 | }; |
||
| 1007 | |||
| 1008 | /** |
||
| 1009 | * Converts a legacy bitcoin to the new cashaddr format |
||
| 1010 | * @param {string} input |
||
| 1011 | * @returns {string} |
||
| 1012 | */ |
||
| 1013 | APIClient.prototype.getCashAddressFromLegacyAddress = function(input) { |
||
| 1014 | if (this.network === bitcoin.networks.bitcoincash || |
||
| 1015 | this.network === bitcoin.networks.bitcoincashtestnet || |
||
| 1016 | this.network === bitcoin.networks.bitcoincashregtest |
||
| 1017 | ) { |
||
| 1018 | var address; |
||
| 1019 | try { |
||
| 1020 | bitcoin.address.fromCashAddress(input, this.network); |
||
| 1021 | return input; |
||
| 1022 | } catch (e) {} |
||
|
0 ignored issues
–
show
Coding Style
Comprehensibility
Best Practice
introduced
by
|
|||
| 1023 | |||
| 1024 | address = bitcoin.address.fromBase58Check(input, this.network); |
||
| 1025 | var scriptType; |
||
| 1026 | if (address.version === this.network.pubKeyHash) { |
||
| 1027 | scriptType = bitcoin.script.types.P2PKH; |
||
| 1028 | } else if (address.version === this.network.scriptHash) { |
||
| 1029 | scriptType = bitcoin.script.types.P2SH; |
||
| 1030 | } else { |
||
| 1031 | throw new Error("Unsupported address type"); |
||
| 1032 | } |
||
| 1033 | |||
| 1034 | return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix); |
||
| 1035 | } |
||
| 1036 | |||
| 1037 | throw new Error("Cash addresses only work on bitcoin cash"); |
||
| 1038 | }; |
||
| 1039 | |||
| 1040 | /** |
||
| 1041 | * get an existing webhook by it's identifier |
||
| 1042 | * |
||
| 1043 | * @param identifier string the unique identifier of the webhook to get |
||
| 1044 | * @param [cb] function callback function to call when request is complete |
||
| 1045 | * @return q.Promise |
||
| 1046 | */ |
||
| 1047 | APIClient.prototype.getWebhook = function(identifier, cb) { |
||
| 1048 | var self = this; |
||
| 1049 | |||
| 1050 | return self.blocktrailClient.get("/webhook/" + identifier, null, cb); |
||
| 1051 | }; |
||
| 1052 | |||
| 1053 | /** |
||
| 1054 | * update an existing webhook |
||
| 1055 | * |
||
| 1056 | * @param identifier string the unique identifier of the webhook |
||
| 1057 | * @param webhookData object the data to update: {identifier: newIdentifier, url:newUrl} |
||
| 1058 | * @param [cb] function callback function to call when request is complete |
||
| 1059 | * @return q.Promise |
||
| 1060 | */ |
||
| 1061 | APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) { |
||
| 1062 | var self = this; |
||
| 1063 | |||
| 1064 | return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb); |
||
| 1065 | }; |
||
| 1066 | |||
| 1067 | /** |
||
| 1068 | * deletes an existing webhook and any event subscriptions associated with it |
||
| 1069 | * |
||
| 1070 | * @param identifier string the unique identifier of the webhook |
||
| 1071 | * @param [cb] function callback function to call when request is complete |
||
| 1072 | * @return q.Promise |
||
| 1073 | */ |
||
| 1074 | APIClient.prototype.deleteWebhook = function(identifier, cb) { |
||
| 1075 | var self = this; |
||
| 1076 | |||
| 1077 | return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb); |
||
| 1078 | }; |
||
| 1079 | |||
| 1080 | /** |
||
| 1081 | * get a paginated list of all the events a webhook is subscribed to |
||
| 1082 | * |
||
| 1083 | * @param identifier string the unique identifier of the webhook |
||
| 1084 | * @param [params] object pagination: {page: 1, limit: 20} |
||
| 1085 | * @param [cb] function callback function to call when request is complete |
||
| 1086 | * @return q.Promise |
||
| 1087 | */ |
||
| 1088 | APIClient.prototype.getWebhookEvents = function(identifier, params, cb) { |
||
| 1089 | var self = this; |
||
| 1090 | |||
| 1091 | if (typeof params === "function") { |
||
| 1092 | cb = params; |
||
| 1093 | params = null; |
||
| 1094 | } |
||
| 1095 | |||
| 1096 | return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb); |
||
| 1097 | }; |
||
| 1098 | |||
| 1099 | /** |
||
| 1100 | * subscribes a webhook to transaction events for a particular transaction |
||
| 1101 | * |
||
| 1102 | * @param identifier string the unique identifier of the webhook |
||
| 1103 | * @param transaction string the transaction hash |
||
| 1104 | * @param confirmations integer the amount of confirmations to send |
||
| 1105 | * @param [cb] function callback function to call when request is complete |
||
| 1106 | * @return q.Promise |
||
| 1107 | */ |
||
| 1108 | APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) { |
||
| 1109 | var self = this; |
||
| 1110 | var postData = { |
||
| 1111 | 'event_type': 'transaction', |
||
| 1112 | 'transaction': transaction, |
||
| 1113 | 'confirmations': confirmations |
||
| 1114 | }; |
||
| 1115 | |||
| 1116 | return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb); |
||
| 1117 | }; |
||
| 1118 | |||
| 1119 | /** |
||
| 1120 | * subscribes a webhook to transaction events on a particular address |
||
| 1121 | * |
||
| 1122 | * @param identifier string the unique identifier of the webhook |
||
| 1123 | * @param address string the address hash |
||
| 1124 | * @param confirmations integer the amount of confirmations to send |
||
| 1125 | * @param [cb] function callback function to call when request is complete |
||
| 1126 | * @return q.Promise |
||
| 1127 | */ |
||
| 1128 | APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) { |
||
| 1129 | var self = this; |
||
| 1130 | var postData = { |
||
| 1131 | 'event_type': 'address-transactions', |
||
| 1132 | 'address': address, |
||
| 1133 | 'confirmations': confirmations |
||
| 1134 | }; |
||
| 1135 | |||
| 1136 | return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb); |
||
| 1137 | }; |
||
| 1138 | |||
| 1139 | /** |
||
| 1140 | * batch subscribes a webhook to multiple transaction events |
||
| 1141 | * |
||
| 1142 | * @param identifier string the unique identifier of the webhook |
||
| 1143 | * @param batchData array An array of objects containing batch event data: |
||
| 1144 | * {address : 'address', confirmations : 'confirmations'] |
||
| 1145 | * where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send |
||
| 1146 | * @param [cb] function callback function to call when request is complete |
||
| 1147 | * @return q.Promise |
||
| 1148 | */ |
||
| 1149 | APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) { |
||
| 1150 | var self = this; |
||
| 1151 | batchData.forEach(function(record) { |
||
| 1152 | record.event_type = 'address-transactions'; |
||
| 1153 | }); |
||
| 1154 | |||
| 1155 | return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb); |
||
| 1156 | }; |
||
| 1157 | |||
| 1158 | /** |
||
| 1159 | * subscribes a webhook to a new block event |
||
| 1160 | * |
||
| 1161 | * @param identifier string the unique identifier of the webhook |
||
| 1162 | * @param [cb] function callback function to call when request is complete |
||
| 1163 | * @return q.Promise |
||
| 1164 | */ |
||
| 1165 | APIClient.prototype.subscribeNewBlocks = function(identifier, cb) { |
||
| 1166 | var self = this; |
||
| 1167 | var postData = { |
||
| 1168 | 'event_type': 'block' |
||
| 1169 | }; |
||
| 1170 | |||
| 1171 | return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb); |
||
| 1172 | }; |
||
| 1173 | |||
| 1174 | /** |
||
| 1175 | * removes an address transaction event subscription from a webhook |
||
| 1176 | * |
||
| 1177 | * @param identifier string the unique identifier of the webhook |
||
| 1178 | * @param address string the address hash |
||
| 1179 | * @param [cb] function callback function to call when request is complete |
||
| 1180 | * @return q.Promise |
||
| 1181 | */ |
||
| 1182 | APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) { |
||
| 1183 | var self = this; |
||
| 1184 | |||
| 1185 | return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb); |
||
| 1186 | }; |
||
| 1187 | |||
| 1188 | /** |
||
| 1189 | * removes an transaction event subscription from a webhook |
||
| 1190 | * |
||
| 1191 | * @param identifier string the unique identifier of the webhook |
||
| 1192 | * @param transaction string the transaction hash |
||
| 1193 | * @param [cb] function callback function to call when request is complete |
||
| 1194 | * @return q.Promise |
||
| 1195 | */ |
||
| 1196 | APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) { |
||
| 1197 | var self = this; |
||
| 1198 | |||
| 1199 | return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb); |
||
| 1200 | }; |
||
| 1201 | |||
| 1202 | /** |
||
| 1203 | * removes a block event subscription from a webhook |
||
| 1204 | * |
||
| 1205 | * @param identifier string the unique identifier of the webhook |
||
| 1206 | * @param [cb] function callback function to call when request is complete |
||
| 1207 | * @return q.Promise |
||
| 1208 | */ |
||
| 1209 | APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) { |
||
| 1210 | var self = this; |
||
| 1211 | |||
| 1212 | return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb); |
||
| 1213 | }; |
||
| 1214 | |||
| 1215 | /** |
||
| 1216 | * initialize an existing wallet |
||
| 1217 | * |
||
| 1218 | * Either takes two argument: |
||
| 1219 | * @param options object {} |
||
| 1220 | * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) |
||
| 1221 | * |
||
| 1222 | * Or takes three arguments (old, deprecated syntax): |
||
| 1223 | * @param identifier string the wallet identifier to be initialized |
||
|
0 ignored issues
–
show
|
|||
| 1224 | * @param passphrase string the password to decrypt the mnemonic with |
||
|
0 ignored issues
–
show
|
|||
| 1225 | * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) |
||
|
0 ignored issues
–
show
|
|||
| 1226 | * |
||
| 1227 | * @returns {q.Promise} |
||
| 1228 | */ |
||
| 1229 | APIClient.prototype.initWallet = function(options, cb) { |
||
| 1230 | var self = this; |
||
| 1231 | |||
| 1232 | if (typeof options !== "object") { |
||
| 1233 | // get the old-style arguments |
||
| 1234 | options = { |
||
| 1235 | identifier: arguments[0], |
||
| 1236 | passphrase: arguments[1] |
||
| 1237 | }; |
||
| 1238 | |||
| 1239 | cb = arguments[2]; |
||
| 1240 | } |
||
| 1241 | |||
| 1242 | if (options.check_backup_key) { |
||
| 1243 | if (typeof options.check_backup_key !== "string") { |
||
| 1244 | throw new Error("Invalid input, must provide the backup key as a string (the xpub)"); |
||
| 1245 | } |
||
| 1246 | } |
||
| 1247 | |||
| 1248 | var deferred = q.defer(); |
||
| 1249 | deferred.promise.spreadNodeify(cb); |
||
| 1250 | |||
| 1251 | var identifier = options.identifier; |
||
| 1252 | |||
| 1253 | if (!identifier) { |
||
| 1254 | deferred.reject(new blocktrail.WalletInitError("Identifier is required")); |
||
| 1255 | return deferred.promise; |
||
| 1256 | } |
||
| 1257 | |||
| 1258 | deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) { |
||
| 1259 | var keyIndex = options.keyIndex || result.key_index; |
||
| 1260 | |||
| 1261 | options.walletVersion = result.wallet_version; |
||
| 1262 | |||
| 1263 | if (options.check_backup_key) { |
||
| 1264 | if (options.check_backup_key !== result.backup_public_key[0]) { |
||
| 1265 | throw new Error("Backup key returned from server didn't match our own copy"); |
||
| 1266 | } |
||
| 1267 | } |
||
| 1268 | var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network); |
||
| 1269 | var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) { |
||
| 1270 | return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network); |
||
| 1271 | }); |
||
| 1272 | var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) { |
||
| 1273 | return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network); |
||
| 1274 | }); |
||
| 1275 | |||
| 1276 | // initialize wallet |
||
| 1277 | var wallet = new Wallet( |
||
| 1278 | self, |
||
| 1279 | identifier, |
||
| 1280 | options.walletVersion, |
||
| 1281 | result.primary_mnemonic, |
||
| 1282 | result.encrypted_primary_seed, |
||
| 1283 | result.encrypted_secret, |
||
| 1284 | primaryPublicKeys, |
||
| 1285 | backupPublicKey, |
||
| 1286 | blocktrailPublicKeys, |
||
| 1287 | keyIndex, |
||
| 1288 | result.segwit || 0, |
||
| 1289 | self.testnet, |
||
| 1290 | self.regtest, |
||
| 1291 | result.checksum, |
||
| 1292 | result.upgrade_key_index, |
||
| 1293 | options.useCashAddress, |
||
| 1294 | options.bypassNewAddressCheck |
||
| 1295 | ); |
||
| 1296 | |||
| 1297 | wallet.recoverySecret = result.recovery_secret; |
||
| 1298 | |||
| 1299 | if (!options.readOnly) { |
||
| 1300 | return wallet.unlock(options).then(function() { |
||
| 1301 | return wallet; |
||
| 1302 | }); |
||
| 1303 | } else { |
||
| 1304 | return wallet; |
||
| 1305 | } |
||
| 1306 | })); |
||
| 1307 | |||
| 1308 | return deferred.promise; |
||
| 1309 | }; |
||
| 1310 | |||
| 1311 | APIClient.CREATE_WALLET_PROGRESS_START = 0; |
||
| 1312 | APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4; |
||
| 1313 | APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5; |
||
| 1314 | APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6; |
||
| 1315 | APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10; |
||
| 1316 | APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20; |
||
| 1317 | APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30; |
||
| 1318 | APIClient.CREATE_WALLET_PROGRESS_INIT = 40; |
||
| 1319 | APIClient.CREATE_WALLET_PROGRESS_DONE = 100; |
||
| 1320 | |||
| 1321 | /** |
||
| 1322 | * create a new wallet |
||
| 1323 | * - will generate a new primary seed and backup seed |
||
| 1324 | * |
||
| 1325 | * Either takes two argument: |
||
| 1326 | * @param options object {} |
||
| 1327 | * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) |
||
| 1328 | * |
||
| 1329 | * For v1 wallets (explicitly specify options.walletVersion=v1): |
||
| 1330 | * @param options object {} |
||
|
0 ignored issues
–
show
|
|||
| 1331 | * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) |
||
|
0 ignored issues
–
show
|
|||
| 1332 | * |
||
| 1333 | * Or takes four arguments (old, deprecated syntax): |
||
| 1334 | * @param identifier string the wallet identifier to be initialized |
||
|
0 ignored issues
–
show
|
|||
| 1335 | * @param passphrase string the password to decrypt the mnemonic with |
||
|
0 ignored issues
–
show
|
|||
| 1336 | * @param keyIndex int override for the blocktrail cosign key to use (for development purposes) |
||
|
0 ignored issues
–
show
|
|||
| 1337 | * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) |
||
|
0 ignored issues
–
show
|
|||
| 1338 | * @returns {q.Promise} |
||
| 1339 | */ |
||
| 1340 | APIClient.prototype.createNewWallet = function(options, cb) { |
||
| 1341 | /* jshint -W071, -W074 */ |
||
| 1342 | |||
| 1343 | var self = this; |
||
| 1344 | |||
| 1345 | if (typeof options !== "object") { |
||
| 1346 | // get the old-style arguments |
||
| 1347 | var identifier = arguments[0]; |
||
| 1348 | var passphrase = arguments[1]; |
||
| 1349 | var keyIndex = arguments[2]; |
||
| 1350 | cb = arguments[3]; |
||
| 1351 | |||
| 1352 | // keyIndex is optional |
||
| 1353 | if (typeof keyIndex === "function") { |
||
| 1354 | cb = keyIndex; |
||
| 1355 | keyIndex = null; |
||
| 1356 | } |
||
| 1357 | |||
| 1358 | options = { |
||
| 1359 | identifier: identifier, |
||
| 1360 | passphrase: passphrase, |
||
| 1361 | keyIndex: keyIndex |
||
| 1362 | }; |
||
| 1363 | } |
||
| 1364 | |||
| 1365 | // default to v3 |
||
| 1366 | options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3; |
||
| 1367 | |||
| 1368 | var deferred = q.defer(); |
||
| 1369 | deferred.promise.spreadNodeify(cb); |
||
| 1370 | |||
| 1371 | q.nextTick(function() { |
||
| 1372 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START); |
||
| 1373 | |||
| 1374 | options.keyIndex = options.keyIndex || 0; |
||
| 1375 | options.passphrase = options.passphrase || options.password; |
||
| 1376 | delete options.password; |
||
| 1377 | |||
| 1378 | if (!options.identifier) { |
||
| 1379 | deferred.reject(new blocktrail.WalletCreateError("Identifier is required")); |
||
| 1380 | return deferred.promise; |
||
| 1381 | } |
||
| 1382 | |||
| 1383 | if (options.walletVersion === Wallet.WALLET_VERSION_V1) { |
||
| 1384 | self._createNewWalletV1(options) |
||
| 1385 | .progress(function(p) { deferred.notify(p); }) |
||
| 1386 | .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); }) |
||
| 1387 | ; |
||
|
0 ignored issues
–
show
|
|||
| 1388 | } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) { |
||
| 1389 | self._createNewWalletV2(options) |
||
| 1390 | .progress(function(p) { deferred.notify(p); }) |
||
| 1391 | .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); }) |
||
| 1392 | ; |
||
|
0 ignored issues
–
show
|
|||
| 1393 | } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) { |
||
| 1394 | self._createNewWalletV3(options) |
||
| 1395 | .progress(function(p) { deferred.notify(p); }) |
||
| 1396 | .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); }) |
||
| 1397 | ; |
||
|
0 ignored issues
–
show
|
|||
| 1398 | } else { |
||
| 1399 | deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!")); |
||
|
0 ignored issues
–
show
|
|||
| 1400 | } |
||
| 1401 | }); |
||
| 1402 | |||
| 1403 | return deferred.promise; |
||
| 1404 | }; |
||
| 1405 | |||
| 1406 | APIClient.prototype._createNewWalletV1 = function(options) { |
||
| 1407 | var self = this; |
||
| 1408 | |||
| 1409 | var deferred = q.defer(); |
||
| 1410 | |||
| 1411 | q.nextTick(function() { |
||
| 1412 | |||
| 1413 | if (!options.primaryMnemonic && !options.primarySeed) { |
||
| 1414 | if (!options.passphrase && !options.password) { |
||
| 1415 | deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase")); |
||
| 1416 | return deferred.promise; |
||
| 1417 | } else { |
||
| 1418 | options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS); |
||
| 1419 | if (options.storePrimaryMnemonic !== false) { |
||
| 1420 | options.storePrimaryMnemonic = true; |
||
| 1421 | } |
||
| 1422 | } |
||
| 1423 | } |
||
| 1424 | |||
| 1425 | if (!options.backupMnemonic && !options.backupPublicKey) { |
||
| 1426 | options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS); |
||
| 1427 | } |
||
| 1428 | |||
| 1429 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY); |
||
| 1430 | |||
| 1431 | self.resolvePrimaryPrivateKeyFromOptions(options) |
||
| 1432 | .then(function(options) { |
||
| 1433 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP); |
||
| 1434 | |||
| 1435 | return self.resolveBackupPublicKeyFromOptions(options) |
||
| 1436 | .then(function(options) { |
||
| 1437 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT); |
||
| 1438 | |||
| 1439 | // create a checksum of our private key which we'll later use to verify we used the right password |
||
| 1440 | var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer()); |
||
| 1441 | var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash); |
||
| 1442 | var keyIndex = options.keyIndex; |
||
| 1443 | |||
| 1444 | var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered(); |
||
| 1445 | |||
| 1446 | // send the public keys to the server to store them |
||
| 1447 | // and the mnemonic, which is safe because it's useless without the password |
||
| 1448 | return self.storeNewWalletV1( |
||
| 1449 | options.identifier, |
||
| 1450 | [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"], |
||
| 1451 | [options.backupPublicKey.toBase58(), "M"], |
||
| 1452 | options.storePrimaryMnemonic ? options.primaryMnemonic : false, |
||
| 1453 | checksum, |
||
| 1454 | keyIndex, |
||
| 1455 | options.segwit || null |
||
| 1456 | ) |
||
| 1457 | .then(function(result) { |
||
| 1458 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT); |
||
| 1459 | |||
| 1460 | var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) { |
||
| 1461 | return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network); |
||
| 1462 | }); |
||
| 1463 | |||
| 1464 | var wallet = new Wallet( |
||
| 1465 | self, |
||
| 1466 | options.identifier, |
||
| 1467 | Wallet.WALLET_VERSION_V1, |
||
| 1468 | options.primaryMnemonic, |
||
| 1469 | null, |
||
| 1470 | null, |
||
| 1471 | {keyIndex: primaryPublicKey}, |
||
| 1472 | options.backupPublicKey, |
||
| 1473 | blocktrailPublicKeys, |
||
| 1474 | keyIndex, |
||
| 1475 | result.segwit || 0, |
||
| 1476 | self.testnet, |
||
| 1477 | self.regtest, |
||
| 1478 | checksum, |
||
| 1479 | result.upgrade_key_index, |
||
| 1480 | options.useCashAddress, |
||
| 1481 | options.bypassNewAddressCheck |
||
| 1482 | ); |
||
| 1483 | |||
| 1484 | return wallet.unlock({ |
||
| 1485 | walletVersion: Wallet.WALLET_VERSION_V1, |
||
| 1486 | passphrase: options.passphrase, |
||
| 1487 | primarySeed: options.primarySeed, |
||
| 1488 | primaryMnemonic: null // explicit null |
||
| 1489 | }).then(function() { |
||
| 1490 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE); |
||
| 1491 | return [ |
||
| 1492 | wallet, |
||
| 1493 | { |
||
| 1494 | walletVersion: wallet.walletVersion, |
||
| 1495 | primaryMnemonic: options.primaryMnemonic, |
||
| 1496 | backupMnemonic: options.backupMnemonic, |
||
| 1497 | blocktrailPublicKeys: blocktrailPublicKeys |
||
| 1498 | } |
||
| 1499 | ]; |
||
| 1500 | }); |
||
| 1501 | }); |
||
| 1502 | } |
||
| 1503 | ); |
||
| 1504 | }) |
||
| 1505 | .then( |
||
| 1506 | function(r) { |
||
| 1507 | deferred.resolve(r); |
||
| 1508 | }, |
||
| 1509 | function(e) { |
||
| 1510 | deferred.reject(e); |
||
| 1511 | } |
||
| 1512 | ) |
||
| 1513 | ; |
||
|
0 ignored issues
–
show
|
|||
| 1514 | }); |
||
| 1515 | |||
| 1516 | return deferred.promise; |
||
| 1517 | }; |
||
| 1518 | |||
| 1519 | View Code Duplication | APIClient.prototype._createNewWalletV2 = function(options) { |
|
|
0 ignored issues
–
show
|
|||
| 1520 | var self = this; |
||
| 1521 | |||
| 1522 | var deferred = q.defer(); |
||
| 1523 | |||
| 1524 | // avoid modifying passed options |
||
| 1525 | options = _.merge({}, options); |
||
| 1526 | |||
| 1527 | determineDataStorageV2_3(options) |
||
| 1528 | .then(function(options) { |
||
| 1529 | options.passphrase = options.passphrase || options.password; |
||
| 1530 | delete options.password; |
||
| 1531 | |||
| 1532 | // avoid deprecated options |
||
| 1533 | if (options.primaryPrivateKey) { |
||
| 1534 | throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey"); |
||
| 1535 | } |
||
| 1536 | |||
| 1537 | // seed should be provided or generated |
||
| 1538 | options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8); |
||
| 1539 | |||
| 1540 | return options; |
||
| 1541 | }) |
||
| 1542 | .then(function(options) { |
||
| 1543 | return produceEncryptedDataV2(options, deferred.notify.bind(deferred)); |
||
| 1544 | }) |
||
| 1545 | .then(function(options) { |
||
| 1546 | return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred)); |
||
| 1547 | }) |
||
| 1548 | .then(function(options) { |
||
| 1549 | // create a checksum of our private key which we'll later use to verify we used the right password |
||
| 1550 | var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer()); |
||
| 1551 | var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash); |
||
| 1552 | var keyIndex = options.keyIndex; |
||
| 1553 | |||
| 1554 | // send the public keys and encrypted data to server |
||
| 1555 | return self.storeNewWalletV2( |
||
| 1556 | options.identifier, |
||
| 1557 | [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"], |
||
| 1558 | [options.backupPublicKey.toBase58(), "M"], |
||
| 1559 | options.storeDataOnServer ? options.encryptedPrimarySeed : false, |
||
| 1560 | options.storeDataOnServer ? options.encryptedSecret : false, |
||
| 1561 | options.storeDataOnServer ? options.recoverySecret : false, |
||
| 1562 | checksum, |
||
| 1563 | keyIndex, |
||
| 1564 | options.support_secret || null, |
||
| 1565 | options.segwit || null |
||
| 1566 | ) |
||
| 1567 | .then( |
||
| 1568 | function(result) { |
||
| 1569 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT); |
||
| 1570 | |||
| 1571 | var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) { |
||
| 1572 | return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network); |
||
| 1573 | }); |
||
| 1574 | |||
| 1575 | var wallet = new Wallet( |
||
| 1576 | self, |
||
| 1577 | options.identifier, |
||
| 1578 | Wallet.WALLET_VERSION_V2, |
||
| 1579 | null, |
||
| 1580 | options.storeDataOnServer ? options.encryptedPrimarySeed : null, |
||
| 1581 | options.storeDataOnServer ? options.encryptedSecret : null, |
||
| 1582 | {keyIndex: options.primaryPublicKey}, |
||
| 1583 | options.backupPublicKey, |
||
| 1584 | blocktrailPublicKeys, |
||
| 1585 | keyIndex, |
||
| 1586 | result.segwit || 0, |
||
| 1587 | self.testnet, |
||
| 1588 | self.regtest, |
||
| 1589 | checksum, |
||
| 1590 | result.upgrade_key_index, |
||
| 1591 | options.useCashAddress, |
||
| 1592 | options.bypassNewAddressCheck |
||
| 1593 | ); |
||
| 1594 | |||
| 1595 | // pass along decrypted data to avoid extra work |
||
| 1596 | return wallet.unlock({ |
||
| 1597 | walletVersion: Wallet.WALLET_VERSION_V2, |
||
| 1598 | passphrase: options.passphrase, |
||
| 1599 | primarySeed: options.primarySeed, |
||
| 1600 | secret: options.secret |
||
| 1601 | }).then(function() { |
||
| 1602 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE); |
||
| 1603 | return [ |
||
| 1604 | wallet, |
||
| 1605 | { |
||
| 1606 | walletVersion: wallet.walletVersion, |
||
| 1607 | encryptedPrimarySeed: options.encryptedPrimarySeed ? |
||
| 1608 | bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) : |
||
| 1609 | null, |
||
| 1610 | backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null, |
||
| 1611 | recoveryEncryptedSecret: options.recoveryEncryptedSecret ? |
||
| 1612 | bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) : |
||
| 1613 | null, |
||
| 1614 | encryptedSecret: options.encryptedSecret ? |
||
| 1615 | bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) : |
||
| 1616 | null, |
||
| 1617 | blocktrailPublicKeys: blocktrailPublicKeys |
||
| 1618 | } |
||
| 1619 | ]; |
||
| 1620 | }); |
||
| 1621 | } |
||
| 1622 | ); |
||
| 1623 | }) |
||
| 1624 | .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); }); |
||
| 1625 | |||
| 1626 | return deferred.promise; |
||
| 1627 | }; |
||
| 1628 | |||
| 1629 | View Code Duplication | APIClient.prototype._createNewWalletV3 = function(options) { |
|
|
0 ignored issues
–
show
|
|||
| 1630 | var self = this; |
||
| 1631 | |||
| 1632 | var deferred = q.defer(); |
||
| 1633 | |||
| 1634 | // avoid modifying passed options |
||
| 1635 | options = _.merge({}, options); |
||
| 1636 | |||
| 1637 | determineDataStorageV2_3(options) |
||
| 1638 | .then(function(options) { |
||
| 1639 | options.passphrase = options.passphrase || options.password; |
||
| 1640 | delete options.password; |
||
| 1641 | |||
| 1642 | // avoid deprecated options |
||
| 1643 | if (options.primaryPrivateKey) { |
||
| 1644 | throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey"); |
||
| 1645 | } |
||
| 1646 | |||
| 1647 | // seed should be provided or generated |
||
| 1648 | options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8); |
||
| 1649 | |||
| 1650 | return options; |
||
| 1651 | }) |
||
| 1652 | .then(function(options) { |
||
| 1653 | return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred)); |
||
| 1654 | }) |
||
| 1655 | .then(function(options) { |
||
| 1656 | return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred)); |
||
| 1657 | }) |
||
| 1658 | .then(function(options) { |
||
| 1659 | // create a checksum of our private key which we'll later use to verify we used the right password |
||
| 1660 | var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer()); |
||
| 1661 | var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash); |
||
| 1662 | var keyIndex = options.keyIndex; |
||
| 1663 | |||
| 1664 | // send the public keys and encrypted data to server |
||
| 1665 | return self.storeNewWalletV3( |
||
| 1666 | options.identifier, |
||
| 1667 | [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"], |
||
| 1668 | [options.backupPublicKey.toBase58(), "M"], |
||
| 1669 | options.storeDataOnServer ? options.encryptedPrimarySeed : false, |
||
| 1670 | options.storeDataOnServer ? options.encryptedSecret : false, |
||
| 1671 | options.storeDataOnServer ? options.recoverySecret : false, |
||
| 1672 | checksum, |
||
| 1673 | keyIndex, |
||
| 1674 | options.support_secret || null, |
||
| 1675 | options.segwit || null |
||
| 1676 | ) |
||
| 1677 | .then( |
||
| 1678 | // result, deferred, self(apiclient) |
||
| 1679 | function(result) { |
||
| 1680 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT); |
||
| 1681 | |||
| 1682 | var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) { |
||
| 1683 | return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network); |
||
| 1684 | }); |
||
| 1685 | |||
| 1686 | var wallet = new Wallet( |
||
| 1687 | self, |
||
| 1688 | options.identifier, |
||
| 1689 | Wallet.WALLET_VERSION_V3, |
||
| 1690 | null, |
||
| 1691 | options.storeDataOnServer ? options.encryptedPrimarySeed : null, |
||
| 1692 | options.storeDataOnServer ? options.encryptedSecret : null, |
||
| 1693 | {keyIndex: options.primaryPublicKey}, |
||
| 1694 | options.backupPublicKey, |
||
| 1695 | blocktrailPublicKeys, |
||
| 1696 | keyIndex, |
||
| 1697 | result.segwit || 0, |
||
| 1698 | self.testnet, |
||
| 1699 | self.regtest, |
||
| 1700 | checksum, |
||
| 1701 | result.upgrade_key_index, |
||
| 1702 | options.useCashAddress, |
||
| 1703 | options.bypassNewAddressCheck |
||
| 1704 | ); |
||
| 1705 | |||
| 1706 | // pass along decrypted data to avoid extra work |
||
| 1707 | return wallet.unlock({ |
||
| 1708 | walletVersion: Wallet.WALLET_VERSION_V3, |
||
| 1709 | passphrase: options.passphrase, |
||
| 1710 | primarySeed: options.primarySeed, |
||
| 1711 | secret: options.secret |
||
| 1712 | }).then(function() { |
||
| 1713 | deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE); |
||
| 1714 | return [ |
||
| 1715 | wallet, |
||
| 1716 | { |
||
| 1717 | walletVersion: wallet.walletVersion, |
||
| 1718 | encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null, |
||
| 1719 | backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null, |
||
| 1720 | recoveryEncryptedSecret: options.recoveryEncryptedSecret ? |
||
| 1721 | EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null, |
||
| 1722 | encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null, |
||
| 1723 | blocktrailPublicKeys: blocktrailPublicKeys |
||
| 1724 | } |
||
| 1725 | ]; |
||
| 1726 | }); |
||
| 1727 | } |
||
| 1728 | ); |
||
| 1729 | }) |
||
| 1730 | .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); }); |
||
| 1731 | |||
| 1732 | return deferred.promise; |
||
| 1733 | }; |
||
| 1734 | |||
| 1735 | function verifyPublicBip32Key(bip32Key, network) { |
||
| 1736 | var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network); |
||
| 1737 | if (typeof hk.keyPair.d !== "undefined") { |
||
| 1738 | throw new Error('BIP32Key contained private key material - abort'); |
||
| 1739 | } |
||
| 1740 | |||
| 1741 | if (bip32Key[1].slice(0, 1) !== "M") { |
||
| 1742 | throw new Error("BIP32Key contained non-public path - abort"); |
||
| 1743 | } |
||
| 1744 | } |
||
| 1745 | |||
| 1746 | function verifyPublicOnly(walletData, network) { |
||
| 1747 | verifyPublicBip32Key(walletData.primary_public_key, network); |
||
| 1748 | verifyPublicBip32Key(walletData.backup_public_key, network); |
||
| 1749 | } |
||
| 1750 | |||
| 1751 | /** |
||
| 1752 | * create wallet using the API |
||
| 1753 | * |
||
| 1754 | * @param identifier string the wallet identifier to create |
||
| 1755 | * @param primaryPublicKey array the primary public key - [key, path] should be M/<keyIndex>' |
||
| 1756 | * @param backupPublicKey array the backup public key - [key, path] should be M/<keyIndex>' |
||
| 1757 | * @param primaryMnemonic string mnemonic to store |
||
| 1758 | * @param checksum string checksum to store |
||
| 1759 | * @param keyIndex int keyIndex that was used to create wallet |
||
| 1760 | * @param segwit bool |
||
| 1761 | * @returns {q.Promise} |
||
| 1762 | */ |
||
| 1763 | APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic, |
||
| 1764 | checksum, keyIndex, segwit) { |
||
| 1765 | var self = this; |
||
| 1766 | |||
| 1767 | var postData = { |
||
| 1768 | identifier: identifier, |
||
| 1769 | wallet_version: Wallet.WALLET_VERSION_V1, |
||
| 1770 | primary_public_key: primaryPublicKey, |
||
| 1771 | backup_public_key: backupPublicKey, |
||
| 1772 | primary_mnemonic: primaryMnemonic, |
||
| 1773 | checksum: checksum, |
||
| 1774 | key_index: keyIndex, |
||
| 1775 | segwit: segwit |
||
| 1776 | }; |
||
| 1777 | |||
| 1778 | verifyPublicOnly(postData, self.network); |
||
| 1779 | |||
| 1780 | return self.blocktrailClient.post("/wallet", null, postData); |
||
| 1781 | }; |
||
| 1782 | |||
| 1783 | /** |
||
| 1784 | * create wallet using the API |
||
| 1785 | * |
||
| 1786 | * @param identifier string the wallet identifier to create |
||
| 1787 | * @param primaryPublicKey array the primary public key - [key, path] should be M/<keyIndex>' |
||
| 1788 | * @param backupPublicKey array the backup public key - [key, path] should be M/<keyIndex>' |
||
| 1789 | * @param encryptedPrimarySeed string openssl format |
||
| 1790 | * @param encryptedSecret string openssl format |
||
| 1791 | * @param recoverySecret string openssl format |
||
| 1792 | * @param checksum string checksum to store |
||
| 1793 | * @param keyIndex int keyIndex that was used to create wallet |
||
| 1794 | * @param supportSecret string |
||
| 1795 | * @param segwit bool |
||
| 1796 | * @returns {q.Promise} |
||
| 1797 | */ |
||
| 1798 | APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret, |
||
| 1799 | recoverySecret, checksum, keyIndex, supportSecret, segwit) { |
||
| 1800 | var self = this; |
||
| 1801 | |||
| 1802 | var postData = { |
||
| 1803 | identifier: identifier, |
||
| 1804 | wallet_version: Wallet.WALLET_VERSION_V2, |
||
| 1805 | primary_public_key: primaryPublicKey, |
||
| 1806 | backup_public_key: backupPublicKey, |
||
| 1807 | encrypted_primary_seed: encryptedPrimarySeed, |
||
| 1808 | encrypted_secret: encryptedSecret, |
||
| 1809 | recovery_secret: recoverySecret, |
||
| 1810 | checksum: checksum, |
||
| 1811 | key_index: keyIndex, |
||
| 1812 | support_secret: supportSecret || null, |
||
| 1813 | segwit: segwit |
||
| 1814 | }; |
||
| 1815 | |||
| 1816 | verifyPublicOnly(postData, self.network); |
||
| 1817 | |||
| 1818 | return self.blocktrailClient.post("/wallet", null, postData); |
||
| 1819 | }; |
||
| 1820 | |||
| 1821 | /** |
||
| 1822 | * create wallet using the API |
||
| 1823 | * |
||
| 1824 | * @param identifier string the wallet identifier to create |
||
| 1825 | * @param primaryPublicKey array the primary public key - [key, path] should be M/<keyIndex>' |
||
| 1826 | * @param backupPublicKey array the backup public key - [key, path] should be M/<keyIndex>' |
||
| 1827 | * @param encryptedPrimarySeed Buffer buffer of ciphertext |
||
| 1828 | * @param encryptedSecret Buffer buffer of ciphertext |
||
| 1829 | * @param recoverySecret Buffer buffer of recovery secret |
||
| 1830 | * @param checksum string checksum to store |
||
| 1831 | * @param keyIndex int keyIndex that was used to create wallet |
||
| 1832 | * @param supportSecret string |
||
| 1833 | * @param segwit bool |
||
| 1834 | * @returns {q.Promise} |
||
| 1835 | */ |
||
| 1836 | APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret, |
||
| 1837 | recoverySecret, checksum, keyIndex, supportSecret, segwit) { |
||
| 1838 | var self = this; |
||
| 1839 | |||
| 1840 | var postData = { |
||
| 1841 | identifier: identifier, |
||
| 1842 | wallet_version: Wallet.WALLET_VERSION_V3, |
||
| 1843 | primary_public_key: primaryPublicKey, |
||
| 1844 | backup_public_key: backupPublicKey, |
||
| 1845 | encrypted_primary_seed: encryptedPrimarySeed.toString('base64'), |
||
| 1846 | encrypted_secret: encryptedSecret.toString('base64'), |
||
| 1847 | recovery_secret: recoverySecret.toString('hex'), |
||
| 1848 | checksum: checksum, |
||
| 1849 | key_index: keyIndex, |
||
| 1850 | support_secret: supportSecret || null, |
||
| 1851 | segwit: segwit |
||
| 1852 | }; |
||
| 1853 | |||
| 1854 | verifyPublicOnly(postData, self.network); |
||
| 1855 | |||
| 1856 | return self.blocktrailClient.post("/wallet", null, postData); |
||
| 1857 | }; |
||
| 1858 | |||
| 1859 | /** |
||
| 1860 | * create wallet using the API |
||
| 1861 | * |
||
| 1862 | * @param identifier string the wallet identifier to create |
||
| 1863 | * @param postData object |
||
| 1864 | * @param [cb] function callback(err, result) |
||
| 1865 | * @returns {q.Promise} |
||
| 1866 | */ |
||
| 1867 | APIClient.prototype.updateWallet = function(identifier, postData, cb) { |
||
| 1868 | var self = this; |
||
| 1869 | |||
| 1870 | return self.blocktrailClient.post("/wallet/" + identifier, null, postData, cb); |
||
| 1871 | }; |
||
| 1872 | |||
| 1873 | /** |
||
| 1874 | * upgrade wallet to use a new account number |
||
| 1875 | * the account number specifies which blocktrail cosigning key is used |
||
| 1876 | * |
||
| 1877 | * @param identifier string the wallet identifier |
||
| 1878 | * @param primaryPublicKey array the primary public key - [key, path] should be M/<keyIndex>' |
||
| 1879 | * @param keyIndex int keyIndex that was used to create wallet |
||
| 1880 | * @param [cb] function callback(err, result) |
||
| 1881 | * @returns {q.Promise} |
||
| 1882 | */ |
||
| 1883 | APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) { |
||
| 1884 | var self = this; |
||
| 1885 | |||
| 1886 | return self.blocktrailClient.post("/wallet/" + identifier + "/upgrade", null, { |
||
| 1887 | key_index: keyIndex, |
||
| 1888 | primary_public_key: primaryPublicKey |
||
| 1889 | }, cb); |
||
| 1890 | }; |
||
| 1891 | |||
| 1892 | /** |
||
| 1893 | * get the balance for the wallet |
||
| 1894 | * |
||
| 1895 | * @param identifier string the wallet identifier |
||
| 1896 | * @param [cb] function callback(err, result) |
||
| 1897 | * @returns {q.Promise} |
||
| 1898 | */ |
||
| 1899 | APIClient.prototype.getWalletBalance = function(identifier, cb) { |
||
| 1900 | var self = this; |
||
| 1901 | |||
| 1902 | return self.blocktrailClient.get("/wallet/" + identifier + "/balance", null, true, cb); |
||
| 1903 | }; |
||
| 1904 | |||
| 1905 | /** |
||
| 1906 | * do HD wallet discovery for the wallet |
||
| 1907 | * |
||
| 1908 | * @param identifier string the wallet identifier |
||
| 1909 | * @param [cb] function callback(err, result) |
||
| 1910 | * @returns {q.Promise} |
||
| 1911 | */ |
||
| 1912 | APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) { |
||
| 1913 | var self = this; |
||
| 1914 | |||
| 1915 | return self.blocktrailClient.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb); |
||
| 1916 | }; |
||
| 1917 | |||
| 1918 | |||
| 1919 | /** |
||
| 1920 | * get a new derivation number for specified parent path |
||
| 1921 | * eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2 |
||
| 1922 | * |
||
| 1923 | * @param identifier string the wallet identifier |
||
| 1924 | * @param path string the parent path for which to get a new derivation, |
||
| 1925 | * can be suffixed with /* to make it clear on which level the derivations hould be |
||
| 1926 | * @param [cb] function callback(err, result) |
||
| 1927 | * @returns {q.Promise} |
||
| 1928 | */ |
||
| 1929 | APIClient.prototype.getNewDerivation = function(identifier, path, cb) { |
||
| 1930 | var self = this; |
||
| 1931 | |||
| 1932 | return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb); |
||
| 1933 | }; |
||
| 1934 | |||
| 1935 | |||
| 1936 | /** |
||
| 1937 | * delete the wallet |
||
| 1938 | * the checksum address and a signature to verify you ownership of the key of that checksum address |
||
| 1939 | * is required to be able to delete a wallet |
||
| 1940 | * |
||
| 1941 | * @param identifier string the wallet identifier |
||
| 1942 | * @param checksumAddress string the address for your master private key (and the checksum used when creating the wallet) |
||
| 1943 | * @param checksumSignature string a signature of the checksum address as message signed by the private key matching that address |
||
| 1944 | * @param [force] bool ignore warnings (such as a non-zero balance) |
||
| 1945 | * @param [cb] function callback(err, result) |
||
| 1946 | * @returns {q.Promise} |
||
| 1947 | */ |
||
| 1948 | APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) { |
||
| 1949 | var self = this; |
||
| 1950 | |||
| 1951 | if (typeof force === "function") { |
||
| 1952 | cb = force; |
||
| 1953 | force = false; |
||
| 1954 | } |
||
| 1955 | |||
| 1956 | return self.blocktrailClient.delete("/wallet/" + identifier, {force: force}, { |
||
| 1957 | checksum: checksumAddress, |
||
| 1958 | signature: checksumSignature |
||
| 1959 | }, cb); |
||
| 1960 | }; |
||
| 1961 | |||
| 1962 | /** |
||
| 1963 | * use the API to get the best inputs to use based on the outputs |
||
| 1964 | * |
||
| 1965 | * the return array has the following format: |
||
| 1966 | * [ |
||
| 1967 | * "utxos" => [ |
||
| 1968 | * [ |
||
| 1969 | * "hash" => "<txHash>", |
||
| 1970 | * "idx" => "<index of the output of that <txHash>", |
||
| 1971 | * "scriptpubkey_hex" => "<scriptPubKey-hex>", |
||
| 1972 | * "value" => 32746327, |
||
| 1973 | * "address" => "1address", |
||
| 1974 | * "path" => "m/44'/1'/0'/0/13", |
||
| 1975 | * "redeem_script" => "<redeemScript-hex>", |
||
| 1976 | * ], |
||
| 1977 | * ], |
||
| 1978 | * "fee" => 10000, |
||
| 1979 | * "change"=> 1010109201, |
||
| 1980 | * ] |
||
| 1981 | * |
||
| 1982 | * @param identifier string the wallet identifier |
||
| 1983 | * @param pay array {'address': (int)value} coins to send |
||
| 1984 | * @param lockUTXO bool lock UTXOs for a few seconds to allow for transaction to be created |
||
| 1985 | * @param allowZeroConf bool allow zero confirmation unspent outputs to be used in coin selection |
||
| 1986 | * @param feeStrategy string defaults to |
||
| 1987 | * @param options |
||
| 1988 | * @param [cb] function callback(err, utxos, fee, change) |
||
| 1989 | * @returns {q.Promise} |
||
| 1990 | */ |
||
| 1991 | APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) { |
||
| 1992 | var self = this; |
||
| 1993 | |||
| 1994 | if (typeof feeStrategy === "function") { |
||
| 1995 | cb = feeStrategy; |
||
| 1996 | feeStrategy = null; |
||
| 1997 | options = {}; |
||
| 1998 | } else if (typeof options === "function") { |
||
| 1999 | cb = options; |
||
| 2000 | options = {}; |
||
| 2001 | } |
||
| 2002 | |||
| 2003 | feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; |
||
| 2004 | options = options || {}; |
||
| 2005 | |||
| 2006 | var deferred = q.defer(); |
||
| 2007 | deferred.promise.spreadNodeify(cb); |
||
| 2008 | |||
| 2009 | var params = { |
||
| 2010 | lock: lockUTXO, |
||
| 2011 | zeroconf: allowZeroConf ? 1 : 0, |
||
| 2012 | zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0, |
||
| 2013 | fee_strategy: feeStrategy |
||
| 2014 | }; |
||
| 2015 | |||
| 2016 | if (options.forcefee) { |
||
| 2017 | params['forcefee'] = options.forcefee; |
||
| 2018 | } |
||
| 2019 | |||
| 2020 | deferred.resolve( |
||
| 2021 | self.blocktrailClient.post("/wallet/" + identifier + "/coin-selection", params, pay).then( |
||
| 2022 | function(result) { |
||
| 2023 | return [result.utxos, result.fee, result.change, result]; |
||
| 2024 | }, |
||
| 2025 | function(err) { |
||
| 2026 | if (err.message.match(/too low to pay the fee/)) { |
||
| 2027 | throw blocktrail.WalletFeeError(err); |
||
| 2028 | } |
||
| 2029 | |||
| 2030 | throw err; |
||
| 2031 | } |
||
| 2032 | ) |
||
| 2033 | ); |
||
| 2034 | |||
| 2035 | return deferred.promise; |
||
| 2036 | }; |
||
| 2037 | |||
| 2038 | /** |
||
| 2039 | * @param [cb] function callback(err, utxos, fee, change) |
||
| 2040 | * @returns {q.Promise} |
||
| 2041 | */ |
||
| 2042 | APIClient.prototype.feePerKB = function(cb) { |
||
| 2043 | var self = this; |
||
| 2044 | |||
| 2045 | var deferred = q.defer(); |
||
| 2046 | deferred.promise.spreadNodeify(cb); |
||
| 2047 | |||
| 2048 | deferred.resolve(self.blocktrailClient.get("/fee-per-kb")); |
||
| 2049 | |||
| 2050 | return deferred.promise; |
||
| 2051 | }; |
||
| 2052 | |||
| 2053 | /** |
||
| 2054 | * send the transaction using the API |
||
| 2055 | * |
||
| 2056 | * @param identifier string the wallet identifier |
||
| 2057 | * @param txHex string partially signed transaction as hex string |
||
| 2058 | * @param paths array list of paths used in inputs which should be cosigned by the API |
||
| 2059 | * @param checkFee bool when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception |
||
| 2060 | * @param [twoFactorToken] string 2FA token |
||
| 2061 | * @param [prioboost] bool |
||
| 2062 | * @param [cb] function callback(err, txHash) |
||
| 2063 | * @returns {q.Promise} |
||
| 2064 | */ |
||
| 2065 | APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) { |
||
| 2066 | var self = this; |
||
| 2067 | |||
| 2068 | if (typeof twoFactorToken === "function") { |
||
| 2069 | cb = twoFactorToken; |
||
| 2070 | twoFactorToken = null; |
||
| 2071 | prioboost = false; |
||
| 2072 | } else if (typeof prioboost === "function") { |
||
| 2073 | cb = prioboost; |
||
| 2074 | prioboost = false; |
||
| 2075 | } |
||
| 2076 | |||
| 2077 | var data = { |
||
| 2078 | paths: paths, |
||
| 2079 | two_factor_token: twoFactorToken |
||
| 2080 | }; |
||
| 2081 | if (typeof txHex === "string") { |
||
| 2082 | data.raw_transaction = txHex; |
||
| 2083 | } else if (typeof txHex === "object") { |
||
| 2084 | Object.keys(txHex).map(function(key) { |
||
| 2085 | data[key] = txHex[key]; |
||
| 2086 | }); |
||
| 2087 | } |
||
| 2088 | |||
| 2089 | return self.blocktrailClient.post( |
||
| 2090 | "/wallet/" + identifier + "/send", |
||
| 2091 | { |
||
| 2092 | check_fee: checkFee ? 1 : 0, |
||
| 2093 | prioboost: prioboost ? 1 : 0 |
||
| 2094 | }, |
||
| 2095 | data, |
||
| 2096 | cb |
||
| 2097 | ); |
||
| 2098 | }; |
||
| 2099 | |||
| 2100 | /** |
||
| 2101 | * setup a webhook for this wallet |
||
| 2102 | * |
||
| 2103 | * @param identifier string the wallet identifier |
||
| 2104 | * @param webhookIdentifier string identifier for the webhook |
||
| 2105 | * @param url string URL to receive webhook events |
||
| 2106 | * @param [cb] function callback(err, webhook) |
||
| 2107 | * @returns {q.Promise} |
||
| 2108 | */ |
||
| 2109 | APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) { |
||
| 2110 | var self = this; |
||
| 2111 | |||
| 2112 | return self.blocktrailClient.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb); |
||
| 2113 | }; |
||
| 2114 | |||
| 2115 | /** |
||
| 2116 | * delete a webhook that was created for this wallet |
||
| 2117 | * |
||
| 2118 | * @param identifier string the wallet identifier |
||
| 2119 | * @param webhookIdentifier string identifier for the webhook |
||
| 2120 | * @param [cb] function callback(err, success) |
||
| 2121 | * @returns {q.Promise} |
||
| 2122 | */ |
||
| 2123 | APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) { |
||
| 2124 | var self = this; |
||
| 2125 | |||
| 2126 | return self.blocktrailClient.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb); |
||
| 2127 | }; |
||
| 2128 | |||
| 2129 | /** |
||
| 2130 | * get all transactions for an wallet (paginated) |
||
| 2131 | * |
||
| 2132 | * @param identifier string wallet identifier |
||
| 2133 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 2134 | * @param [cb] function callback function to call when request is complete |
||
| 2135 | * @return q.Promise |
||
| 2136 | */ |
||
| 2137 | APIClient.prototype.walletTransactions = function(identifier, params, cb) { |
||
| 2138 | var self = this; |
||
| 2139 | |||
| 2140 | if (typeof params === "function") { |
||
| 2141 | cb = params; |
||
| 2142 | params = null; |
||
| 2143 | } |
||
| 2144 | |||
| 2145 | return self.blocktrailClient.get("/wallet/" + identifier + "/transactions", params, true, cb); |
||
| 2146 | }; |
||
| 2147 | |||
| 2148 | /** |
||
| 2149 | * get all addresses for an wallet (paginated) |
||
| 2150 | * |
||
| 2151 | * @param identifier string wallet identifier |
||
| 2152 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 2153 | * @param [cb] function callback function to call when request is complete |
||
| 2154 | * @return q.Promise |
||
| 2155 | */ |
||
| 2156 | APIClient.prototype.walletAddresses = function(identifier, params, cb) { |
||
| 2157 | var self = this; |
||
| 2158 | |||
| 2159 | if (typeof params === "function") { |
||
| 2160 | cb = params; |
||
| 2161 | params = null; |
||
| 2162 | } |
||
| 2163 | |||
| 2164 | return self.blocktrailClient.get("/wallet/" + identifier + "/addresses", params, true, cb); |
||
| 2165 | }; |
||
| 2166 | |||
| 2167 | /** |
||
| 2168 | * @param identifier string wallet identifier |
||
| 2169 | * @param address string the address to label |
||
| 2170 | * @param label string the label |
||
| 2171 | * @param [cb] function callback(err, res) |
||
| 2172 | * @return q.Promise |
||
| 2173 | */ |
||
| 2174 | APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) { |
||
| 2175 | var self = this; |
||
| 2176 | |||
| 2177 | return self.blocktrailClient.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb); |
||
| 2178 | }; |
||
| 2179 | |||
| 2180 | APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) { |
||
| 2181 | var self = this; |
||
| 2182 | |||
| 2183 | if (typeof feeStrategy === "function") { |
||
| 2184 | cb = feeStrategy; |
||
| 2185 | feeStrategy = null; |
||
| 2186 | } else if (typeof options === "function") { |
||
| 2187 | cb = options; |
||
| 2188 | options = {}; |
||
| 2189 | } |
||
| 2190 | |||
| 2191 | feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; |
||
| 2192 | options = options || {}; |
||
| 2193 | |||
| 2194 | var params = { |
||
| 2195 | outputs: options.outputs ? options.outputs : 1, |
||
| 2196 | zeroconf: allowZeroConf ? 1 : 0, |
||
| 2197 | zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0, |
||
| 2198 | fee_strategy: feeStrategy |
||
| 2199 | }; |
||
| 2200 | |||
| 2201 | if (options.forcefee) { |
||
| 2202 | params['forcefee'] = options.forcefee; |
||
| 2203 | } |
||
| 2204 | |||
| 2205 | return self.blocktrailClient.get("/wallet/" + identifier + "/max-spendable", params, true, cb); |
||
| 2206 | }; |
||
| 2207 | |||
| 2208 | /** |
||
| 2209 | * get all UTXOs for an wallet (paginated) |
||
| 2210 | * |
||
| 2211 | * @param identifier string wallet identifier |
||
| 2212 | * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} |
||
| 2213 | * @param [cb] function callback function to call when request is complete |
||
| 2214 | * @return q.Promise |
||
| 2215 | */ |
||
| 2216 | APIClient.prototype.walletUTXOs = function(identifier, params, cb) { |
||
| 2217 | var self = this; |
||
| 2218 | |||
| 2219 | if (typeof params === "function") { |
||
| 2220 | cb = params; |
||
| 2221 | params = null; |
||
| 2222 | } |
||
| 2223 | |||
| 2224 | return self.blocktrailClient.get("/wallet/" + identifier + "/utxos", params, true, cb); |
||
| 2225 | }; |
||
| 2226 | |||
| 2227 | /** |
||
| 2228 | * get a paginated list of all wallets associated with the api user |
||
| 2229 | * |
||
| 2230 | * @param [params] object pagination: {page: 1, limit: 20} |
||
| 2231 | * @param [cb] function callback function to call when request is complete |
||
| 2232 | * @return q.Promise |
||
| 2233 | */ |
||
| 2234 | APIClient.prototype.allWallets = function(params, cb) { |
||
| 2235 | var self = this; |
||
| 2236 | |||
| 2237 | if (typeof params === "function") { |
||
| 2238 | cb = params; |
||
| 2239 | params = null; |
||
| 2240 | } |
||
| 2241 | |||
| 2242 | return self.blocktrailClient.get("/wallets", params, true, cb); |
||
| 2243 | }; |
||
| 2244 | |||
| 2245 | /** |
||
| 2246 | * verify a message signed bitcoin-core style |
||
| 2247 | * |
||
| 2248 | * @param message string |
||
| 2249 | * @param address string |
||
| 2250 | * @param signature string |
||
| 2251 | * @param [cb] function callback function to call when request is complete |
||
| 2252 | * @return q.Promise |
||
| 2253 | */ |
||
| 2254 | APIClient.prototype.verifyMessage = function(message, address, signature, cb) { |
||
| 2255 | var self = this; |
||
| 2256 | |||
| 2257 | var deferred = q.defer(); |
||
| 2258 | deferred.promise.nodeify(cb); |
||
| 2259 | try { |
||
| 2260 | var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64')); |
||
|
0 ignored issues
–
show
The variable
Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. Loading history...
|
|||
| 2261 | deferred.resolve(result); |
||
| 2262 | } catch (e) { |
||
| 2263 | deferred.reject(e); |
||
| 2264 | } |
||
| 2265 | |||
| 2266 | return deferred.promise; |
||
| 2267 | }; |
||
| 2268 | |||
| 2269 | /** |
||
| 2270 | * max is 0.001 |
||
| 2271 | * testnet only |
||
| 2272 | * |
||
| 2273 | * @param address |
||
| 2274 | * @param amount |
||
| 2275 | * @param cb |
||
| 2276 | */ |
||
| 2277 | APIClient.prototype.faucetWithdrawl = function(address, amount, cb) { |
||
| 2278 | var self = this; |
||
| 2279 | |||
| 2280 | return self.blocktrailClient.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb); |
||
| 2281 | }; |
||
| 2282 | |||
| 2283 | /** |
||
| 2284 | * send a raw transaction |
||
| 2285 | * |
||
| 2286 | * @param rawTransaction string raw transaction as HEX |
||
| 2287 | * @param [cb] function callback function to call when request is complete |
||
| 2288 | * @return q.Promise |
||
| 2289 | */ |
||
| 2290 | APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) { |
||
| 2291 | var self = this; |
||
| 2292 | |||
| 2293 | return self.blocktrailClient.post("/send-raw-tx", null, rawTransaction, cb); |
||
| 2294 | }; |
||
| 2295 | |||
| 2296 | /** |
||
| 2297 | * get the current price index |
||
| 2298 | * |
||
| 2299 | * @param [cb] function callback({'USD': 287.30}) |
||
| 2300 | * @return q.Promise |
||
| 2301 | */ |
||
| 2302 | APIClient.prototype.price = function(cb) { |
||
| 2303 | var self = this; |
||
| 2304 | |||
| 2305 | return self.blocktrailClient.get("/price", null, false, cb); |
||
| 2306 | }; |
||
| 2307 | |||
| 2308 | module.exports = APIClient; |
||
| 2309 |