diff --git a/src/bitgo.js b/src/bitgo.js index e9da76d865..a1dd29c860 100644 --- a/src/bitgo.js +++ b/src/bitgo.js @@ -93,6 +93,10 @@ BitGo.prototype.decrypt = function(password, opaque) { return sjcl.decrypt(password, opaque); }; +BitGo.prototype.url = function(path) { + return this._baseUrl + path; +}; + // // market // Get the latest bitcoin prices. @@ -102,8 +106,7 @@ BitGo.prototype.market = function(callback) { throw new Error('invalid argument'); } - var url = this._baseUrl + '/market/latest'; - this.get(url) + this.get(this.url('/market/latest')) .end(function(err, res) { if (err) { return callback(err); @@ -127,13 +130,11 @@ BitGo.prototype.authenticate = function(username, password, otp, callback) { } var self = this; - var url = this._baseUrl + '/user/login'; - if (this._user) { return callback(new Error('already logged in')); } - this.post(url) + this.post(this.url('/user/login')) .send({email: username, password: password, otp: otp}) .end(function(err, res) { if (self.handleBitGoAPIError(err, res, callback)) { @@ -159,8 +160,7 @@ BitGo.prototype.logout = function(callback) { } var self = this; - var url = this._baseUrl + '/user/logout'; - this.get(url) + this.get(this.url('/user/login')) .send() .end(function(err, res) { delete self._user; @@ -182,9 +182,8 @@ BitGo.prototype.me = function(callback) { return callback(new Error('not authenticated')); } - var url = this._baseUrl + '/user/me'; var self = this; - this.get(url) + this.get(this.url('/user/me')) .send() .end(function(err, res) { if (self.handleBitGoAPIError(err, res, callback)) { @@ -242,6 +241,6 @@ BitGo.prototype.handleBitGoAPIError = function(err, res, callback) { var error = res.body.error ? res.body.error : res.status.toString(); callback({status: res.status, error: error, details: new Error(error)}); return true; -} +}; module.exports = BitGo; diff --git a/src/keychains.js b/src/keychains.js index ee9463376a..558f0f2dce 100644 --- a/src/keychains.js +++ b/src/keychains.js @@ -27,7 +27,7 @@ Keychains.prototype.isValid = function(string) { } catch (e) { return false; } -} +}; // // create @@ -60,9 +60,7 @@ Keychains.prototype.list = function(callback) { if (typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/keychains'; - this.bitgo.get(url) + this.bitgo.get(this.bitgo.url('/keychains')) .end(function(err, res) { if (err) { return callback(err); @@ -79,13 +77,10 @@ Keychains.prototype.add = function(options, callback) { if (typeof(options) != 'object' || typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/keychains'; - this.bitgo.post(url) + this.bitgo.post(this.bitgo.url('/keychains')) .send({ - label: options.label, xpub: options.xpub, - xprv: options.encryptedXprv + encryptedXprv: options.encryptedXprv }) .end(function(err, res) { if (err) { @@ -95,6 +90,24 @@ Keychains.prototype.add = function(options, callback) { }); }; +// +// addBitGo +// Add a new BitGo server keychain +// +Keychains.prototype.createBitGo = function(options, callback) { + if (typeof(options) != 'object' || typeof(callback) != 'function') { + throw new Error('invalid argument'); + } + this.bitgo.post(this.bitgo.url('/keychains/bitgo')) + .send({}) + .end(function(err, res) { + if (err) { + return callback(err); + } + callback(null, res.body); + }); +}; + // // get // Fetch an existing keychain @@ -107,10 +120,8 @@ Keychains.prototype.get = function(options, callback) { typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/keychains/' + options.xpub; var self = this; - this.bitgo.post(url) + this.bitgo.post(this.bitgo.url('/keychain/' + options.xpub)) .send({ otp: options.otp }) @@ -134,13 +145,10 @@ Keychains.prototype.update = function(options, callback) { typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/keychains/' + options.xpub; var self = this; - this.bitgo.put(url) + this.bitgo.put(this.bitgo.url('/keychain/' + options.xpub)) .send({ - label: options.label, - xprv: options.encryptedXprv, // TODO: This field should be renamed to encryptedXprv + encryptedXprv: options.encryptedXprv, otp: options.otp }) .end(function(err, res) { diff --git a/src/transactionbuilder.js b/src/transactionbuilder.js index de685d12d1..878c4f85f2 100644 --- a/src/transactionbuilder.js +++ b/src/transactionbuilder.js @@ -116,7 +116,7 @@ var TransactionBuilder = function(wallet, recipient, fee) { // Use the rough formula of 6 signatures per KB. var signaturesPerInput = 2; // 2-of-3 wallets return Math.ceil(_tx.ins.length * signaturesPerInput / 6) * FEE_PER_KB; - } + }; // Iterate _unspents, sum the inputs, and save _inputs with the total // input amound and final list of inputs to use with the transaction. @@ -227,7 +227,7 @@ var TransactionBuilder = function(wallet, recipient, fee) { // this.tx = function() { return Util.bytesToHex(_tx.serialize()); - } + }; }; diff --git a/src/wallet.js b/src/wallet.js index 10080c4e6d..b0057c47d3 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -15,9 +15,7 @@ var Wallet = function(bitgo, wallet) { this.wallet = wallet; this.keychains = []; if (wallet.private) { - this.keychains.push(wallet.private.userKeychain); - this.keychains.push(wallet.private.backupKeychain); - this.keychains.push(wallet.private.bitgoKeychain); + this.keychains = wallet.private.keychains; } }; @@ -27,7 +25,7 @@ var Wallet = function(bitgo, wallet) { // Wallet.prototype.address = function() { return this.wallet.id; -} +}; // // type @@ -35,7 +33,7 @@ Wallet.prototype.address = function() { // Wallet.prototype.type = function() { return this.wallet.type; -} +}; // @@ -44,7 +42,7 @@ Wallet.prototype.type = function() { // Wallet.prototype.label = function() { return this.wallet.label; -} +}; // // balance @@ -52,7 +50,7 @@ Wallet.prototype.label = function() { // Wallet.prototype.balance = function() { return this.wallet.balance; -} +}; // // pendingBalance @@ -60,7 +58,7 @@ Wallet.prototype.balance = function() { // Wallet.prototype.pendingBalance = function() { return this.wallet.pendingBalance; -} +}; // // availableBalance @@ -68,25 +66,25 @@ Wallet.prototype.pendingBalance = function() { // Wallet.prototype.availableBalance = function() { return this.wallet.availableBalance; -} +}; + +Wallet.prototype.url = function(extra) { + extra = extra || ''; + return this.bitgo.url('/wallet/' + this.address() + extra); +}; // // createAddress // Creates a new address for use with this wallet. -// Options include: -// internal: a flag if this should be an internal or external chain // Wallet.prototype.createAddress = function(options, callback) { if (typeof(options) != 'object' || typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/address/chain/' + this.type() + '/' + this.address(); + var chain = options.chain || 0; var self = this; - this.bitgo.post(url) - .send({ - internal: options.internal - }) + this.bitgo.post(this.url('/chain/' + chain)) + .send({}) .end(function(err, res) { if (self.bitgo.handleBitGoAPIError(err, res, callback)) { return; @@ -105,10 +103,8 @@ Wallet.prototype.delete = function(callback) { if (typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/addresses/' + this.type() + '/' + this.address(); var self = this; - this.bitgo.del(url) + this.bitgo.del(this.url()) .send() .end(function(err, res) { if (self.bitgo.handleBitGoAPIError(err, res, callback)) { @@ -128,18 +124,18 @@ Wallet.prototype.unspents = function(options, callback) { if (typeof(options) != 'object' || typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/transactions/unspents/' + this.type() + '/' + this.address(); + var url = this.url('/unspents'); if (options.btcLimit) { - if (typeof(options.btcLimit) != 'number') { + if (typeof(options.limit) != 'number') { throw new Error('invalid argument'); } - url += '?limit=' + (options.btcLimit * 1e8); + url += '?limit=' + (options.limit * 1e8); } var self = this; this.bitgo.get(url) .send() .end(function(err, res) { + console.log(res.body); if (self.bitgo.handleBitGoAPIError(err, res, callback)) { return; } @@ -156,10 +152,8 @@ Wallet.prototype.transactions = function(options, callback) { if (typeof(options) != 'object' || typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/transactions/' + this.type() + '/' + this.address(); var self = this; - this.bitgo.get(url) + this.bitgo.get(this.url('/tx')) .send() .end(function(err, res) { if (self.bitgo.handleBitGoAPIError(err, res, callback)) { @@ -187,7 +181,6 @@ Wallet.prototype.createTransaction = function(address, amount, fee, keychain, ca typeof(callback) != 'function') { throw new Error('invalid argument'); } - var tb = new TransactionBuilder(this, { address: address, amount: amount }, fee); tb.prepare() .then(function() { @@ -199,7 +192,7 @@ Wallet.prototype.createTransaction = function(address, amount, fee, keychain, ca .catch(function(e) { callback(e); }); -} +}; // // send @@ -212,10 +205,8 @@ Wallet.prototype.send = function(tx, callback) { if (typeof(tx) != 'string' || typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/transactions/' + this.type(); var self = this; - this.bitgo.post(url) + this.bitgo.post(this.bitgo.url('/tx/send')) .send({ tx: tx }) .end(function(err, res) { if (self.bitgo.handleBitGoAPIError(err, res, callback)) { @@ -223,6 +214,6 @@ Wallet.prototype.send = function(tx, callback) { } callback(null, { tx: res.body.transaction, hash: res.body.transactionHash }); }); -} +}; module.exports = Wallet; diff --git a/src/wallets.js b/src/wallets.js index bfc70547cc..b7f407f9ed 100644 --- a/src/wallets.js +++ b/src/wallets.js @@ -24,17 +24,15 @@ Wallets.prototype.list = function(callback) { if (typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/addresses'; var self = this; - this.bitgo.get(url) + this.bitgo.get(this.bitgo.url('/wallets')) .end(function(err, res) { if (err) { return callback(err); } var wallets = {}; - for (var address in res.body.addresses) { - wallets[address] = new Wallet(self.bitgo, res.body.addresses[address]); + for (var wallet in res.body.wallets) { + wallets[wallet] = new Wallet(self.bitgo, res.body.wallets[wallet]); } callback(null, wallets); }); @@ -71,18 +69,16 @@ Wallets.prototype.add = function(options, callback) { if (options.m != 2 || options.n != 3) { throw new Error('unsupported multi-sig type'); } - - var url = this.bitgo._baseUrl + '/addresses/bitcoin'; var self = this; - this.bitgo.post(url) + var keychains = options.keychains.map(function(k) { return {xpub: k.xpub}; }); + this.bitgo.post(this.bitgo.url('/wallets')) .send({ label: options.label, type: 'safehd', safehd: { m: options.m, n: options.n, - userKeychainXpub: options.keychains[0], - backupKeychainXpub: options.keychains[1] + keychains: keychains } }) .end(function(err, res) { @@ -106,12 +102,10 @@ Wallets.prototype.get = function(options, callback) { typeof(options.type) != 'string' || typeof(callback) != 'function') { throw new Error('invalid argument'); } - - var url = this.bitgo._baseUrl + '/addresses/' + options.type + '/' + options.address; var self = this; - this.bitgo.post(url) + this.bitgo.post(this.bitgo.url('/wallet/' + options.address)) .send({ - gpk: options.otp ? true : false, + gpk: options.gpk, otp: options.otp }) .end(function(err, res) { @@ -133,9 +127,7 @@ Wallets.prototype.get = function(options, callback) { // otp: the one-time-password for unlocking the private data of the wallet // Wallets.prototype.getWithPrivateInfo = function(options, callback) { - if (typeof(options) != 'object' || typeof(options.otp) != 'string') { - throw new Error('invalid argument'); - } + options.gpk = true; return this.get(options, callback); }; diff --git a/test/keychains.js b/test/keychains.js index 0d1b652c60..c462108599 100644 --- a/test/keychains.js +++ b/test/keychains.js @@ -95,14 +95,11 @@ describe('Keychains', function() { it('add', function(done) { var options = { - label: 'my keychain', xpub: extendedKey.xpub }; keychains.add(options, function(err, keychain) { assert.equal(err, null); assert.equal(keychain.xpub, extendedKey.xpub); - assert.equal(keychain.label, 'my keychain'); - assert.equal(keychain.index, 100); assert.equal(keychain.path, 'm'); done(); }); @@ -116,8 +113,6 @@ describe('Keychains', function() { keychains.get(options, function(err, keychain) { assert.equal(err, null); assert.equal(keychain.xpub, extendedKey.xpub); - assert.equal(keychain.label, 'my keychain'); - assert.equal(keychain.index, 100); assert.equal(keychain.path, 'm'); done(); }); @@ -134,15 +129,12 @@ describe('Keychains', function() { it('add', function(done) { var options = { - label: 'my keychain', xpub: extendedKey.xpub, - encryptedXprv: 'xyzzy' // TODO - add encryption! + encryptedXprv: 'xyzzy' }; keychains.add(options, function(err, keychain) { assert.equal(err, null); assert.equal(keychain.xpub, extendedKey.xpub); - assert.equal(keychain.label, 'my keychain'); - assert.equal(keychain.index, 100); assert.equal(keychain.path, 'm'); assert.equal(keychain.encryptedXprv, 'xyzzy'); done(); @@ -157,8 +149,6 @@ describe('Keychains', function() { keychains.get(options, function(err, keychain) { assert.equal(err, null); assert.equal(keychain.xpub, extendedKey.xpub); - assert.equal(keychain.label, 'my keychain'); - assert.equal(keychain.index, 100); assert.equal(keychain.path, 'm'); assert.equal(keychain.encryptedXprv, 'xyzzy'); done(); @@ -193,15 +183,12 @@ describe('Keychains', function() { it('update ', function(done) { var options = { - label: 'my keychain', xpub: newKey.xpub, otp: bitgo.testUserOTP() }; keychains.add(options, function(err, keychain) { assert.equal(err, null); assert.equal(keychain.xpub, newKey.xpub); - assert.equal(keychain.label, 'my keychain'); - assert.equal(keychain.index, 100); assert.equal(keychain.path, 'm'); options.label = 'new label'; @@ -210,11 +197,8 @@ describe('Keychains', function() { keychains.update(options, function(err, keychain) { assert.equal(err, null); assert.equal(keychain.xpub, newKey.xpub); - assert.equal(keychain.label, 'new label'); assert.equal(keychain.encryptedXprv, 'abracadabra'); - assert.equal(keychain.index, 100); assert.equal(keychain.path, 'm'); - done(); }); }); diff --git a/test/lib/test_bitgo.js b/test/lib/test_bitgo.js index 0a82e98595..2ec3ddc42c 100644 --- a/test/lib/test_bitgo.js +++ b/test/lib/test_bitgo.js @@ -8,6 +8,7 @@ var BitGo = require('../../src/bitgo.js'); BitGo.TEST_USER = 'mike+test@bitgo.com'; BitGo.TEST_PASSWORD = 'itestutestwetest'; +BitGo.TEST_PASSWORD_HMAC = 'ce897c2ded122c6cbacf9084691f1d6f9eb8433d6b9a655f2698947889333fe3'; // // testUserOTP diff --git a/test/npm-debug.log b/test/npm-debug.log new file mode 100644 index 0000000000..ed2f066530 --- /dev/null +++ b/test/npm-debug.log @@ -0,0 +1,17 @@ +0 info it worked if it ends with ok +1 verbose cli [ 'node', '/usr/local/bin/npm', 'test' ] +2 info using npm@1.4.9 +3 info using node@v0.10.28 +4 error Error: ENOENT, open '/Users/bpd/work/BitGoJS/test/package.json' +5 error If you need help, you may report this *entire* log, +5 error including the npm and node versions, at: +5 error +6 error System Darwin 13.1.0 +7 error command "node" "/usr/local/bin/npm" "test" +8 error cwd /Users/bpd/work/BitGoJS/test +9 error node -v v0.10.28 +10 error npm -v 1.4.9 +11 error path /Users/bpd/work/BitGoJS/test/package.json +12 error code ENOENT +13 error errno 34 +14 verbose exit [ 34, true ] diff --git a/test/wallet.js b/test/wallet.js index 53c7ce03c8..f654b357f0 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -37,7 +37,7 @@ describe('Wallet', function() { address: TEST_WALLET1_ADDRESS, otp: bitgo.testUserOTP() }; - wallets.get(options, function(err, wallet) { + wallets.getWithPrivateInfo(options, function(err, wallet) { if (err) { throw err; } @@ -49,7 +49,7 @@ describe('Wallet', function() { address: TEST_WALLET2_ADDRESS, otp: bitgo.testUserOTP() }; - wallets.get(options, function(err, wallet) { + wallets.getWithPrivateInfo(options, function(err, wallet) { wallet2 = wallet; done(); }); @@ -88,7 +88,7 @@ describe('Wallet', function() { }); it('list', function(done) { - var options = { btcLimit: 0.5 * 1e8 }; + var options = { limit: 0.5 * 1e8 }; wallet1.unspents(options, function(err, unspents) { assert.equal(err, null); assert.equal(Array.isArray(unspents), true); diff --git a/test/wallets.js b/test/wallets.js index 075b2627e4..6bcc7cf71f 100644 --- a/test/wallets.js +++ b/test/wallets.js @@ -43,12 +43,9 @@ describe('Wallets', function() { }); describe('Add', function() { - var numKeychains = 3; - before(function() { - for (var index = 0; index < numKeychains; ++index) { - keychains.push(bitgo.keychains().create()); - } + keychains.push(bitgo.keychains().create()); + keychains.push(bitgo.keychains().create()); }); it('arguments', function() { @@ -59,43 +56,47 @@ describe('Wallets', function() { it('wallet', function(done) { var options = { - label: 'user keychain', xpub: keychains[0].xpub, encryptedXprv: keychains[0].xprv }; bitgo.keychains().add(options, function(err, keychain) { assert.equal(err, null); assert.equal(keychain.xpub, keychains[0].xpub); + assert.equal(keychain.encryptedXprv, keychains[0].xprv); var options = { - label: 'backup keychain', xpub: keychains[1].xpub }; bitgo.keychains().add(options, function(err, keychain) { assert.equal(err, null); assert.equal(keychain.xpub, keychains[1].xpub); - var options = { - label: 'my wallet', - m: 2, - n: 3, - keychains: [ keychains[0].xpub, keychains[1].xpub ] - }; - wallets.add(options, function(err, wallet) { - assert.equal(err, null); - testWallet = wallet; - - assert.equal(wallet.balance(), 0); - assert.equal(wallet.label(), 'my wallet'); - assert.equal(wallet.pendingBalance(), 0); - assert.equal(wallet.availableBalance(), 0); - assert.equal(wallet.keychains.length, 3); - assert.equal(bitgo.keychains().isValid(wallet.keychains[0]), true); - assert.equal(bitgo.keychains().isValid(wallet.keychains[1]), true); - assert.equal(bitgo.keychains().isValid(wallet.keychains[2]), true); - assert.equal(wallet.keychains[0].xpub, keychains[0].xpub); - assert.equal(wallet.keychains[1].xpub, keychains[1].xpub); - done(); + bitgo.keychains().createBitGo({}, function(err, keychain) { + assert(keychain.xpub); + keychains.push(keychain); + + var options = { + label: 'my wallet', + m: 2, + n: 3, + keychains: keychains.map(function(k) { return {xpub: k.xpub}; }) + }; + wallets.add(options, function(err, wallet) { + assert.equal(err, null); + testWallet = wallet; + + assert.equal(wallet.balance(), 0); + assert.equal(wallet.label(), 'my wallet'); + assert.equal(wallet.pendingBalance(), 0); + assert.equal(wallet.availableBalance(), 0); + assert.equal(wallet.keychains.length, 3); + assert.equal(bitgo.keychains().isValid(wallet.keychains[0].xpub), true); + assert.equal(bitgo.keychains().isValid(wallet.keychains[1].xpub), true); + assert.equal(bitgo.keychains().isValid(wallet.keychains[2].xpub), true); + assert.equal(wallet.keychains[0].xpub, keychains[0].xpub); + assert.equal(wallet.keychains[1].xpub, keychains[1].xpub); + done(); + }); }); }); }); @@ -119,11 +120,7 @@ describe('Wallets', function() { otp: bitgo.testUserOTP() }; wallets.get(options, function(err, wallet) { - assert.equal(wallet.address(), options.address); - assert.equal(wallet.balance(), 0); - assert.equal(wallet.label(), ''); - assert.equal(wallet.pendingBalance(), 0); - assert.equal(wallet.availableBalance(), 0); + assert(!wallet); done(); }); });