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

APIClient.transaction   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 33
rs 8.8571
nop 2

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
    if (options.btccom && process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM) {
201
        options.throttleRequestsTimeout = process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM;
202
    }
203
204
    // trim off leading https?://
205
    if (options.host && options.host.indexOf("https://") === 0) {
206
        options.https = true;
207
        options.host = options.host.substr(8);
208
    } else if (options.host && options.host.indexOf("http://") === 0) {
209
        options.https = false;
210
        options.host = options.host.substr(7);
211
    }
212
213
    if (typeof options.https === "undefined") {
214
        options.https = true;
215
    }
216
217
    if (!options.port) {
218
        options.port = options.https ? 443 : 80;
219
    }
220
221
    if (options.btccom) {
222
        if (!options.host) {
223
            options.host = 'chain.api.btc.com';
224
        }
225
226
        if (options.testnet && !options.host.match(/tchain/)) {
227
            options.host = options.host.replace(/chain/, 'tchain');
228
        }
229
230
        if (!options.endpoint) {
231
            options.endpoint = "/" + (options.apiVersion || "v3");
232
        }
233
    } else {
234
        if (!options.host) {
235
            options.host = 'api.blocktrail.com';
236
        }
237
238
        if (!options.endpoint) {
239
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
240
        }
241
    }
242
243
    return options;
244
};
245
246
APIClient.initRestClient = function(options) {
247
    options = APIClient.updateHostOptions(options);
248
    return new RestClient(options);
249
};
250
251
var determineDataStorageV2_3 = function(options) {
252
    return q.when(options)
253
        .then(function(options) {
254
            // legacy
255
            if (options.storePrimaryMnemonic) {
256
                options.storeDataOnServer = options.storePrimaryMnemonic;
257
            }
258
259
            // storeDataOnServer=false when primarySeed is provided
260
            if (typeof options.storeDataOnServer === "undefined") {
261
                options.storeDataOnServer = !options.primarySeed;
262
            }
263
264
            return options;
265
        });
266
};
267
268
var produceEncryptedDataV2 = function(options, notify) {
269
    return q.when(options)
270
        .then(function(options) {
271
            if (options.storeDataOnServer) {
272
                if (!options.secret) {
273
                    if (!options.passphrase) {
274
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
275
                    }
276
277
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
278
279
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
280
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
281
                }
282
283
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
284
285
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
286
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
287
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
288
289
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
290
291
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
292
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
293
            }
294
295
            return options;
296
        });
297
};
298
299
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
300
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
301
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
302
        var saltBuf = Encryption.generateSalt();
303
        var iv = Encryption.generateIV();
304
305
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
306
            return require('./webworker');
307
        }, onLoadWorkerLoadAsmCrypto, {
308
            method: 'Encryption.encryptWithSaltAndIV',
309
            pt: pt,
310
            pw: pw,
311
            saltBuf: saltBuf,
312
            iv: iv,
313
            iterations: iter
314
        })
315
            .then(function(data) {
316
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
317
            });
318
    } else {
319
        try {
320
            return q.when(Encryption.encrypt(pt, pw, iter));
321
        } catch (e) {
322
            return q.reject(e);
323
        }
324
    }
325
};
326
327
APIClient.prototype.promisedDecrypt = function(ct, pw) {
328
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
329
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
330
            return require('./webworker');
331
        }, onLoadWorkerLoadAsmCrypto, {
332
            method: 'Encryption.decrypt',
333
            ct: ct,
334
            pw: pw
335
        })
336
            .then(function(data) {
337
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
338
            });
339
    } else {
340
        try {
341
            return q.when(Encryption.decrypt(ct, pw));
342
        } catch (e) {
343
            return q.reject(e);
344
        }
345
    }
346
};
347
348
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
349
    var self = this;
350
351
    return q.when(options)
352
        .then(function(options) {
353
            if (options.storeDataOnServer) {
354
                return q.when()
355
                    .then(function() {
356
                        if (!options.secret) {
357
                            if (!options.passphrase) {
358
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
359
                            }
360
361
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
362
363
                            // -> now a buffer
364
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
365
366
                            // -> now a buffer
367
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
368
                                .then(function(encryptedSecret) {
369
                                    options.encryptedSecret = encryptedSecret;
370
                                });
371
                        } else {
372
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

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

Loading history...
373
                                throw new Error('Secret must be a buffer');
374
                            }
375
                        }
376
                    })
377
                    .then(function() {
378
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
379
380
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
381
                            .then(function(encryptedPrimarySeed) {
382
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
383
                            });
384
                    })
385
                    .then(function() {
386
                        // skip generating recovery secret when explicitly set to false
387
                        if (options.recoverySecret === false) {
388
                            return;
389
                        }
390
391
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
392
                        if (!options.recoverySecret) {
393
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
394
                        }
395
396
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
397
                            .then(function(recoveryEncryptedSecret) {
398
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
399
                            });
400
                    })
401
                    .then(function() {
402
                        return options;
403
                    });
404
            } else {
405
                return options;
406
            }
407
        });
408
};
409
410
var doRemainingWalletDataV2_3 = function(options, network, notify) {
411
    return q.when(options)
412
        .then(function(options) {
413
            if (!options.backupPublicKey) {
414
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
415
            }
416
417
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
418
419
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
420
421
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
422
423
            if (!options.backupPublicKey) {
424
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
425
                options.backupPublicKey = options.backupPrivateKey.neutered();
426
            }
427
428
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
429
430
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
431
432
            return options;
433
        });
434
};
435
436
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
437
    var self = this;
438
439
    var deferred = q.defer();
440
    deferred.promise.spreadNodeify(cb);
441
442
    deferred.resolve(q.fcall(function() {
443
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
444
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
445
        });
446
    }));
447
448
    return deferred.promise;
449
};
450
451
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
452
    var self = this;
453
454
    if (useWebWorker) {
455
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
456
            return require('./webworker');
457
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
458
            .then(function(data) {
459
                return data.seed;
460
            });
461
    } else {
462
        try {
463
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
464
        } catch (e) {
465
            return q.reject(e);
466
        }
467
    }
468
};
469
470
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
471
    var self = this;
472
473
    var deferred = q.defer();
474
    deferred.promise.nodeify(cb);
475
476
    try {
477
        // avoid conflicting options
478
        if (options.passphrase && options.password) {
479
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
480
        }
481
        // normalize passphrase/password
482
        options.passphrase = options.passphrase || options.password;
483
        delete options.password;
484
485
        // avoid conflicting options
486
        if (options.primaryMnemonic && options.primarySeed) {
487
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
488
        }
489
490
        // avoid deprecated options
491
        if (options.primaryPrivateKey) {
492
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
493
        }
494
495
        // make sure we have at least one thing to use
496
        if (!options.primaryMnemonic && !options.primarySeed) {
497
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
498
        }
499
500
        if (options.primarySeed) {
501
            self.primarySeed = options.primarySeed;
502
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
503
            deferred.resolve(options);
504
        } else {
505
            if (!options.passphrase) {
506
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
507
            }
508
509
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
510
                .then(function(seedHex) {
511
                    try {
512
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

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