Initial commit
This commit is contained in:
commit
3e6f1cfebe
|
@ -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