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

api_client.js ➔ APIClient   B

Complexity

Conditions 6
Paths 33

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

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