Completed
Pull Request — master (#99)
by Ruben de
01:05
created

APIClient.initRestClient   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
nc 1
dl 0
loc 4
rs 10
nop 1
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
    self.dataClient = APIClient.initRestClient(_.merge({}, options));
121
    /**
122
     * @type RestClient
123
     */
124
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
125
126
    if (options.btccom) {
127
        var rawTxHost = (options.testnet ? 'tchain.btc.com' : 'btc.com');
128
129
        self.converter = new BtccomConverter(self.network, true, rawTxHost);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
130
    } else {
131
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
132
    }
133
134
};
135
136
APIClient.normalizeNetworkFromOptions = function(options) {
137
    /* jshint -W071, -W074 */
138
    var network = 'BTC';
139
    var testnet = false;
140
    var regtest = false;
141
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
142
143
    var prefix;
144
    var done = false;
145
146
    if (options.network) {
147
        var lower = options.network.toLowerCase();
148
149
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
150
        if (!m) {
151
            throw new Error("Invalid network [" + options.network + "]");
152
        }
153
154
        if (m[2] === 'btc') {
155
            network = "BTC";
156
        } else {
157
            network = "BCC";
158
        }
159
160
        prefix = m[1];
161
        if (prefix) {
162
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
163
            done = true;
164
            if (prefix === 'r') {
165
                testnet = true;
166
                regtest = true;
167
            } else if (prefix === 't') {
168
                testnet = true;
169
            }
170
        }
171
    }
172
173
    // if we're not already done then apply options.regtest and options.testnet
174
    if (!done) {
175
        if (options.regtest) {
176
            testnet = true;
177
            regtest = true;
178
            prefix = "r";
179
        } else if (options.testnet) {
180
            testnet = true;
181
            prefix = "t";
182
        }
183
    }
184
185
    apiNetwork = (prefix || "") + network;
186
187
    return [network, testnet, regtest, apiNetwork];
188
};
189
190
APIClient.updateHostOptions = function(options) {
191
    /* jshint -W071, -W074 */
192
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
193
    if (!options.btccom && process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
194
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
195
    }
196
    if (options.btccom && process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT) {
197
        options.host = process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT;
198
    }
199
200
    if (options.btccom && process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM) {
201
        options.throttleRequestsTimeout = process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM;
202
    }
203
204
    // trim off leading https?://
205
    if (options.host && options.host.indexOf("https://") === 0) {
206
        options.https = true;
207
        options.host = options.host.substr(8);
208
    } else if (options.host && options.host.indexOf("http://") === 0) {
209
        options.https = false;
210
        options.host = options.host.substr(7);
211
    }
212
213
    if (typeof options.https === "undefined") {
214
        options.https = true;
215
    }
216
217
    if (!options.port) {
218
        options.port = options.https ? 443 : 80;
219
    }
220
221
    if (options.btccom) {
222
        if (!options.host) {
223
            options.host = 'chain.api.btc.com';
224
        }
225
226
        if (options.testnet && !options.host.match(/tchain/)) {
227
            options.host = options.host.replace(/chain/, 'tchain');
228
        }
229
230
        if (!options.endpoint) {
231
            options.endpoint = "/" + (options.apiVersion || "v3");
232
        }
233
    } else {
234
        if (!options.host) {
235
            options.host = 'api.blocktrail.com';
236
        }
237
238
        if (!options.endpoint) {
239
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
240
        }
241
    }
242
243
    return options;
244
};
245
246
APIClient.initRestClient = function(options) {
247
    options = APIClient.updateHostOptions(options);
248
    return new RestClient(options);
249
};
250
251
var determineDataStorageV2_3 = function(options) {
252
    return q.when(options)
253
        .then(function(options) {
254
            // legacy
255
            if (options.storePrimaryMnemonic) {
256
                options.storeDataOnServer = options.storePrimaryMnemonic;
257
            }
258
259
            // storeDataOnServer=false when primarySeed is provided
260
            if (typeof options.storeDataOnServer === "undefined") {
261
                options.storeDataOnServer = !options.primarySeed;
262
            }
263
264
            return options;
265
        });
266
};
267
268
var produceEncryptedDataV2 = function(options, notify) {
269
    return q.when(options)
270
        .then(function(options) {
271
            if (options.storeDataOnServer) {
272
                if (!options.secret) {
273
                    if (!options.passphrase) {
274
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
275
                    }
276
277
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
278
279
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
280
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
281
                }
282
283
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
284
285
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
286
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
287
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
288
289
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
290
291
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
292
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
293
            }
294
295
            return options;
296
        });
297
};
298
299
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
300
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
301
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
302
        var saltBuf = Encryption.generateSalt();
303
        var iv = Encryption.generateIV();
304
305
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
306
            return require('./webworker');
307
        }, onLoadWorkerLoadAsmCrypto, {
308
            method: 'Encryption.encryptWithSaltAndIV',
309
            pt: pt,
310
            pw: pw,
311
            saltBuf: saltBuf,
312
            iv: iv,
313
            iterations: iter
314
        })
315
            .then(function(data) {
316
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
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...
317
            });
318
    } else {
319
        try {
320
            return q.when(Encryption.encrypt(pt, pw, iter));
321
        } catch (e) {
322
            return q.reject(e);
323
        }
324
    }
325
};
326
327
APIClient.prototype.promisedDecrypt = function(ct, pw) {
328
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
329
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
330
            return require('./webworker');
331
        }, onLoadWorkerLoadAsmCrypto, {
332
            method: 'Encryption.decrypt',
333
            ct: ct,
334
            pw: pw
335
        })
336
            .then(function(data) {
337
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
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...
338
            });
339
    } else {
340
        try {
341
            return q.when(Encryption.decrypt(ct, pw));
342
        } catch (e) {
343
            return q.reject(e);
344
        }
345
    }
346
};
347
348
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
349
    var self = this;
350
351
    return q.when(options)
352
        .then(function(options) {
353
            if (options.storeDataOnServer) {
354
                return q.when()
355
                    .then(function() {
356
                        if (!options.secret) {
357
                            if (!options.passphrase) {
358
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
359
                            }
360
361
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
362
363
                            // -> now a buffer
364
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
365
366
                            // -> now a buffer
367
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
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...
368
                                .then(function(encryptedSecret) {
369
                                    options.encryptedSecret = encryptedSecret;
370
                                });
371
                        } else {
372
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Bug introduced by
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...
Complexity Best Practice introduced by
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 return statement is found in some execution paths, but not in all.

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 isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

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