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

APIClient.transaction   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
c 2
b 0
f 0
nc 1
dl 0
loc 31
rs 8.8571
nop 2

1 Function

Rating   Name   Duplication   Size   Complexity  
A 0 2 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
var useWebWorker = require('./use-webworker')();
22
23
24
/**
25
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
26
 *
27
 * @param promise   {q.Promise}
28
 * @param cb        function
29
 * @return q.Promise
30
 */
31
function callbackify(promise, cb) {
32
    // add a .then to trigger the cb for people using callbacks
33
    if (cb) {
34
        promise
35
            .then(function(res) {
36
                // use q.nextTick for asyncness
37
                q.nextTick(function() {
38
                    cb(null, res);
39
                });
40
            }, function(err) {
41
                // use q.nextTick for asyncness
42
                q.nextTick(function() {
43
                    cb(err, null);
44
                });
45
            });
46
    }
47
48
    // return the promise for people using promises
49
    return promise;
50
}
51
52
/**
53
 * Bindings to consume the BlockTrail API
54
 *
55
 * @param options       object{
56
 *                          apiKey: 'API_KEY',
57
 *                          apiSecret: 'API_SECRET',
58
 *                          host: 'defaults to api.blocktrail.com',
59
 *                          network: 'BTC|LTC',
60
 *                          testnet: true|false
61
 *                      }
62
 * @constructor
63
 */
64
var APIClient = function(options) {
65
    var self = this;
66
67
    // handle constructor call without 'new'
68
    if (!(this instanceof APIClient)) {
69
        return new APIClient(options);
70
    }
71
    self.bitcoinCash = options.network && options.network === "BCC";
72
    self.testnet = options.testnet = options.testnet || false;
73
    if (self.bitcoinCash) {
74
        if (self.testnet) {
75
            self.network = bitcoin.networks.bitcoincashtestnet;
76
        } else {
77
            self.network = bitcoin.networks.bitcoincash;
78
        }
79
    } else {
80
        if (self.testnet) {
81
            self.network = bitcoin.networks.testnet;
82
        } else {
83
            self.network = bitcoin.networks.bitcoin;
84
        }
85
    }
86
87
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
88
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
89
90
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
91
92
    if (typeof options.btccom === "undefined") {
93
        options.btccom = true;
94
    }
95
96
    /**
97
     * @type RestClient
98
     */
99
    self.client = APIClient.initRestClient(options);
100
101
    if (options.btccom) {
102
        self.converter = new BtccomConverter(self.network, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
103
    } else {
104
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
105
    }
106
107
};
108
109
APIClient.initRestClient = function(options) {
110
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
111
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
112
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
113
    }
114
115
    // trim off leading https?://
116
    if (options.host && options.host.indexOf("https://") === 0) {
117
        options.https = true;
118
        options.host = options.host.substr(8);
119
    } else if (options.host && options.host.indexOf("http://") === 0) {
120
        options.https = false;
121
        options.host = options.host.substr(7);
122
    }
123
124
    if (typeof options.https === "undefined") {
125
        options.https = true;
126
    }
127
128
    if (!options.port) {
129
        options.port = options.https ? 443 : 80;
130
    }
131
132
    if (options.btccom) {
133
        if (!options.host) {
134
            options.host = 'chain.api.btc.com';
135
        }
136
137
        if (!options.endpoint) {
138
            options.endpoint = "/" + (options.apiVersion || "v3");
139
        }
140
141
    } else {
142
        if (!options.host) {
143
            options.host = 'api.blocktrail.com';
144
        }
145
146
        if (!options.endpoint) {
147
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
148
        }
149
    }
150
151
    return new RestClient(options);
152
};
153
154
var determineDataStorageV2_3 = function(options) {
155
    return q.when(options)
156
        .then(function(options) {
157
            // legacy
158
            if (options.storePrimaryMnemonic) {
159
                options.storeDataOnServer = options.storePrimaryMnemonic;
160
            }
161
162
            // storeDataOnServer=false when primarySeed is provided
163
            if (typeof options.storeDataOnServer === "undefined") {
164
                options.storeDataOnServer = !options.primarySeed;
165
            }
166
167
            return options;
168
        });
169
};
170
171
var produceEncryptedDataV2 = function(options, notify) {
172
    return q.when(options)
173
        .then(function(options) {
174
            if (options.storeDataOnServer) {
175
                if (!options.secret) {
176
                    if (!options.passphrase) {
177
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
178
                    }
179
180
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
181
182
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
183
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
184
                }
185
186
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
187
188
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
189
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
190
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
191
192
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
193
194
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
195
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
196
            }
197
198
            return options;
199
        });
200
};
201
202
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
203
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
204
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
205
        var saltBuf = Encryption.generateSalt();
206
        var iv = Encryption.generateIV();
207
208
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
209
            return require('./webworker');
210
        }, onLoadWorkerLoadAsmCrypto, {
211
            method: 'Encryption.encryptWithSaltAndIV',
212
            pt: pt,
213
            pw: pw,
214
            saltBuf: saltBuf,
215
            iv: iv,
216
            iterations: iter
217
        })
218
            .then(function(data) {
219
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

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

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

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

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

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

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

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

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

Loading history...
271
                                .then(function(encryptedSecret) {
272
                                    options.encryptedSecret = encryptedSecret;
273
                                });
274
                        } else {
275
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

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

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

Loading history...
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

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

Consider this little piece of code

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

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

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

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

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

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

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

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