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

APIClient.addressUnconfirmedTransactions   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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

1 Function

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