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