Completed
Pull Request — master (#97)
by Ruben de
01:17 queued 19s
created

APIClient.normalizeNetworkFromOptions   C

Complexity

Conditions 10
Paths 72

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
c 0
b 0
f 0
nc 72
dl 0
loc 49
rs 5.5471
nop 1

How to fix   Complexity   

Complexity

Complex classes like APIClient.normalizeNetworkFromOptions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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