commit
3e6f1cfebe
9 changed files with 837 additions and 0 deletions
@ -0,0 +1,30 @@ |
|||||
|
# Logs |
||||
|
logs |
||||
|
*.log |
||||
|
|
||||
|
# Runtime data |
||||
|
pids |
||||
|
*.pid |
||||
|
*.seed |
||||
|
|
||||
|
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
|
lib-cov |
||||
|
|
||||
|
# Coverage directory used by tools like istanbul |
||||
|
coverage |
||||
|
|
||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) |
||||
|
.grunt |
||||
|
|
||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html) |
||||
|
build/Release |
||||
|
|
||||
|
# Dependency directory |
||||
|
# Commenting this out is preferred by some people, see |
||||
|
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- |
||||
|
node_modules |
||||
|
|
||||
|
# Users Environment Variables |
||||
|
.lock-wscript |
||||
|
|
||||
|
*.swp |
@ -0,0 +1,33 @@ |
|||||
|
var _ = require('lodash'); |
||||
|
|
||||
|
var locks = {}; |
||||
|
|
||||
|
var Lock = function () { |
||||
|
this.taken = false; |
||||
|
this.queue = []; |
||||
|
}; |
||||
|
|
||||
|
Lock.prototype.free = function () { |
||||
|
if (this.queue.length > 0) { |
||||
|
var f = this.queue.shift(); |
||||
|
f(this); |
||||
|
} else { |
||||
|
this.taken = false; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
Lock.get = function (key, callback) { |
||||
|
if (_.isUndefined(locks[key])) { |
||||
|
locks[key] = new Lock(); |
||||
|
} |
||||
|
var lock = locks[key]; |
||||
|
|
||||
|
if (lock.taken) { |
||||
|
lock.queue.push(callback); |
||||
|
} else { |
||||
|
lock.taken = true; |
||||
|
callback(lock); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
module.exports = Lock; |
@ -0,0 +1,18 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
function Address(opts) { |
||||
|
opts = opts || {}; |
||||
|
|
||||
|
this.address = opts.address; |
||||
|
this.path = opts.path; |
||||
|
}; |
||||
|
|
||||
|
Address.fromObj = function (obj) { |
||||
|
var x = new Address(); |
||||
|
|
||||
|
x.address = obj.address; |
||||
|
x.path = obj.path; |
||||
|
return x; |
||||
|
}; |
||||
|
|
||||
|
module.exports = Address; |
@ -0,0 +1,27 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var _ = require('lodash'); |
||||
|
|
||||
|
function Copayer(opts) { |
||||
|
opts = opts || {}; |
||||
|
|
||||
|
this.walletId = opts.walletId; |
||||
|
this.id = opts.id; |
||||
|
this.name = opts.name; |
||||
|
this.xPubKey = opts.xPubKey; |
||||
|
this.xPubKeySignature = opts.xPubKeySignature; |
||||
|
}; |
||||
|
|
||||
|
Copayer.fromObj = function (obj) { |
||||
|
var x = new Copayer(); |
||||
|
|
||||
|
x.walletId = obj.walletId; |
||||
|
x.id = obj.id; |
||||
|
x.name = obj.name; |
||||
|
x.xPubKey = obj.xPubKey; |
||||
|
x.xPubKeySignature = obj.xPubKeySignature; |
||||
|
return x; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
module.exports = Copayer; |
@ -0,0 +1,28 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var _ = require('lodash'); |
||||
|
|
||||
|
function Wallet(opts) { |
||||
|
opts = opts || {}; |
||||
|
|
||||
|
this.id = opts.id; |
||||
|
this.name = opts.name; |
||||
|
this.m = opts.m; |
||||
|
this.n = opts.n; |
||||
|
this.status = 'pending'; |
||||
|
this.publicKeyRing = []; |
||||
|
}; |
||||
|
|
||||
|
Wallet.fromObj = function (obj) { |
||||
|
var x = new Wallet(); |
||||
|
|
||||
|
x.id = obj.id; |
||||
|
x.name = obj.name; |
||||
|
x.m = obj.m; |
||||
|
x.n = obj.n; |
||||
|
x.status = obj.status; |
||||
|
x.publicKeyRing = obj.publicKeyRing; |
||||
|
return x; |
||||
|
}; |
||||
|
|
||||
|
module.exports = Wallet; |
@ -0,0 +1,235 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var _ = require('lodash'); |
||||
|
var $ = require('preconditions').singleton(); |
||||
|
var async = require('async'); |
||||
|
var log = require('npmlog'); |
||||
|
log.debug = log.verbose; |
||||
|
|
||||
|
var Lock = require('./lock'); |
||||
|
var Storage = require('./storage'); |
||||
|
var Wallet = require('./model/wallet'); |
||||
|
var Copayer = require('./model/copayer'); |
||||
|
|
||||
|
function CopayServer(opts) { |
||||
|
opts = opts || {}; |
||||
|
this.storage = new Storage(opts); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
CopayServer.prototype.createWallet = function (opts, cb) { |
||||
|
var self = this; |
||||
|
|
||||
|
self.getWallet({ id: opts.id }, function (err, wallet) { |
||||
|
if (err) return cb(err); |
||||
|
if (wallet) return cb('Wallet already exists'); |
||||
|
|
||||
|
var wallet = new Wallet({ |
||||
|
id: opts.id, |
||||
|
name: opts.name, |
||||
|
m: opts.m, |
||||
|
n: opts.n, |
||||
|
network: opts.network || 'livenet', |
||||
|
pubKey: opts.pubKey, |
||||
|
}); |
||||
|
|
||||
|
self.storage.storeWallet(wallet, cb); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
CopayServer.prototype.getWallet = function (opts, cb) { |
||||
|
var self = this; |
||||
|
|
||||
|
self.storage.fetchWallet(opts.id, function (err, wallet) { |
||||
|
if (err || !wallet) return cb(err); |
||||
|
if (opts.includeCopayers) { |
||||
|
self.storage.fetchCopayers(wallet.id, function (err, copayers) { |
||||
|
if (err) return cb(err); |
||||
|
wallet.copayers = copayers || []; |
||||
|
return cb(null, wallet); |
||||
|
}); |
||||
|
} else { |
||||
|
return cb(null, wallet); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
CopayServer.prototype.joinWallet = function (opts, cb) { |
||||
|
var self = this; |
||||
|
|
||||
|
Lock.get(opts.walletId, function (lock) { |
||||
|
var _cb = function (err, res) { |
||||
|
cb(err, res); |
||||
|
lock.free(); |
||||
|
}; |
||||
|
|
||||
|
self.getWallet({ id: opts.walletId, includeCopayers: true }, function (err, wallet) { |
||||
|
if (err) return _cb(err); |
||||
|
if (!wallet) return _cb('Wallet not found'); |
||||
|
if (_.find(wallet.copayers, { xPubKey: opts.xPubKey })) return _cb('Copayer already in wallet'); |
||||
|
if (wallet.copayers.length == wallet.n) return _cb('Wallet full'); |
||||
|
|
||||
|
// TODO: validate copayer's extended public key using the public key from this wallet
|
||||
|
// Note: use Bitcore.crypto.ecdsa .verify()
|
||||
|
|
||||
|
var copayer = new Copayer({ |
||||
|
walletId: wallet.id, |
||||
|
id: opts.id, |
||||
|
name: opts.name, |
||||
|
xPubKey: opts.xPubKey, |
||||
|
xPubKeySignature: opts.xPubKeySignature, |
||||
|
}); |
||||
|
|
||||
|
self.storage.storeCopayer(copayer, function (err) { |
||||
|
if (err) return _cb(err); |
||||
|
if ((wallet.copayers.length + 1) < wallet.n) return _cb(); |
||||
|
|
||||
|
wallet.status = 'complete'; |
||||
|
wallet.publicKeyRing = _.pluck(wallet.copayers, 'xPubKey'); |
||||
|
wallet.publicKeyRing.push(copayer.xPubKey); |
||||
|
self.storage.storeWallet(wallet, _cb); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
CopayServer.prototype._doCreateAddress = function (pkr, isChange) { |
||||
|
throw 'not implemented'; |
||||
|
}; |
||||
|
|
||||
|
// opts = {
|
||||
|
// walletId,
|
||||
|
// isChange,
|
||||
|
// };
|
||||
|
CopayServer.prototype.createAddress = function (opts, cb) { |
||||
|
var self = this; |
||||
|
|
||||
|
self.getWallet({ id: opts.walletId }, function (err, wallet) { |
||||
|
if (err) return cb(err); |
||||
|
if (!wallet) return cb('Wallet not found'); |
||||
|
|
||||
|
var address = self._doCreateAddress(wallet.publicKeyRing, opts.isChange); |
||||
|
return cb(null, address); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
CopayServer.prototype._verifyMessageSignature = function (copayerId, message, signature) { |
||||
|
throw 'not implemented'; |
||||
|
}; |
||||
|
|
||||
|
CopayServer.prototype._getBlockExplorer = function (provider, network) { |
||||
|
var url; |
||||
|
|
||||
|
switch (provider) { |
||||
|
default: |
||||
|
case 'insight': |
||||
|
switch (network) { |
||||
|
default: |
||||
|
case 'livenet': |
||||
|
url = 'https://insight.bitpay.com:443'; |
||||
|
break; |
||||
|
case 'testnet': |
||||
|
url = 'https://test-insight.bitpay.com:443' |
||||
|
break; |
||||
|
} |
||||
|
return new Bitcore.Insight(url, network); |
||||
|
break; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
CopayServer.prototype._getUtxos = function (opts, cb) { |
||||
|
var self = this; |
||||
|
|
||||
|
// Get addresses for this wallet
|
||||
|
self.storage.getAddresses(opts.walletId, function (err, addresses) { |
||||
|
if (err) return cb(err); |
||||
|
if (addresses.length == 0) return cb('The wallet has no addresses'); |
||||
|
|
||||
|
var addresses = _.pluck(addresses, 'address'); |
||||
|
|
||||
|
var bc = _getBlockExplorer('insight', opts.network); |
||||
|
bc.getUnspentUtxos(addresses, function (err, utxos) { |
||||
|
if (err) return cb(err); |
||||
|
|
||||
|
// TODO: filter 'locked' utxos
|
||||
|
|
||||
|
return cb(null, utxos); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
CopayServer.prototype._doCreateTx = function (opts, cb) { |
||||
|
var tx = new Bitcore.Transaction() |
||||
|
.from(opts.utxos) |
||||
|
.to(opts.toAddress, opts.amount) |
||||
|
.change(opts.changeAddress); |
||||
|
|
||||
|
return tx; |
||||
|
}; |
||||
|
|
||||
|
// opts = {
|
||||
|
// copayerId,
|
||||
|
// walletId,
|
||||
|
// toAddress,
|
||||
|
// amount, // in Satoshi
|
||||
|
// message,
|
||||
|
// otToken, // one time random token generated by the client and signed to avoid replay attacks
|
||||
|
// utxos: [], // optional (not yet implemented)
|
||||
|
// requestSignature, // S(toAddress + amount + otToken) using this copayers privKey
|
||||
|
// // using this signature, the server can
|
||||
|
// };
|
||||
|
|
||||
|
// result = {
|
||||
|
// ntxid,
|
||||
|
// rawTx,
|
||||
|
// };
|
||||
|
|
||||
|
CopayServer.prototype.createTx = function (opts, cb) { |
||||
|
// Client generates a unique token and signs toAddress + amount + token.
|
||||
|
// This way we authenticate + avoid replay attacks.
|
||||
|
var self = this; |
||||
|
|
||||
|
self.getWallet({ id: opts.walletId }, function (err, wallet) { |
||||
|
if (err) return cb(err); |
||||
|
if (!wallet) return cb('Wallet not found'); |
||||
|
|
||||
|
var msg = '' + opts.toAddress + opts.amount + opts.otToken; |
||||
|
if (!self._verifyMessageSignature(opts.copayerId, msg, opts.requestSignature)) return cb('Invalid request'); |
||||
|
|
||||
|
|
||||
|
var txArgs = { |
||||
|
toAddress: opts.toAddress, |
||||
|
amount: opts.amount, |
||||
|
changeAddress: opts.changeAddress, |
||||
|
}; |
||||
|
|
||||
|
self._getUtxos({ walletId: wallet.id }, function (err, utxos) { |
||||
|
if (err) return cb('Could not retrieve UTXOs'); |
||||
|
txArgs.utxos = utxos; |
||||
|
self._doCreateTx(txArgs, function (err, tx) { |
||||
|
if (err) return cb('Could not create transaction'); |
||||
|
|
||||
|
self.storage.storeTx(tx, function (err) { |
||||
|
if (err) return cb(err); |
||||
|
|
||||
|
return cb(null, { |
||||
|
ntxid: tx.ntxid, |
||||
|
rawTx: tx.raw, |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
CopayServer.prototype.getPendingTxs = function (opts, cb) { |
||||
|
var self = this; |
||||
|
|
||||
|
//self.storage.get
|
||||
|
}; |
||||
|
|
||||
|
|
||||
|
module.exports = CopayServer; |
@ -0,0 +1,77 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var _ = require('lodash'); |
||||
|
var levelup = require('levelup'); |
||||
|
var $ = require('preconditions').singleton(); |
||||
|
var async = require('async'); |
||||
|
var log = require('npmlog'); |
||||
|
log.debug = log.verbose; |
||||
|
|
||||
|
var Wallet = require('./model/wallet'); |
||||
|
var Copayer = require('./model/copayer'); |
||||
|
var Address = require('./model/address'); |
||||
|
|
||||
|
var Storage = function (opts) { |
||||
|
opts = opts || {}; |
||||
|
this.db = opts.db || levelup(opts.dbPath || './db/copay.db', { valueEncoding: 'json' }); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
Storage.prototype.fetchWallet = function (id, cb) { |
||||
|
this.db.get('wallet-' + id, function (err, data) { |
||||
|
if (err) { |
||||
|
if (err.notFound) return cb(); |
||||
|
return cb(err); |
||||
|
} |
||||
|
return cb(null, Wallet.fromObj(data)); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Storage.prototype.fetchCopayers = function (walletId, cb) { |
||||
|
var copayers = []; |
||||
|
var key = 'wallet-' + walletId + '-copayer-'; |
||||
|
this.db.createReadStream({ gte: key, lt: key + '~' }) |
||||
|
.on('data', function (data) { |
||||
|
copayers.push(Copayer.fromObj(data.value)); |
||||
|
}) |
||||
|
.on('error', function (err) { |
||||
|
if (err.notFound) return cb(); |
||||
|
return cb(err); |
||||
|
}) |
||||
|
.on('end', function () { |
||||
|
return cb(null, copayers); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
Storage.prototype.storeWallet = function (wallet, cb) { |
||||
|
this.db.put('wallet-' + wallet.id, wallet, cb); |
||||
|
}; |
||||
|
|
||||
|
Storage.prototype.storeCopayer = function (copayer, cb) { |
||||
|
this.db.put('wallet-' + copayer.walletId + '-copayer-' + copayer.id, copayer, cb); |
||||
|
}; |
||||
|
|
||||
|
Storage.prototype.getAddresses = function (walletId, cb) { |
||||
|
var addresses = []; |
||||
|
var key = 'wallet-' + walletId + '-address-'; |
||||
|
this.db.createReadStream({ gte: key, lt: key + '~' }) |
||||
|
.on('data', function (data) { |
||||
|
addresses.push(Address.fromObj(data.value)); |
||||
|
}) |
||||
|
.on('error', function (err) { |
||||
|
if (err.notFound) return cb(); |
||||
|
return cb(err); |
||||
|
}) |
||||
|
.on('end', function () { |
||||
|
return cb(null, addresses); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
Storage.prototype._dump = function (cb) { |
||||
|
this.db.readStream() |
||||
|
.on('data', console.log) |
||||
|
.on('end', function () { if (cb) return cb(); }); |
||||
|
}; |
||||
|
|
||||
|
module.exports = Storage; |
@ -0,0 +1,38 @@ |
|||||
|
{ |
||||
|
"name": "copay-server", |
||||
|
"description": "Copay server", |
||||
|
"author": "isocolsky", |
||||
|
"version": "0.0.1", |
||||
|
"keywords": [ |
||||
|
"bitcoin", |
||||
|
"copay", |
||||
|
"multisig", |
||||
|
"wallet" |
||||
|
], |
||||
|
"repository": { |
||||
|
"url": "git@github.com:isocolsky/copay-lib.git", |
||||
|
"type": "git" |
||||
|
}, |
||||
|
"bugs": { |
||||
|
"url": "https://github.com/isocolsky/copay-lib/issues" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"bitcore": "^0.8.6", |
||||
|
"async": "^0.9.0", |
||||
|
"lodash": "^2.4.1", |
||||
|
"preconditions": "^1.0.7", |
||||
|
"express": "^4.10.0", |
||||
|
"leveldown": "^0.10.0", |
||||
|
"levelup": "^0.19.0", |
||||
|
"npmlog": "^0.1.1" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"chai": "^1.9.1", |
||||
|
"mocha": "^1.18.2", |
||||
|
"sinon": "^1.10.3", |
||||
|
"memdown": "^1.0.0" |
||||
|
}, |
||||
|
"scripts": { |
||||
|
"start": "node server.js" |
||||
|
} |
||||
|
} |
@ -0,0 +1,351 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var _ = require('lodash'); |
||||
|
var async = require('async'); |
||||
|
|
||||
|
var chai = require('chai'); |
||||
|
var sinon = require('sinon'); |
||||
|
var should = chai.should(); |
||||
|
var levelup = require('levelup'); |
||||
|
var memdown = require('memdown'); |
||||
|
|
||||
|
var Wallet = require('../lib/model/wallet'); |
||||
|
var Copayer = require('../lib/model/copayer'); |
||||
|
var CopayServer = require('../lib/server'); |
||||
|
|
||||
|
var db; |
||||
|
var server; |
||||
|
|
||||
|
describe('Copay server', function() { |
||||
|
beforeEach(function() { |
||||
|
db = levelup(memdown, { valueEncoding: 'json' }); |
||||
|
}); |
||||
|
|
||||
|
describe('#getWallet', function() { |
||||
|
beforeEach(function() { |
||||
|
server = new CopayServer({ |
||||
|
db: db, |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should get existing wallet', function (done) { |
||||
|
var w1 = new Wallet({ |
||||
|
id: '123', |
||||
|
name: 'my wallet', |
||||
|
m: 2, |
||||
|
n: 3, |
||||
|
pubKey: 'dummy', |
||||
|
}); |
||||
|
var w2 = new Wallet({ |
||||
|
id: '234', |
||||
|
name: 'my wallet 2', |
||||
|
m: 3, |
||||
|
n: 4, |
||||
|
pubKey: 'dummy', |
||||
|
}); |
||||
|
|
||||
|
db.batch([{ |
||||
|
type: 'put', |
||||
|
key: 'wallet-123', |
||||
|
value: w1, |
||||
|
}, { |
||||
|
type: 'put', |
||||
|
key: 'wallet-234', |
||||
|
value: w2, |
||||
|
}]); |
||||
|
|
||||
|
server.getWallet({ id: '123', includeCopayers: true }, function (err, wallet) { |
||||
|
should.not.exist(err); |
||||
|
wallet.id.should.equal('123'); |
||||
|
wallet.name.should.equal('my wallet'); |
||||
|
wallet.status.should.equal('pending'); |
||||
|
wallet.copayers.length.should.equal(0); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should return undefined when requesting non-existent wallet', function (done) { |
||||
|
var w1 = new Wallet({ |
||||
|
id: '123', |
||||
|
name: 'my wallet', |
||||
|
m: 2, |
||||
|
n: 3, |
||||
|
pubKey: 'dummy', |
||||
|
}); |
||||
|
var w2 = new Wallet({ |
||||
|
id: '234', |
||||
|
name: 'my wallet 2', |
||||
|
m: 3, |
||||
|
n: 4, |
||||
|
pubKey: 'dummy', |
||||
|
}); |
||||
|
|
||||
|
db.batch([{ |
||||
|
type: 'put', |
||||
|
key: 'wallet-123', |
||||
|
value: w1, |
||||
|
}, { |
||||
|
type: 'put', |
||||
|
key: 'wallet-234', |
||||
|
value: w2, |
||||
|
}]); |
||||
|
|
||||
|
server.getWallet({ id: '345' }, function (err, wallet) { |
||||
|
should.not.exist(err); |
||||
|
should.not.exist(wallet); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('#createWallet', function() { |
||||
|
beforeEach(function() { |
||||
|
server = new CopayServer({ |
||||
|
db: db, |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should create and store wallet', function(done) { |
||||
|
var opts = { |
||||
|
id: '123', |
||||
|
name: 'my wallet', |
||||
|
m: 2, |
||||
|
n: 3, |
||||
|
pubKey: 'dummy', |
||||
|
}; |
||||
|
server.createWallet(opts, function(err) { |
||||
|
should.not.exist(err); |
||||
|
server.getWallet({ id: '123' }, function (err, wallet) { |
||||
|
should.not.exist(err); |
||||
|
wallet.id.should.equal('123'); |
||||
|
wallet.name.should.equal('my wallet'); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should fail to recreate existing wallet', function(done) { |
||||
|
var opts = { |
||||
|
id: '123', |
||||
|
name: 'my wallet', |
||||
|
m: 2, |
||||
|
n: 3, |
||||
|
pubKey: 'dummy', |
||||
|
}; |
||||
|
server.createWallet(opts, function(err) { |
||||
|
should.not.exist(err); |
||||
|
server.getWallet({ id: '123' }, function (err, wallet) { |
||||
|
should.not.exist(err); |
||||
|
wallet.id.should.equal('123'); |
||||
|
wallet.name.should.equal('my wallet'); |
||||
|
server.createWallet(opts, function(err) { |
||||
|
should.exist(err); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('#joinWallet', function() { |
||||
|
beforeEach(function() { |
||||
|
server = new CopayServer({ |
||||
|
db: db, |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should join existing wallet', function (done) { |
||||
|
var walletOpts = { |
||||
|
id: '123', |
||||
|
name: 'my wallet', |
||||
|
m: 2, |
||||
|
n: 3, |
||||
|
pubKey: 'dummy', |
||||
|
}; |
||||
|
server.createWallet(walletOpts, function(err) { |
||||
|
should.not.exist(err); |
||||
|
var copayerOpts = { |
||||
|
walletId: '123', |
||||
|
id: '999', |
||||
|
name: 'me', |
||||
|
xPubKey: 'dummy', |
||||
|
xPubKeySignature: 'dummy', |
||||
|
}; |
||||
|
server.joinWallet(copayerOpts, function (err) { |
||||
|
should.not.exist(err); |
||||
|
server.getWallet({ id: '123', includeCopayers: true }, function (err, wallet) { |
||||
|
wallet.id.should.equal('123'); |
||||
|
wallet.copayers.length.should.equal(1); |
||||
|
var copayer = wallet.copayers[0]; |
||||
|
copayer.id.should.equal('999'); |
||||
|
copayer.name.should.equal('me'); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should fail to join non-existent wallet', function (done) { |
||||
|
var walletOpts = { |
||||
|
id: '123', |
||||
|
name: 'my wallet', |
||||
|
m: 2, |
||||
|
n: 3, |
||||
|
pubKey: 'dummy', |
||||
|
}; |
||||
|
server.createWallet(walletOpts, function(err) { |
||||
|
should.not.exist(err); |
||||
|
var copayerOpts = { |
||||
|
walletId: '234', |
||||
|
id: '999', |
||||
|
name: 'me', |
||||
|
xPubKey: 'dummy', |
||||
|
xPubKeySignature: 'dummy', |
||||
|
}; |
||||
|
server.joinWallet(copayerOpts, function (err) { |
||||
|
should.exist(err); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should fail to join full wallet', function (done) { |
||||
|
var walletOpts = { |
||||
|
id: '123', |
||||
|
name: 'my wallet', |
||||
|
m: 1, |
||||
|
n: 1, |
||||
|
pubKey: 'dummy', |
||||
|
}; |
||||
|
server.createWallet(walletOpts, function(err) { |
||||
|
should.not.exist(err); |
||||
|
var copayer1Opts = { |
||||
|
walletId: '123', |
||||
|
id: '111', |
||||
|
name: 'me', |
||||
|
xPubKey: 'dummy1', |
||||
|
xPubKeySignature: 'dummy', |
||||
|
}; |
||||
|
var copayer2Opts = { |
||||
|
walletId: '123', |
||||
|
id: '222', |
||||
|
name: 'me 2', |
||||
|
xPubKey: 'dummy2', |
||||
|
xPubKeySignature: 'dummy', |
||||
|
}; |
||||
|
server.joinWallet(copayer1Opts, function (err) { |
||||
|
should.not.exist(err); |
||||
|
server.getWallet({ id: '123' }, function (err, wallet) { |
||||
|
wallet.status.should.equal('complete'); |
||||
|
server.joinWallet(copayer2Opts, function (err) { |
||||
|
should.exist(err); |
||||
|
err.should.equal('Wallet full'); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should fail to re-join wallet', function (done) { |
||||
|
var walletOpts = { |
||||
|
id: '123', |
||||
|
name: 'my wallet', |
||||
|
m: 1, |
||||
|
n: 1, |
||||
|
pubKey: 'dummy', |
||||
|
}; |
||||
|
server.createWallet(walletOpts, function(err) { |
||||
|
should.not.exist(err); |
||||
|
var copayerOpts = { |
||||
|
walletId: '123', |
||||
|
id: '111', |
||||
|
name: 'me', |
||||
|
xPubKey: 'dummy', |
||||
|
xPubKeySignature: 'dummy', |
||||
|
}; |
||||
|
server.joinWallet(copayerOpts, function (err) { |
||||
|
should.not.exist(err); |
||||
|
server.joinWallet(copayerOpts, function (err) { |
||||
|
should.exist(err); |
||||
|
err.should.equal('Copayer already in wallet'); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('should set pkr and status = complete on last copayer joining', function (done) { |
||||
|
helpers.createAndJoinWallet('123', 2, 3, function (err, wallet) { |
||||
|
server.getWallet({ id: '123' }, function (err, wallet) { |
||||
|
should.not.exist(err); |
||||
|
wallet.status.should.equal('complete'); |
||||
|
wallet.publicKeyRing.length.should.equal(3); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
var helpers = {}; |
||||
|
helpers.createAndJoinWallet = function (id, m, n, cb) { |
||||
|
var walletOpts = { |
||||
|
id: id, |
||||
|
name: id + ' wallet', |
||||
|
m: m, |
||||
|
n: n, |
||||
|
pubKey: 'dummy', |
||||
|
}; |
||||
|
server.createWallet(walletOpts, function(err) { |
||||
|
if (err) return cb(err); |
||||
|
|
||||
|
async.each(_.range(1, n + 1), function (i, cb) { |
||||
|
var copayerOpts = { |
||||
|
walletId: id, |
||||
|
id: '' + i, |
||||
|
name: 'copayer ' + i, |
||||
|
xPubKey: 'dummy' + i, |
||||
|
xPubKeySignature: 'dummy', |
||||
|
}; |
||||
|
server.joinWallet(copayerOpts, function (err) { |
||||
|
return cb(err); |
||||
|
}); |
||||
|
}, function (err) { |
||||
|
if (err) return cb(err); |
||||
|
server.getWallet({ id: id, includeCopayers: true }, function (err, wallet) { |
||||
|
return cb(err, wallet); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
describe('#createTx', function() { |
||||
|
beforeEach(function() { |
||||
|
server = new CopayServer({ |
||||
|
db: db, |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it.skip('should create tx', function (done) { |
||||
|
server._verifyMessageSignature = sinon.stub().returns(true); |
||||
|
helpers.createAndJoinWallet('123', 2, 2, function (err, wallet) { |
||||
|
var txOpts = { |
||||
|
copayerId: '1', |
||||
|
walletId: '123', |
||||
|
toAddress: 'dummy', |
||||
|
amount: 100, |
||||
|
message: 'some message', |
||||
|
otToken: 'dummy', |
||||
|
requestSignature: 'dummy', |
||||
|
}; |
||||
|
server.createTx(txOpts, function (err, res) { |
||||
|
should.not.exist(err); |
||||
|
res.ntxid.should.exist; |
||||
|
res.txRaw.should.exist; |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
Loading…
Reference in new issue