Completed
Pull Request — master (#94)
by thomas
55s
created

lib/api_client.js   F

Complexity

Total Complexity 285
Complexity/F 2.04

Size

Lines of Code 2001
Function Count 140

Duplication

Duplicated Lines 212
Ratio 10.59 %

Importance

Changes 6
Bugs 1 Features 1
Metric Value
cc 0
wmc 285
nc 0
mnd 3
bc 264
fnc 140
dl 212
loc 2001
rs 2.4
bpm 1.8857
cpm 2.0357
noi 24
c 6
b 1
f 1

73 Functions

Rating   Name   Duplication   Size   Complexity  
A api_client.js ➔ determineDataStorageV2_3 0 16 1
C APIClient.resolveBackupPublicKeyFromOptions 0 38 8
A APIClient.promisedDecrypt 0 20 4
A APIClient.mnemonicToPrivateKey 0 14 1
A APIClient.addresses 0 5 1
A APIClient.addressUnspentOutputs 0 10 2
A APIClient.mnemonicToSeedHex 0 18 3
A APIClient.batchAddressUnspentOutputs 0 10 2
B api_client.js ➔ doRemainingWalletDataV2_3 0 25 1
A APIClient.address 0 5 1
A APIClient.setupWebhook 0 11 2
C APIClient.resolvePrimaryPrivateKeyFromOptions 0 58 11
A APIClient.batchAddressHasTransactions 0 10 2
C api_client.js ➔ APIClient 0 33 7
A APIClient.produceEncryptedDataV3 0 61 1
A APIClient.blockLatest 0 5 1
A APIClient.debugAuth 0 5 1
A APIClient.addressTransactions 0 10 2
A APIClient.verifyAddress 0 5 1
B api_client.js ➔ produceEncryptedDataV2 0 30 1
A APIClient.allWebhooks 0 10 2
A APIClient.block 0 5 1
F APIClient.initRestClient 0 33 12
A APIClient.blockTransactions 0 10 2
A APIClient.addressUnconfirmedTransactions 0 10 2
A APIClient.transaction 0 5 1
A APIClient.transactions 0 5 1
B APIClient.promisedEncrypt 0 27 4
A APIClient.allBlocks 0 10 2
A APIClient.sendRawTransaction 0 5 1
A APIClient.getWebhook 0 5 1
B APIClient.getCashAddressFromLegacyAddress 0 23 6
B APIClient._createNewWalletV2 107 108 1
A APIClient.storeNewWalletV1 0 19 1
A APIClient.doWalletDiscovery 0 5 1
A APIClient.unsubscribeNewBlocks 0 5 1
B APIClient.getLegacyBitcoinCashAddress 0 23 6
A APIClient.getWalletBalance 0 5 1
A APIClient.deleteWalletWebhook 0 5 1
A APIClient.setupWalletWebhook 0 5 1
C APIClient.sendTransaction 0 34 7
A APIClient.storeNewWalletV3 0 22 1
A APIClient.walletTransactions 0 10 2
A APIClient.getNewDerivation 0 5 1
A APIClient.price 0 5 1
B APIClient._createNewWalletV3 46 104 1
A APIClient.walletUTXOs 0 10 2
A APIClient.createNewWallet 0 65 3
A APIClient.subscribeAddressTransactions 0 10 1
C APIClient.coinSelection 0 46 7
A APIClient.feePerKB 0 10 1
A APIClient.walletAddresses 0 10 2
A APIClient.updateWallet 0 5 1
A api_client.js ➔ verifyPublicBip32Key 0 10 3
B APIClient.initWallet 0 80 5
A APIClient.unsubscribeTransaction 0 5 1
A APIClient.allWallets 0 10 2
A APIClient.deleteWallet 0 13 2
C APIClient.walletMaxSpendable 0 27 8
B APIClient._createNewWalletV1 57 111 1
A APIClient.updateWebhook 0 5 1
A APIClient.labelWalletAddress 0 5 1
A APIClient.verifyMessage 0 17 2
A APIClient.subscribeNewBlocks 0 8 1
A api_client.js ➔ verifyPublicOnly 0 4 1
A APIClient.getWebhookEvents 0 10 2
A APIClient.batchSubscribeAddressTransactions 0 8 1
A APIClient.subscribeTransaction 0 10 1
A APIClient.upgradeKeyIndex 0 8 1
A APIClient.storeNewWalletV2 0 22 1
A APIClient.unsubscribeAddressTransactions 0 5 1
A APIClient.deleteWebhook 0 5 1
A APIClient.faucetWithdrawl 0 5 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

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

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