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

APIClient.addressTransactions   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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

1 Function

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

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

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

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

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

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

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

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

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

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

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

Consider this little piece of code

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

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

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

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

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

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

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

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

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

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

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