Completed
Pull Request — master (#99)
by Ruben de
59s
created

api_client.js ➔ APIClient   C

Complexity

Conditions 7
Paths 161

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 7
c 3
b 1
f 0
nc 161
dl 0
loc 46
rs 5.5
nop 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
/**
22
 *
23
 * @param opt
24
 * @returns {*}
25
 */
26
function networkFromOptions(opt) {
27
    if (opt.bitcoinCash) {
28
        if (opt.regtest) {
29
            return bitcoin.networks.bitcoincashregtest;
30
        } else if (opt.testnet) {
31
            return bitcoin.networks.bitcoincashtestnet;
32
        } else {
33
            return bitcoin.networks.bitcoincash;
34
        }
35
    } else {
36
        if (opt.regtest) {
37
            return bitcoin.networks.regtest;
38
        } else if (opt.testnet) {
39
            return bitcoin.networks.testnet;
40
        } else {
41
            return bitcoin.networks.bitcoin;
42
        }
43
    }
44
}
45
46
var useWebWorker = require('./use-webworker')();
47
48
49
/**
50
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
51
 *
52
 * @param promise   {q.Promise}
53
 * @param cb        function
54
 * @return q.Promise
55
 */
56
function callbackify(promise, cb) {
57
    // add a .then to trigger the cb for people using callbacks
58
    if (cb) {
59
        promise
60
            .then(function(res) {
61
                // use q.nextTick for asyncness
62
                q.nextTick(function() {
63
                    cb(null, res);
64
                });
65
            }, function(err) {
66
                // use q.nextTick for asyncness
67
                q.nextTick(function() {
68
                    cb(err, null);
69
                });
70
            });
71
    }
72
73
    // return the promise for people using promises
74
    return promise;
75
}
76
77
/**
78
 * Bindings to consume the BlockTrail API
79
 *
80
 * @param options       object{
81
 *                          apiKey: 'API_KEY',
82
 *                          apiSecret: 'API_SECRET',
83
 *                          host: 'defaults to api.blocktrail.com',
84
 *                          network: 'BTC|LTC',
85
 *                          testnet: true|false
86
 *                      }
87
 * @constructor
88
 */
89
var APIClient = function(options) {
90
    var self = this;
91
92
    // handle constructor call without 'new'
93
    if (!(this instanceof APIClient)) {
94
        return new APIClient(options);
95
    }
96
97
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
98
    options.network = normalizedNetwork[0];
99
    options.testnet = normalizedNetwork[1];
100
    options.regtest = normalizedNetwork[2];
101
    // apiNetwork we allow to be customized for debugging purposes
102
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
103
104
    self.bitcoinCash = options.network === "BCC";
105
    self.regtest = options.regtest;
106
    self.testnet = options.testnet;
107
    self.network = networkFromOptions(self);
108
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
109
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
110
111
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
112
113
    if (typeof options.btccom === "undefined") {
114
        options.btccom = true;
115
    }
116
117
    /**
118
     * @type RestClient
119
     */
120
    self.dataClient = APIClient.initRestClient(_.merge({}, options));
121
    /**
122
     * @type RestClient
123
     */
124
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
125
126
    if (options.btccom) {
127
        var rawTxHost = (options.testnet ? 'tchain.btc.com' : 'btc.com');
128
129
        self.converter = new BtccomConverter(self.network, true, rawTxHost);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
130
    } else {
131
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
132
    }
133
134
};
135
136
APIClient.normalizeNetworkFromOptions = function(options) {
137
    /* jshint -W071, -W074 */
138
    var network = 'BTC';
139
    var testnet = false;
140
    var regtest = false;
141
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
142
143
    var prefix;
144
    var done = false;
145
146
    if (options.network) {
147
        var lower = options.network.toLowerCase();
148
149
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
150
        if (!m) {
151
            throw new Error("Invalid network [" + options.network + "]");
152
        }
153
154
        if (m[2] === 'btc') {
155
            network = "BTC";
156
        } else {
157
            network = "BCC";
158
        }
159
160
        prefix = m[1];
161
        if (prefix) {
162
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
163
            done = true;
164
            if (prefix === 'r') {
165
                testnet = true;
166
                regtest = true;
167
            } else if (prefix === 't') {
168
                testnet = true;
169
            }
170
        }
171
    }
172
173
    // if we're not already done then apply options.regtest and options.testnet
174
    if (!done) {
175
        if (options.regtest) {
176
            testnet = true;
177
            regtest = true;
178
            prefix = "r";
179
        } else if (options.testnet) {
180
            testnet = true;
181
            prefix = "t";
182
        }
183
    }
184
185
    apiNetwork = (prefix || "") + network;
186
187
    return [network, testnet, regtest, apiNetwork];
188
};
189
190
APIClient.updateHostOptions = function(options) {
191
    /* jshint -W071, -W074 */
192
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
193
    if (!options.btccom && process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
194
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
195
    }
196
    if (options.btccom && process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT) {
197
        options.host = process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT;
198
    }
199
200
    if (options.btccom && process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM) {
201
        options.throttleRequestsTimeout = process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM;
202
    }
203
204
    // trim off leading https?://
205
    if (options.host && options.host.indexOf("https://") === 0) {
206
        options.https = true;
207
        options.host = options.host.substr(8);
208
    } else if (options.host && options.host.indexOf("http://") === 0) {
209
        options.https = false;
210
        options.host = options.host.substr(7);
211
    }
212
213
    if (typeof options.https === "undefined") {
214
        options.https = true;
215
    }
216
217
    if (!options.port) {
218
        options.port = options.https ? 443 : 80;
219
    }
220
221
    if (options.btccom) {
222
        if (!options.host) {
223
            options.host = 'chain.api.btc.com';
224
        }
225
226
        if (options.testnet && !options.host.match(/tchain/)) {
227
            options.host = options.host.replace(/chain/, 'tchain');
228
        }
229
230
        if (!options.endpoint) {
231
            options.endpoint = "/" + (options.apiVersion || "v3");
232
        }
233
    } else {
234
        if (!options.host) {
235
            options.host = 'api.blocktrail.com';
236
        }
237
238
        if (!options.endpoint) {
239
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
240
        }
241
    }
242
243
    return options;
244
};
245
246
APIClient.initRestClient = function(options) {
247
    options = APIClient.updateHostOptions(options);
248
    return new RestClient(options);
249
};
250
251
var determineDataStorageV2_3 = function(options) {
252
    return q.when(options)
253
        .then(function(options) {
254
            // legacy
255
            if (options.storePrimaryMnemonic) {
256
                options.storeDataOnServer = options.storePrimaryMnemonic;
257
            }
258
259
            // storeDataOnServer=false when primarySeed is provided
260
            if (typeof options.storeDataOnServer === "undefined") {
261
                options.storeDataOnServer = !options.primarySeed;
262
            }
263
264
            return options;
265
        });
266
};
267
268
var produceEncryptedDataV2 = function(options, notify) {
269
    return q.when(options)
270
        .then(function(options) {
271
            if (options.storeDataOnServer) {
272
                if (!options.secret) {
273
                    if (!options.passphrase) {
274
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
275
                    }
276
277
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
278
279
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
280
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
281
                }
282
283
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
284
285
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
286
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
287
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
288
289
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
290
291
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
292
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
293
            }
294
295
            return options;
296
        });
297
};
298
299
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
300
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
301
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
302
        var saltBuf = Encryption.generateSalt();
303
        var iv = Encryption.generateIV();
304
305
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
306
            return require('./webworker');
307
        }, onLoadWorkerLoadAsmCrypto, {
308
            method: 'Encryption.encryptWithSaltAndIV',
309
            pt: pt,
310
            pw: pw,
311
            saltBuf: saltBuf,
312
            iv: iv,
313
            iterations: iter
314
        })
315
            .then(function(data) {
316
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

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

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

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

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

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

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

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

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

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

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

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

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

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

Consider this little piece of code

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

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

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

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

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

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

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

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