Completed
Pull Request — master (#99)
by
unknown
01:22
created

APIClient.addressUnconfirmedTransactions   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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

1 Function

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