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

APIClient.getLegacyBitcoinCashAddress   C

Complexity

Conditions 7
Paths 3

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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