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

api_client.js ➔ APIClient   D

Complexity

Conditions 8
Paths 641

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 8
c 2
b 1
f 0
nc 641
dl 0
loc 40
rs 4
nop 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
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
    /**
93
     * @type RestClient
94
     */
95
    self.client = APIClient.initRestClient(options);
96
97
    if (options.btccom) {
98
        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...
99
    } else {
100
        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...
101
    }
102
103
};
104
105
APIClient.initRestClient = function(options) {
106
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
107
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
108
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
109
    }
110
111
    // trim off leading https?://
112
    if (options.host && options.host.indexOf("https://") === 0) {
113
        options.https = true;
114
        options.host = options.host.substr(8);
115
    } else if (options.host && options.host.indexOf("http://") === 0) {
116
        options.https = false;
117
        options.host = options.host.substr(7);
118
    }
119
120
    if (typeof options.https === "undefined") {
121
        options.https = true;
122
    }
123
124
    if (!options.port) {
125
        options.port = options.https ? 443 : 80;
126
    }
127
128
    if (options.btccom) {
129
        if (!options.host) {
130
            options.host = 'chain.api.btc.com';
131
        }
132
133
        if (!options.endpoint) {
134
            options.endpoint = "/" + (options.apiVersion || "v3");
135
        }
136
137
    } else {
138
        if (!options.host) {
139
            options.host = 'api.blocktrail.com';
140
        }
141
142
        if (!options.endpoint) {
143
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
144
        }
145
    }
146
147
    return new RestClient(options);
148
};
149
150
var determineDataStorageV2_3 = function(options) {
151
    return q.when(options)
152
        .then(function(options) {
153
            // legacy
154
            if (options.storePrimaryMnemonic) {
155
                options.storeDataOnServer = options.storePrimaryMnemonic;
156
            }
157
158
            // storeDataOnServer=false when primarySeed is provided
159
            if (typeof options.storeDataOnServer === "undefined") {
160
                options.storeDataOnServer = !options.primarySeed;
161
            }
162
163
            return options;
164
        });
165
};
166
167
var produceEncryptedDataV2 = function(options, notify) {
168
    return q.when(options)
169
        .then(function(options) {
170
            if (options.storeDataOnServer) {
171
                if (!options.secret) {
172
                    if (!options.passphrase) {
173
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
174
                    }
175
176
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
177
178
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
179
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
180
                }
181
182
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
183
184
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
185
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
186
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
187
188
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
189
190
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
191
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
192
            }
193
194
            return options;
195
        });
196
};
197
198
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
199
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
200
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
201
        var saltBuf = Encryption.generateSalt();
202
        var iv = Encryption.generateIV();
203
204
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
205
            return require('./webworker');
206
        }, onLoadWorkerLoadAsmCrypto, {
207
            method: 'Encryption.encryptWithSaltAndIV',
208
            pt: pt,
209
            pw: pw,
210
            saltBuf: saltBuf,
211
            iv: iv,
212
            iterations: iter
213
        })
214
            .then(function(data) {
215
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

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

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

Loading history...
216
            });
217
    } else {
218
        try {
219
            return q.when(Encryption.encrypt(pt, pw, iter));
220
        } catch (e) {
221
            return q.reject(e);
222
        }
223
    }
224
};
225
226
APIClient.prototype.promisedDecrypt = function(ct, pw) {
227
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
228
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
229
            return require('./webworker');
230
        }, onLoadWorkerLoadAsmCrypto, {
231
            method: 'Encryption.decrypt',
232
            ct: ct,
233
            pw: pw
234
        })
235
            .then(function(data) {
236
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

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

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

Loading history...
237
            });
238
    } else {
239
        try {
240
            return q.when(Encryption.decrypt(ct, pw));
241
        } catch (e) {
242
            return q.reject(e);
243
        }
244
    }
245
};
246
247
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
248
    var self = this;
249
250
    return q.when(options)
251
        .then(function(options) {
252
            if (options.storeDataOnServer) {
253
                return q.when()
254
                    .then(function() {
255
                        if (!options.secret) {
256
                            if (!options.passphrase) {
257
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
258
                            }
259
260
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
261
262
                            // -> now a buffer
263
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
264
265
                            // -> now a buffer
266
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

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

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

Loading history...
267
                                .then(function(encryptedSecret) {
268
                                    options.encryptedSecret = encryptedSecret;
269
                                });
270
                        } else {
271
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

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

Consider this little piece of code

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

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

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

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

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

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

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

Loading history...
272
                                throw new Error('Secret must be a buffer');
273
                            }
274
                        }
275
                    })
276
                    .then(function() {
277
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
278
279
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
280
                            .then(function(encryptedPrimarySeed) {
281
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
282
                            });
283
                    })
284
                    .then(function() {
285
                        // skip generating recovery secret when explicitly set to false
286
                        if (options.recoverySecret === false) {
287
                            return;
288
                        }
289
290
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
291
                        if (!options.recoverySecret) {
292
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
293
                        }
294
295
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
296
                            .then(function(recoveryEncryptedSecret) {
297
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
298
                            });
299
                    })
300
                    .then(function() {
301
                        return options;
302
                    });
303
            } else {
304
                return options;
305
            }
306
        });
307
};
308
309
var doRemainingWalletDataV2_3 = function(options, network, notify) {
310
    return q.when(options)
311
        .then(function(options) {
312
            if (!options.backupPublicKey) {
313
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
314
            }
315
316
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
317
318
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
319
320
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
321
322
            if (!options.backupPublicKey) {
323
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
324
                options.backupPublicKey = options.backupPrivateKey.neutered();
325
            }
326
327
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
328
329
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
330
331
            return options;
332
        });
333
};
334
335
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
336
    var self = this;
337
338
    var deferred = q.defer();
339
    deferred.promise.spreadNodeify(cb);
340
341
    deferred.resolve(q.fcall(function() {
342
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
343
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
344
        });
345
    }));
346
347
    return deferred.promise;
348
};
349
350
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
351
    var self = this;
352
353
    if (useWebWorker) {
354
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
355
            return require('./webworker');
356
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
357
            .then(function(data) {
358
                return data.seed;
359
            });
360
    } else {
361
        try {
362
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
363
        } catch (e) {
364
            return q.reject(e);
365
        }
366
    }
367
};
368
369
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
370
    var self = this;
371
372
    var deferred = q.defer();
373
    deferred.promise.nodeify(cb);
374
375
    try {
376
        // avoid conflicting options
377
        if (options.passphrase && options.password) {
378
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
379
        }
380
        // normalize passphrase/password
381
        options.passphrase = options.passphrase || options.password;
382
        delete options.password;
383
384
        // avoid conflicting options
385
        if (options.primaryMnemonic && options.primarySeed) {
386
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
387
        }
388
389
        // avoid deprecated options
390
        if (options.primaryPrivateKey) {
391
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
392
        }
393
394
        // make sure we have at least one thing to use
395
        if (!options.primaryMnemonic && !options.primarySeed) {
396
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
397
        }
398
399
        if (options.primarySeed) {
400
            self.primarySeed = options.primarySeed;
401
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
402
            deferred.resolve(options);
403
        } else {
404
            if (!options.passphrase) {
405
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
406
            }
407
408
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
409
                .then(function(seedHex) {
410
                    try {
411
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

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

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

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