Completed
Pull Request — master (#100)
by thomas
01:11
created

api_client.js ➔ APIClient   B

Complexity

Conditions 3
Paths 9

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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