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

APIClient.addressUnconfirmedTransactions   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
nc 2
dl 0
loc 25
rs 8.8571
nop 3

1 Function

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