Compare commits

..

No commits in common. "master" and "v1.7.0" have entirely different histories.

65 changed files with 1648 additions and 12068 deletions

View File

@ -1,44 +0,0 @@
{
"bitwise": true,
"camelcase": true,
"curly": true,
"devel": false,
"eqeqeq": true,
"eqnull": false,
"freeze": true,
"funcscope": false,
"immed": true,
"indent": 2,
"latedef": "nofunc",
"maxcomplexity": 10,
"maxdepth": 4,
"maxerr": 99999,
"maxlen": 120,
"maxparams": 4,
"maxstatements": 15,
"mocha": true,
"newcap": true,
"noarg": true,
"node": true,
"noempty": true,
"nonew": true,
"quotmark": "single",
"regexp": true,
"smarttabs": false,
"strict": true,
"trailing": true,
"undef": true,
"unused": true,
"predef": [
"after",
"afterEach",
"before",
"beforeEach",
"describe",
"exports",
"it",
"xit",
"module",
"require"
]
}

View File

@ -12,7 +12,7 @@ addons:
- g++-4.8 - g++-4.8
- clang - clang
node_js: node_js:
- '8' - '4'
before_install: before_install:
- export CXX="g++-4.8" CC="gcc-4.8" - export CXX="g++-4.8" CC="gcc-4.8"
install: install:

View File

@ -13,35 +13,24 @@ Bitcore Wallet Service facilitates multisig HD wallets creation and operation th
BWS can usually be installed within minutes and accommodates all the needed infrastructure for peers in a multisig wallet to communicate and operate with minimum server trust. BWS can usually be installed within minutes and accommodates all the needed infrastructure for peers in a multisig wallet to communicate and operate with minimum server trust.
See [Bitcore-wallet-client](https://github.com/bitpay/bitcore-wallet-client) for the *official* client library that communicates to BWS and verifies its response. Also check [Bitcore-wallet](https://github.com/bitpay/bitcore-wallet) for a simple CLI wallet implementation that relies on BWS. See [Bitcore-wallet-client] (https://github.com/bitpay/bitcore-wallet-client) for the *official* client library that communicates to BWS and verifies its response. Also check [Bitcore-wallet] (https://github.com/bitpay/bitcore-wallet) for a simple CLI wallet implementation that relays on BWS.
BWS is been used in production enviroments for [Copay Wallet](https://copay.io), [Bitpay App wallet](https://bitpay.com/wallet) and others. BWS have a extensive test suite but have not been tested on production environments yet and have been recently released, so it it is still should be considered BETA software.
More about BWS at https://blog.bitpay.com/announcing-the-bitcore-wallet-suite/ More about BWS at https://blog.bitpay.com/announcing-the-bitcore-wallet-suite/
# Getting Started # Install
``` ```
git clone https://github.com/bitpay/bitcore-wallet-service.git npm install bitcore-wallet-service
cd bitcore-wallet-service && npm start npm start
``` ```
This will launch the BWS service (with default settings) at `http://localhost:3232/bws/api`. This will launch the BWS service (with default settings) at `http://localhost:3232/bws/api`.
BWS needs mongoDB. You can configure the connection at `config.js` BWS needs mongoDB. You can configure the connection at `config.js`
BWS supports SSL and Clustering. For a detailed guide on installing BWS with extra features see [Installing BWS](https://github.com/bitpay/bitcore-wallet-service/blob/master/installation.md). BWS supports SSL and Clustering. For a detailed guide on installing BWS with extra features see [Installing BWS](https://github.com/bitpay/bitcore-wallet-service/blob/master/installation.md).
BWS uses by default a Request Rate Limitation to CreateWallet endpoint. If you need to modify it, check defaults.js' `Defaults.RateLimit`
# Using BWS with PM2
BWS can be used with PM2 with the provided `app.js` script:
```
pm2 start app.js --name "bitcoin-wallet-service"
```
# Security Considerations # Security Considerations
* Private keys are never sent to BWS. Copayers store them locally. * Private keys are never sent to BWS. Copayers store them locally.
* Extended public keys are stored on BWS. This allows BWS to easily check wallet balance, send offline notifications to copayers, etc. * Extended public keys are stored on BWS. This allows BWS to easily check wallet balance, send offline notifications to copayers, etc.
@ -51,29 +40,7 @@ BWS can be used with PM2 with the provided `app.js` script:
* Addresses and change addresses are derived independently and locally by the copayers from their local data. * Addresses and change addresses are derived independently and locally by the copayers from their local data.
* TX Proposals templates are signed by copayers and verified by others, so the BWS cannot create or tamper with them. * TX Proposals templates are signed by copayers and verified by others, so the BWS cannot create or tamper with them.
# Using SSL
You can add your certificates at the config.js using:
``` json
https: true,
privateKeyFile: 'private.pem',
certificateFile: 'cert.pem',
////// The following is only for certs which are not
////// trusted by nodejs 'https' by default
////// CAs like Verisign do not require this
// CAinter1: '', // ex. 'COMODORSADomainValidationSecureServerCA.crt'
// CAinter2: '', // ex. 'COMODORSAAddTrustCA.crt'
// CAroot: '', // ex. 'AddTrustExternalCARoot.crt'
```
@dabura667 made a report about how to use letsencrypt with BWS: https://github.com/bitpay/bitcore-wallet-service/issues/423
# REST API # REST API
Note: all currency amounts are in units of satoshis (1/100,000,000 of a bitcoin).
## Authentication ## Authentication
In order to access a wallet, clients are required to send the headers: In order to access a wallet, clients are required to send the headers:
@ -133,18 +100,6 @@ Returns:
* byAddress array ['address', 'path', 'amount']: A list of addresses holding funds. * byAddress array ['address', 'path', 'amount']: A list of addresses holding funds.
* totalKbToSendMax: An estimation of the number of KiB required to include all available UTXOs in a tx (including unconfirmed). * totalKbToSendMax: An estimation of the number of KiB required to include all available UTXOs in a tx (including unconfirmed).
`/v1/txnotes/:txid`: Get user notes associated to the specified transaction.
Returns:
* The note associated to the `txid` as a string.
`/v1/fiatrates/:code`: Get the fiat rate for the specified ISO 4217 code.
Optional Arguments:
* provider: An identifier representing the source of the rates.
* ts: The timestamp for the fiat rate (defaults to now).
Returns:
* The fiat exchange rate.
## POST Endpoints ## POST Endpoints
`/v1/wallets/`: Create a new Wallet `/v1/wallets/`: Create a new Wallet
@ -160,7 +115,6 @@ Returns:
`/v1/wallets/:id/copayers/`: Join a Wallet in creation `/v1/wallets/:id/copayers/`: Join a Wallet in creation
Required Arguments: Required Arguments:
* walletId: Id of the wallet to join * walletId: Id of the wallet to join
* name: Copayer Name * name: Copayer Name
@ -173,11 +127,10 @@ Returns:
* wallet: Object with wallet's information * wallet: Object with wallet's information
`/v1/txproposals/`: Add a new transaction proposal `/v1/txproposals/`: Add a new transaction proposal
Required Arguments: Required Arguments:
* toAddress: RCPT Bitcoin address. * toAddress: RCPT Bitcoin address.
* amount: amount (in satoshis) of the mount proposed to be transfered * amount: amount (in satoshis) of the mount proposed to be transfered
* proposalsSignature: Signature of the proposal by the creator peer, using proposalSigningKey. * proposalsSignature: Signature of the proposal by the creator peer, using prososalSigningKey.
* (opt) message: Encrypted private message to peers. * (opt) message: Encrypted private message to peers.
* (opt) payProUrl: Paypro URL for peers to verify TX * (opt) payProUrl: Paypro URL for peers to verify TX
* (opt) feePerKb: Use an alternative fee per KB for this TX. * (opt) feePerKb: Use an alternative fee per KB for this TX.
@ -187,7 +140,7 @@ Returns:
* TX Proposal object. (see [fields on the source code](https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js)). `.id` is probably needed in this case. * TX Proposal object. (see [fields on the source code](https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js)). `.id` is probably needed in this case.
`/v3/addresses/`: Request a new main address from wallet . (creates an address on normal conditions) `/v1/addresses/`: Request a new main address from wallet
Returns: Returns:
* Address object: (https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/address.js)). Note that `path` is returned so client can derive the address independently and check server's response. * Address object: (https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/address.js)). Note that `path` is returned so client can derive the address independently and check server's response.
@ -215,13 +168,6 @@ Returns:
Optional Arguments: Optional Arguments:
* includeCopayerBranches: Scan all copayer branches following BIP45 recommendation (defaults to false). * includeCopayerBranches: Scan all copayer branches following BIP45 recommendation (defaults to false).
`/v1/txconfirmations/`: Subscribe to receive push notifications when the specified transaction gets confirmed.
Required Arguments:
* txid: The transaction to subscribe to.
## PUT Endpoints
`/v1/txnotes/:txid/`: Modify a note for a tx.
## DELETE Endpoints ## DELETE Endpoints
`/v1/txproposals/:id/`: Deletes a transaction proposal. Only the creator can delete a TX Proposal, and only if it has no other signatures or rejections `/v1/txproposals/:id/`: Deletes a transaction proposal. Only the creator can delete a TX Proposal, and only if it has no other signatures or rejections
@ -229,10 +175,13 @@ Required Arguments:
Returns: Returns:
* TX Proposal object. (see [fields on the source code](https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js)). `.id` is probably needed in this case. * TX Proposal object. (see [fields on the source code](https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js)). `.id` is probably needed in this case.
`/v1/txconfirmations/:txid`: Unsubscribe from transaction `txid` and no longer listen to its confirmation.
# Push Notifications # Push Notifications
## Installation
In order to use push notifications service, you need install:
* [node-pushserver](https://www.npmjs.com/package/node-pushserver)
Recomended to complete config.js file: Recomended to complete config.js file:
* [GCM documentation to get your API key](https://developers.google.com/cloud-messaging/gcm) * [GCM documentation to get your API key](https://developers.google.com/cloud-messaging/gcm)
@ -243,8 +192,8 @@ Required Arguments:
`/v1/pushnotifications/subscriptions/`: Adds subscriptions for push notifications service at database. `/v1/pushnotifications/subscriptions/`: Adds subscriptions for push notifications service at database.
## DELETE Endpoints ## DELETE Endopints
`/v2/pushnotifications/subscriptions/`: Remove subscriptions for push notifications service from database. `/v1/pushnotifications/subscriptions/`: Remove subscriptions for push notifications service from database.

23
app.js
View File

@ -1,23 +0,0 @@
#!/usr/bin/env node
var spawn = require('child_process').spawn;
var async = require('async');
var scripts = ['locker/locker.js', 'messagebroker/messagebroker.js',
'bcmonitor/bcmonitor.js', 'emailservice/emailservice.js',
'pushnotificationsservice/pushnotificationsservice.js',
'fiatrateservice/fiatrateservice.js', 'bws.js'];
async.eachSeries(scripts, function(script, callback) {
console.log(`Spawning ${script}`);
var node = spawn('node', [script]);
node.stdout.on('data', (data) => {
console.log(`${data}`);
});
node.stderr.on('data', (data) => {
console.error(`${data}`);
});
callback();
});

View File

@ -38,40 +38,24 @@ var config = {
}, },
}, },
blockchainExplorerOpts: { blockchainExplorerOpts: {
btc: {
livenet: { livenet: {
provider: 'insight', provider: 'insight',
url: 'https://explorer.btcprivate.org:443', url: 'https://insight.bitpay.com:443',
}, },
testnet: { testnet: {
provider: 'insight', provider: 'insight',
url: 'https://explorer.testnet.btcprivate.org:443', url: 'https://test-insight.bitpay.com:443',
// url: 'http://localhost:3001',
// Multiple servers (in priority order) // Multiple servers (in priority order)
// url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'], // url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'],
}, },
}, },
bch: {
livenet: {
provider: 'insight',
//url: 'https://cashexplorer.bitcoin.com',
url: 'https://bch-insight.bitpay.com:443',
addressFormat: 'cashaddr', // copay, cashaddr, or legacy
},
testnet: {
provider: 'insight',
url: 'https://test-bch-insight.bitpay.com:443',
addressFormat: 'cashaddr', // copay, cashaddr, or legacy
},
},
},
pushNotificationsOpts: { pushNotificationsOpts: {
templatePath: './lib/templates', templatePath: './lib/templates',
defaultLanguage: 'en', defaultLanguage: 'en',
defaultUnit: 'btc', defaultUnit: 'btc',
subjectPrefix: '', subjectPrefix: '',
pushServerUrl: 'https://fcm.googleapis.com/fcm', pushServerUrl: 'http://localhost:8000',
authorizationKey: '',
}, },
fiatRateServiceOpts: { fiatRateServiceOpts: {
defaultProvider: 'BitPay', defaultProvider: 'BitPay',

View File

@ -1,61 +0,0 @@
var Bitcore_ = {
btc: require('bitcore-lib'),
bch: require('bitcore-lib-cash')
};
var _ = require('lodash');
function BCHAddressTranslator() {
};
BCHAddressTranslator.getAddressCoin = function(address) {
try {
new Bitcore_['btc'].Address(address);
return 'legacy';
} catch (e) {
try {
var a= new Bitcore_['bch'].Address(address);
if (a.toString() == address) return 'copay';
return 'cashaddr';
} catch (e) {
return;
}
}
};
// Supports 3 formats: legacy (1xxx, mxxxx); Copay: (Cxxx, Hxxx), Cashaddr(qxxx);
BCHAddressTranslator.translate = function(addresses, to, from) {
var wasArray = true;
if (!_.isArray(addresses)) {
wasArray = false;
addresses = [addresses];
}
from = from || BCHAddressTranslator.getAddressCoin(addresses[0]);
if (from == to) return addresses;
var ret = _.map(addresses, function(x) {
var bitcore = Bitcore_[from == 'legacy' ? 'btc' : 'bch'];
var orig = new bitcore.Address(x).toObject();
if (to == 'cashaddr') {
return Bitcore_['bch'].Address.fromObject(orig).toCashAddress(true);
} else if (to == 'copay') {
return Bitcore_['bch'].Address.fromObject(orig).toString();
} else if (to == 'legacy') {
return Bitcore_['btc'].Address.fromObject(orig).toString();
}
});
if (wasArray)
return ret;
else
return ret[0];
};
module.exports = BCHAddressTranslator;

View File

@ -6,21 +6,11 @@ var log = require('npmlog');
log.debug = log.verbose; log.debug = log.verbose;
var Insight = require('./blockchainexplorers/insight'); var Insight = require('./blockchainexplorers/insight');
var Common = require('./common');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
var PROVIDERS = { var PROVIDERS = {
'insight': { 'insight': {
'btc': { 'livenet': 'https://insight.bitpay.com:443',
'livenet': 'https://explorer.btcprivate.org:443', 'testnet': 'https://test-insight.bitpay.com:443',
'testnet': 'https://explorer.testnet.btcprivate.org:443',
},
'bch': {
'livenet': 'https://bch-insight.bitpay.com:443',
'testnet': 'https://test-bch-insight.bitpay.com:443',
},
}, },
}; };
@ -28,32 +18,20 @@ function BlockChainExplorer(opts) {
$.checkArgument(opts); $.checkArgument(opts);
var provider = opts.provider || 'insight'; var provider = opts.provider || 'insight';
var coin = opts.coin || Defaults.COIN;
var network = opts.network || 'livenet'; var network = opts.network || 'livenet';
$.checkState(PROVIDERS[provider], 'Provider ' + provider + ' not supported'); $.checkState(PROVIDERS[provider], 'Provider ' + provider + ' not supported');
$.checkState(_.contains(_.keys(PROVIDERS[provider]), coin), 'Coin ' + coin + ' not supported by this provider'); $.checkState(_.contains(_.keys(PROVIDERS[provider]), network), 'Network ' + network + ' not supported by this provider');
$.checkState(_.contains(_.keys(PROVIDERS[provider][coin]), network), 'Network ' + network + ' not supported by this provider for coin ' + coin);
var url = opts.url || PROVIDERS[provider][coin][network];
if (coin != 'bch' && opts.addressFormat)
throw new Error('addressFormat only supported for bch');
if (coin == 'bch' && !opts.addressFormat)
opts.addressFormat = 'cashaddr';
var url = opts.url || PROVIDERS[provider][network];
switch (provider) { switch (provider) {
case 'insight': case 'insight':
return new Insight({ return new Insight({
coin: coin,
network: network, network: network,
url: url, url: url,
apiPrefix: opts.apiPrefix, apiPrefix: opts.apiPrefix,
userAgent: opts.userAgent, userAgent: opts.userAgent,
addressFormat: opts.addressFormat,
}); });
default: default:
throw new Error('Provider ' + provider + ' not supported.'); throw new Error('Provider ' + provider + ' not supported.');

View File

@ -1,38 +1,23 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var async = require('async');
var $ = require('preconditions').singleton(); var $ = require('preconditions').singleton();
var log = require('npmlog'); var log = require('npmlog');
log.debug = log.verbose; log.debug = log.verbose;
var io = require('socket.io-client'); var io = require('socket.io-client');
var requestList = require('./request-list'); var requestList = require('./request-list');
var Common = require('../common');
var BCHAddressTranslator = require('../bchaddresstranslator');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
function Insight(opts) { function Insight(opts) {
$.checkArgument(opts); $.checkArgument(opts);
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS)); $.checkArgument(_.contains(['livenet', 'testnet'], opts.network));
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
$.checkArgument(opts.url); $.checkArgument(opts.url);
this.apiPrefix = _.isUndefined(opts.apiPrefix)? '/api' : opts.apiPrefix; this.apiPrefix = opts.apiPrefix || '/api';
this.coin = opts.coin || Defaults.COIN;
this.network = opts.network || 'livenet'; this.network = opts.network || 'livenet';
this.hosts = opts.url; this.hosts = opts.url;
this.userAgent = opts.userAgent || 'bws'; this.userAgent = opts.userAgent || 'bws';
};
if (opts.addressFormat) {
$.checkArgument(Constants.ADDRESS_FORMATS.includes(opts.addressFormat), 'Unkown addr format:' + opts.addressFormat);
this.addressFormat = opts.addressFormat != 'copay' ? opts.addressFormat : null;
}
this.requestQueue = async.queue(this._doRequest.bind(this), Defaults.INSIGHT_REQUEST_POOL_SIZE);
}
var _parseErr = function(err, res) { var _parseErr = function(err, res) {
if (err) { if (err) {
@ -43,42 +28,6 @@ var _parseErr = function(err, res) {
return "Error querying the blockchain"; return "Error querying the blockchain";
}; };
// Translate Request Address query
Insight.prototype.translateQueryAddresses = function(addresses) {
if (!this.addressFormat) return addresses;
return BCHAddressTranslator.translate(addresses, this.addressFormat, 'copay');
};
// Translate Result Address
Insight.prototype.translateResultAddresses = function(addresses) {
if (!this.addressFormat) return addresses;
return BCHAddressTranslator.translate(addresses, 'copay', this.addressFormat);
};
Insight.prototype.translateTx = function(tx) {
var self = this;
if (!this.addressFormat) return tx;
_.each(tx.vin, function(x){
if (x.addr) {
x.addr = self.translateResultAddresses(x.addr);
}
});
_.each(tx.vout, function(x){
if (x.scriptPubKey && x.scriptPubKey.addresses) {
x.scriptPubKey.addresses = self.translateResultAddresses(x.scriptPubKey.addresses);
}
});
};
Insight.prototype._doRequest = function(args, cb) { Insight.prototype._doRequest = function(args, cb) {
var opts = { var opts = {
hosts: this.hosts, hosts: this.hosts,
@ -86,43 +35,28 @@ Insight.prototype._doRequest = function(args, cb) {
'User-Agent': this.userAgent, 'User-Agent': this.userAgent,
} }
}; };
var s = JSON.stringify(args);
// if ( s.length > 100 )
// s= s.substr(0,100) + '...';
log.debug('', 'Insight Q: %s', s);
requestList(_.defaults(args, opts), cb); requestList(_.defaults(args, opts), cb);
}; };
Insight.prototype.getConnectionInfo = function() { Insight.prototype.getConnectionInfo = function() {
return 'Insight (' + this.coin + '/' + this.network + ') @ ' + this.hosts; return 'Insight (' + this.network + ') @ ' + this.hosts;
}; };
/** /**
* Retrieve a list of unspent outputs associated with an address or set of addresses * Retrieve a list of unspent outputs associated with an address or set of addresses
*/ */
Insight.prototype.getUtxos = function(addresses, cb) { Insight.prototype.getUtxos = function(addresses, cb) {
var self = this;
var url = this.url + this.apiPrefix + '/addrs/utxo'; var url = this.url + this.apiPrefix + '/addrs/utxo';
var args = { var args = {
method: 'POST', method: 'POST',
path: this.apiPrefix + '/addrs/utxo', path: this.apiPrefix + '/addrs/utxo',
json: { json: {
addrs: this.translateQueryAddresses(_.uniq([].concat(addresses))).join(',') addrs: [].concat(addresses).join(',')
}, },
}; };
this.requestQueue.push(args, function(err, res, unspent) { this._doRequest(args, function(err, res, unspent) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res)); if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
if (self.addressFormat) {
_.each(unspent, function(x) {
x.address = self.translateResultAddresses(x.address);
});
}
return cb(null, unspent); return cb(null, unspent);
}); });
}; };
@ -139,35 +73,29 @@ Insight.prototype.broadcast = function(rawTx, cb) {
}, },
}; };
this.requestQueue.push(args, function(err, res, body) { this._doRequest(args, function(err, res, body) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res)); if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
return cb(null, body ? body.txid : null); return cb(null, body ? body.txid : null);
}); });
}; };
Insight.prototype.getTransaction = function(txid, cb) { Insight.prototype.getTransaction = function(txid, cb) {
var self = this;
var args = { var args = {
method: 'GET', method: 'GET',
path: this.apiPrefix + '/tx/' + txid, path: this.apiPrefix + '/tx/' + txid,
json: true, json: true,
}; };
this.requestQueue.push(args, function(err, res, tx) { this._doRequest(args, function(err, res, tx) {
if (res && res.statusCode == 404) return cb(); if (res && res.statusCode == 404) return cb();
if (err || res.statusCode !== 200) if (err || res.statusCode !== 200)
return cb(_parseErr(err, res)); return cb(_parseErr(err, res));
self.translateTx(tx);
return cb(null, tx); return cb(null, tx);
}); });
}; };
Insight.prototype.getTransactions = function(addresses, from, to, cb) { Insight.prototype.getTransactions = function(addresses, from, to, cb) {
var self = this;
var qs = []; var qs = [];
var total; var total;
if (_.isNumber(from)) qs.push('from=' + from); if (_.isNumber(from)) qs.push('from=' + from);
@ -182,13 +110,11 @@ Insight.prototype.getTransactions = function(addresses, from, to, cb) {
method: 'POST', method: 'POST',
path: this.apiPrefix + '/addrs/txs' + (qs.length > 0 ? '?' + qs.join('&') : ''), path: this.apiPrefix + '/addrs/txs' + (qs.length > 0 ? '?' + qs.join('&') : ''),
json: { json: {
addrs: this.translateQueryAddresses(_.uniq([].concat(addresses))).join(',') addrs: [].concat(addresses).join(',')
}, },
timeout: 120000,
}; };
this._doRequest(args, function(err, res, txs) {
this.requestQueue.push(args, function(err, res, txs) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res)); if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
if (_.isObject(txs)) { if (_.isObject(txs)) {
@ -202,13 +128,6 @@ Insight.prototype.getTransactions = function(addresses, from, to, cb) {
// NOTE: Whenever Insight breaks communication with bitcoind, it returns invalid data but no error code. // NOTE: Whenever Insight breaks communication with bitcoind, it returns invalid data but no error code.
if (!_.isArray(txs) || (txs.length != _.compact(txs).length)) return cb(new Error('Could not retrieve transactions from blockchain. Request was:' + JSON.stringify(args))); if (!_.isArray(txs) || (txs.length != _.compact(txs).length)) return cb(new Error('Could not retrieve transactions from blockchain. Request was:' + JSON.stringify(args)));
if (self.addressFormat) {
_.each(txs, function(tx){
self.translateTx(tx);
});
}
return cb(null, txs, total); return cb(null, txs, total);
}); });
}; };
@ -218,17 +137,15 @@ Insight.prototype.getAddressActivity = function(address, cb) {
var args = { var args = {
method: 'GET', method: 'GET',
path: self.apiPrefix + '/addr/' + this.translateQueryAddresses(address), path: self.apiPrefix + '/addr/' + address,
json: true, json: true,
}; };
this.requestQueue.push(args, function(err, res, result) { this._doRequest(args, function(err, res, result) {
if (res && res.statusCode == 404) return cb(); if (res && res.statusCode == 404) return cb();
if (err || res.statusCode !== 200) if (err || res.statusCode !== 200)
return cb(_parseErr(err, res)); return cb(_parseErr(err, res));
// note: result.addrStr is not translated, but not used.
var nbTxs = result.unconfirmedTxApperances + result.txApperances; var nbTxs = result.unconfirmedTxApperances + result.txApperances;
return cb(null, nbTxs > 0); return cb(null, nbTxs > 0);
}); });
@ -245,7 +162,7 @@ Insight.prototype.estimateFee = function(nbBlocks, cb) {
path: path, path: path,
json: true, json: true,
}; };
this.requestQueue.push(args, function(err, res, body) { this._doRequest(args, function(err, res, body) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res)); if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
return cb(null, body); return cb(null, body);
}); });
@ -259,27 +176,12 @@ Insight.prototype.getBlockchainHeight = function(cb) {
path: path, path: path,
json: true, json: true,
}; };
this.requestQueue.push(args, function(err, res, body) { this._doRequest(args, function(err, res, body) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res)); if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
return cb(null, body.blockChainHeight); return cb(null, body.blockChainHeight);
}); });
}; };
Insight.prototype.getTxidsInBlock = function(blockHash, cb) {
var self = this;
var args = {
method: 'GET',
path: this.apiPrefix + '/block/' + blockHash,
json: true,
};
this.requestQueue.push(args, function(err, res, body) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
return cb(null, body.tx);
});
};
Insight.prototype.initSocket = function() { Insight.prototype.initSocket = function() {
// sockets always use the first server on the pull // sockets always use the first server on the pull

View File

@ -31,23 +31,11 @@ var requestList = function(args, cb) {
async.whilst( async.whilst(
function() { function() {
nextUrl = urls.shift(); nextUrl = urls.shift();
if (!nextUrl && success === 'false')
log.warn('no more servers to test for the request');
return nextUrl && !success; return nextUrl && !success;
}, },
function(a_cb) { function(a_cb) {
args.uri = nextUrl; args.uri = nextUrl;
var time = 0;
var interval = setInterval(function() {
time += 10;
log.debug('', 'Delayed insight query: %s, time: %d s', args.uri, time);
}, 10000);
request(args, function(err, res, body) { request(args, function(err, res, body) {
clearInterval(interval);
sucess = false;
if (err) { if (err) {
log.warn('REQUEST FAIL: ' + nextUrl + ' ERROR: ' + err); log.warn('REQUEST FAIL: ' + nextUrl + ' ERROR: ' + err);
} }

View File

@ -14,9 +14,6 @@ var Lock = require('./lock');
var Notification = require('./model/notification'); var Notification = require('./model/notification');
var WalletService = require('./server'); var WalletService = require('./server');
var Common = require('./common');
var Constants = Common.Constants;
var Utils = Common.Utils;
function BlockchainMonitor() {}; function BlockchainMonitor() {};
@ -28,42 +25,25 @@ BlockchainMonitor.prototype.start = function(opts, cb) {
async.parallel([ async.parallel([
function(done) { function(done) {
self.explorers = { self.explorers = _.map(['livenet', 'testnet'], function(network) {
btc: {},
bch: {},
};
var coinNetworkPairs = [];
_.each(_.values(Constants.COINS), function(coin) {
_.each(_.values(Constants.NETWORKS), function(network) {
coinNetworkPairs.push({
coin: coin,
network: network
});
});
});
_.each(coinNetworkPairs, function(pair) {
var explorer; var explorer;
if (opts.blockchainExplorers && opts.blockchainExplorers[pair.coin] && opts.blockchainExplorers[pair.coin][pair.network]) { if (opts.blockchainExplorers) {
explorer = opts.blockchainExplorers[pair.coin][pair.network]; explorer = opts.blockchainExplorers[network];
} else { } else {
var config = {} var config = {}
if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[pair.coin] && opts.blockchainExplorerOpts[pair.coin][pair.network]) { if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[network]) {
config = opts.blockchainExplorerOpts[pair.coin][pair.network]; config = opts.blockchainExplorerOpts[network];
} else {
return;
} }
var explorer = new BlockchainExplorer({ var explorer = new BlockchainExplorer({
provider: config.provider, provider: config.provider,
coin: pair.coin, network: network,
network: pair.network,
url: config.url, url: config.url,
userAgent: WalletService.getServiceVersion(), userAgent: WalletService.getServiceVersion(),
}); });
} }
$.checkState(explorer); $.checkState(explorer);
self._initExplorer(pair.coin, pair.network, explorer); self._initExplorer(explorer);
self.explorers[pair.coin][pair.network] = explorer; return explorer;
}); });
done(); done();
}, },
@ -92,7 +72,7 @@ BlockchainMonitor.prototype.start = function(opts, cb) {
}); });
}; };
BlockchainMonitor.prototype._initExplorer = function(coin, network, explorer) { BlockchainMonitor.prototype._initExplorer = function(explorer) {
var self = this; var self = this;
var socket = explorer.initSocket(); var socket = explorer.initSocket();
@ -104,11 +84,11 @@ BlockchainMonitor.prototype._initExplorer = function(coin, network, explorer) {
socket.on('connect_error', function() { socket.on('connect_error', function() {
log.error('Error connecting to ' + explorer.getConnectionInfo()); log.error('Error connecting to ' + explorer.getConnectionInfo());
}); });
socket.on('tx', _.bind(self._handleIncomingTx, self, coin, network)); socket.on('tx', _.bind(self._handleIncommingTx, self));
socket.on('block', _.bind(self._handleNewBlock, self, coin, network)); socket.on('block', _.bind(self._handleNewBlock, self, explorer.network));
}; };
BlockchainMonitor.prototype._handleThirdPartyBroadcasts = function(data, processIt) { BlockchainMonitor.prototype._handleTxId = function(data, processIt) {
var self = this; var self = this;
if (!data || !data.txid) return; if (!data || !data.txid) return;
@ -123,7 +103,7 @@ BlockchainMonitor.prototype._handleThirdPartyBroadcasts = function(data, process
if (!processIt) { if (!processIt) {
log.info('Detected broadcast ' + data.txid + ' of an accepted txp [' + txp.id + '] for wallet ' + walletId + ' [' + txp.amount + 'sat ]'); log.info('Detected broadcast ' + data.txid + ' of an accepted txp [' + txp.id + '] for wallet ' + walletId + ' [' + txp.amount + 'sat ]');
return setTimeout(self._handleThirdPartyBroadcasts.bind(self, data, true), 20 * 1000); return setTimeout(self._handleTxId.bind(self, data, true), 20 * 1000);
} }
log.info('Processing accepted txp [' + txp.id + '] for wallet ' + walletId + ' [' + txp.amount + 'sat ]'); log.info('Processing accepted txp [' + txp.id + '] for wallet ' + walletId + ' [' + txp.amount + 'sat ]');
@ -152,30 +132,25 @@ BlockchainMonitor.prototype._handleThirdPartyBroadcasts = function(data, process
}); });
}; };
BlockchainMonitor.prototype._handleIncomingPayments = function(coin, network, data) {
BlockchainMonitor.prototype._handleTxOuts = function(data) {
var self = this; var self = this;
if (!data || !data.vout) return; if (!data || !data.vout) return;
var outs = _.compact(_.map(data.vout, function(v) { var outs = _.compact(_.map(data.vout, function(v) {
var addr = _.keys(v)[0]; var addr = _.keys(v)[0];
var amount = +v[addr];
// This is because a bug on insight, that always return no copay addr
if (coin == 'bch' && Utils.getAddressCoin(addr) !='bch') {
addr = Utils.translateAddress(addr, coin);
}
return { return {
address: addr, address: addr,
amount: amount, amount: +v[addr]
}; };
})); }));
if (_.isEmpty(outs)) return; if (_.isEmpty(outs)) return;
async.each(outs, function(out, next) { async.each(outs, function(out, next) {
self.storage.fetchAddress(out.address, function(err, address) {
// toDo, remove coin here: no more same address for diff coins
self.storage.fetchAddressByCoin(coin, out.address, function(err, address) {
if (err) { if (err) {
log.error('Could not fetch addresses from the db'); log.error('Could not fetch addresses from the db');
return next(err); return next(err);
@ -185,17 +160,6 @@ BlockchainMonitor.prototype._handleIncomingPayments = function(coin, network, da
var walletId = address.walletId; var walletId = address.walletId;
log.info('Incoming tx for wallet ' + walletId + ' [' + out.amount + 'sat -> ' + out.address + ']'); log.info('Incoming tx for wallet ' + walletId + ' [' + out.amount + 'sat -> ' + out.address + ']');
var fromTs = Date.now() - 24 * 3600 * 1000;
self.storage.fetchNotifications(walletId, null, fromTs, function(err, notifications) {
if (err) return next(err);
var alreadyNotified = _.any(notifications, function(n) {
return n.type == 'NewIncomingTx' && n.data && n.data.txid == data.txid;
});
if (alreadyNotified) {
log.info('The incoming tx ' + data.txid + ' was already notified');
return next();
}
var notification = Notification.create({ var notification = Notification.create({
type: 'NewIncomingTx', type: 'NewIncomingTx',
data: { data: {
@ -206,58 +170,41 @@ BlockchainMonitor.prototype._handleIncomingPayments = function(coin, network, da
walletId: walletId, walletId: walletId,
}); });
self.storage.softResetTxHistoryCache(walletId, function() { self.storage.softResetTxHistoryCache(walletId, function() {
self._updateAddressesWithBalance(address, function() { self._updateActiveAddresses(address, function() {
self._storeAndBroadcastNotification(notification, next); self._storeAndBroadcastNotification(notification, next);
}); });
}); });
}); });
});
}, function(err) { }, function(err) {
return; return;
}); });
}; };
BlockchainMonitor.prototype._updateAddressesWithBalance = function(address, cb) { BlockchainMonitor.prototype._updateActiveAddresses = function(address, cb) {
var self = this; var self = this;
self.storage.fetchAddressesWithBalance(address.walletId, function(err, result) { self.storage.storeActiveAddresses(address.walletId, address.address, function(err) {
if (err) {
log.warn('Could not update wallet cache', err);
return cb(err);
}
var addresses = _.map(result,'address');
if (_.indexOf(addresses, address.address) >= 0) {
return cb();
}
addresses.push(address.address);
log.info('Activating address ' + address.address);
self.storage.storeAddressesWithBalance(address.walletId, addresses, function(err) {
if (err) { if (err) {
log.warn('Could not update wallet cache', err); log.warn('Could not update wallet cache', err);
} }
return cb(err); return cb(err);
}); });
});
}; };
BlockchainMonitor.prototype._handleIncomingTx = function(coin, network, data) { BlockchainMonitor.prototype._handleIncommingTx = function(data) {
this._handleThirdPartyBroadcasts(data); this._handleTxId(data);
this._handleIncomingPayments(coin, network, data); this._handleTxOuts(data);
}; };
BlockchainMonitor.prototype._notifyNewBlock = function(coin, network, hash) { BlockchainMonitor.prototype._handleNewBlock = function(network, hash) {
var self = this; var self = this;
log.info('New ' + network + ' block: ' + hash); log.info('New ' + network + ' block: ', hash);
var notification = Notification.create({ var notification = Notification.create({
type: 'NewBlock', type: 'NewBlock',
walletId: network, // use network name as wallet id for global notifications walletId: network, // use network name as wallet id for global notifications
data: { data: {
hash: hash, hash: hash,
coin: coin,
network: network, network: network,
}, },
}); });
@ -269,64 +216,6 @@ BlockchainMonitor.prototype._notifyNewBlock = function(coin, network, hash) {
}); });
}; };
BlockchainMonitor.prototype._handleTxConfirmations = function(coin, network, hash) {
var self = this;
function processTriggeredSubs(subs, cb) {
async.each(subs, function(sub) {
log.info('New tx confirmation ' + sub.txid);
sub.isActive = false;
self.storage.storeTxConfirmationSub(sub, function(err) {
if (err) return cb(err);
var notification = Notification.create({
type: 'TxConfirmation',
walletId: sub.walletId,
creatorId: sub.copayerId,
data: {
txid: sub.txid,
coin: coin,
network: network,
// TODO: amount
},
});
self._storeAndBroadcastNotification(notification, cb);
});
});
};
var explorer = self.explorers[coin][network];
if (!explorer) return;
explorer.getTxidsInBlock(hash, function(err, txids) {
if (err) {
log.error('Could not fetch txids from block ' + hash, err);
return;
}
self.storage.fetchActiveTxConfirmationSubs(null, function(err, subs) {
if (err) return;
if (_.isEmpty(subs)) return;
var indexedSubs = _.indexBy(subs, 'txid');
var triggered = [];
_.each(txids, function(txid) {
if (indexedSubs[txid]) triggered.push(indexedSubs[txid]);
});
processTriggeredSubs(triggered, function(err) {
if (err) {
log.error('Could not process tx confirmations', err);
}
return;
});
});
});
};
BlockchainMonitor.prototype._handleNewBlock = function(coin, network, hash) {
this._notifyNewBlock(coin, network, hash);
this._handleTxConfirmations(coin, network, hash);
};
BlockchainMonitor.prototype._storeAndBroadcastNotification = function(notification, cb) { BlockchainMonitor.prototype._storeAndBroadcastNotification = function(notification, cb) {
var self = this; var self = this;

View File

@ -2,18 +2,11 @@
var Constants = {}; var Constants = {};
Constants.COINS = {
BTC: 'btc',
BCH: 'bch',
};
Constants.NETWORKS = { Constants.NETWORKS = {
LIVENET: 'livenet', LIVENET: 'livenet',
TESTNET: 'testnet', TESTNET: 'testnet',
}; };
Constants.ADDRESS_FORMATS = ['copay', 'cashaddr', 'legacy'];
Constants.SCRIPT_TYPES = { Constants.SCRIPT_TYPES = {
P2SH: 'P2SH', P2SH: 'P2SH',
P2PKH: 'P2PKH', P2PKH: 'P2PKH',

View File

@ -3,7 +3,7 @@
var Defaults = {}; var Defaults = {};
Defaults.MIN_FEE_PER_KB = 0; Defaults.MIN_FEE_PER_KB = 0;
Defaults.MAX_FEE_PER_KB = 10000 * 1000; // 10k sat/b Defaults.MAX_FEE_PER_KB = 1000000;
Defaults.MIN_TX_FEE = 0; Defaults.MIN_TX_FEE = 0;
Defaults.MAX_TX_FEE = 0.1 * 1e8; Defaults.MAX_TX_FEE = 0.1 * 1e8;
Defaults.MAX_TX_SIZE_IN_KB = 100; Defaults.MAX_TX_SIZE_IN_KB = 100;
@ -24,35 +24,25 @@ Defaults.MAX_MAIN_ADDRESS_GAP = 20;
// TODO: should allow different gap sizes for external/internal chains // TODO: should allow different gap sizes for external/internal chains
Defaults.SCAN_ADDRESS_GAP = Defaults.MAX_MAIN_ADDRESS_GAP + 20; Defaults.SCAN_ADDRESS_GAP = Defaults.MAX_MAIN_ADDRESS_GAP + 20;
Defaults.FEE_LEVELS = { Defaults.FEE_LEVELS = [{
btc: [{
name: 'urgent',
nbBlocks: 2,
multiplier: 1.5,
defaultValue: 150000,
}, {
name: 'priority', name: 'priority',
nbBlocks: 2, nbBlocks: 2,
defaultValue: 100000 defaultValue: 50000
}, { }, {
name: 'normal', name: 'normal',
nbBlocks: 3, nbBlocks: 3,
defaultValue: 80000 defaultValue: 40000
}, { }, {
name: 'economy', name: 'economy',
nbBlocks: 6, nbBlocks: 6,
defaultValue: 50000 defaultValue: 25000
}, { }, {
name: 'superEconomy', name: 'superEconomy',
nbBlocks: 24, nbBlocks: 24,
defaultValue: 20000 defaultValue: 10000
}], }];
bch: [{
name: 'normal', Defaults.DEFAULT_FEE_PER_KB = Defaults.FEE_LEVELS[1].defaultValue;
nbBlocks: 2,
defaultValue: 2000,
}]
};
// How many levels to fallback to if the value returned by the network for a given nbBlocks is -1 // How many levels to fallback to if the value returned by the network for a given nbBlocks is -1
Defaults.FEE_LEVELS_FALLBACK = 2; Defaults.FEE_LEVELS_FALLBACK = 2;
@ -60,12 +50,6 @@ Defaults.FEE_LEVELS_FALLBACK = 2;
// Minimum nb of addresses a wallet must have to start using 2-step balance optimization // Minimum nb of addresses a wallet must have to start using 2-step balance optimization
Defaults.TWO_STEP_BALANCE_THRESHOLD = 100; Defaults.TWO_STEP_BALANCE_THRESHOLD = 100;
// Age Limit for addresses to be considered 'active' always
Defaults.TWO_STEP_CREATION_HOURS = 24;
// Time to prevent re-quering inactive addresses (MIN)
Defaults.TWO_STEP_INACTIVE_CLEAN_DURATION_MIN = 60;
Defaults.FIAT_RATE_PROVIDER = 'BitPay'; Defaults.FIAT_RATE_PROVIDER = 'BitPay';
Defaults.FIAT_RATE_FETCH_INTERVAL = 10; // In minutes Defaults.FIAT_RATE_FETCH_INTERVAL = 10; // In minutes
Defaults.FIAT_RATE_MAX_LOOK_BACK_TIME = 120; // In minutes Defaults.FIAT_RATE_MAX_LOOK_BACK_TIME = 120; // In minutes
@ -96,12 +80,6 @@ Defaults.CONFIRMATIONS_TO_START_CACHING = 6 * 6; // ~ 6hrs
// Number of addresses from which tx history is enabled in a wallet // Number of addresses from which tx history is enabled in a wallet
Defaults.HISTORY_CACHE_ADDRESS_THRESOLD = 100; Defaults.HISTORY_CACHE_ADDRESS_THRESOLD = 100;
// Number of addresses from which balance in cache for a few seconds
Defaults.BALANCE_CACHE_ADDRESS_THRESOLD = Defaults.HISTORY_CACHE_ADDRESS_THRESOLD;
Defaults.BALANCE_CACHE_DIRECT_DURATION = 60;
Defaults.BALANCE_CACHE_DURATION = 10;
// Cache time for blockchain height (in seconds) // Cache time for blockchain height (in seconds)
Defaults.BLOCKHEIGHT_CACHE_TIME = 10 * 60; Defaults.BLOCKHEIGHT_CACHE_TIME = 10 * 60;
@ -112,29 +90,4 @@ Defaults.NOTIFICATIONS_TIMESPAN = 60;
Defaults.SESSION_EXPIRATION = 1 * 60 * 60; // 1 hour to session expiration Defaults.SESSION_EXPIRATION = 1 * 60 * 60; // 1 hour to session expiration
Defaults.RateLimit = {
createWallet: {
windowMs: 60 * 60 * 1000, // hour window
delayAfter: 8, // begin slowing down responses after the 3rd request
delayMs: 3000, // slow down subsequent responses by 3 seconds per request
max: 15, // start blocking after 20 request
message: 'Too many wallets created from this IP, please try again after an hour',
},
estimateFee: {
windowMs: 60 * 10 *1000, // 10 min window
delayAfter: 5, // begin slowing down responses after the 3rd request
delayMs: 300, // slow down subsequent responses by 3 seconds per request
max: 10, // start blocking after 200 request
message: 'Too many request',
},
// otherPosts: {
// windowMs: 60 * 60 * 1000, // 1 hour window
// max: 1200 , // 1 post every 3 sec average, max.
// },
};
Defaults.COIN = 'btc';
Defaults.INSIGHT_REQUEST_POOL_SIZE = 20;
module.exports = Defaults; module.exports = Defaults;

View File

@ -7,13 +7,6 @@ var encoding = bitcore.encoding;
var secp256k1 = require('secp256k1'); var secp256k1 = require('secp256k1');
var Utils = {}; var Utils = {};
var Bitcore = require('bitcore-lib');
var Bitcore_ = {
btc: Bitcore,
bch: require('bitcore-lib-cash')
};
Utils.getMissingFields = function(obj, args) { Utils.getMissingFields = function(obj, args) {
args = [].concat(args); args = [].concat(args);
@ -71,7 +64,7 @@ Utils._tryImportPublicKey = function(publicKey) {
publicKeyBuffer = new Buffer(publicKey, 'hex'); publicKeyBuffer = new Buffer(publicKey, 'hex');
} }
return publicKeyBuffer; return publicKeyBuffer;
} catch (e) { } catch(e) {
return false; return false;
} }
}; };
@ -83,7 +76,7 @@ Utils._tryImportSignature = function(signature) {
signatureBuffer = new Buffer(signature, 'hex'); signatureBuffer = new Buffer(signature, 'hex');
} }
return secp256k1.signatureImport(signatureBuffer); return secp256k1.signatureImport(signatureBuffer);
} catch (e) { } catch(e) {
return false; return false;
} }
}; };
@ -91,7 +84,7 @@ Utils._tryImportSignature = function(signature) {
Utils._tryVerifyMessage = function(hash, sig, publicKeyBuffer) { Utils._tryVerifyMessage = function(hash, sig, publicKeyBuffer) {
try { try {
return secp256k1.verify(hash, sig, publicKeyBuffer); return secp256k1.verify(hash, sig, publicKeyBuffer);
} catch (e) { } catch(e) {
return false; return false;
} }
}; };
@ -112,12 +105,7 @@ Utils.formatAmount = function(satoshis, unit, opts) {
toSatoshis: 1, toSatoshis: 1,
maxDecimals: 0, maxDecimals: 0,
minDecimals: 0, minDecimals: 0,
}, }
bch: {
toSatoshis: 100000000,
maxDecimals: 6,
minDecimals: 2,
},
}; };
$.shouldBeNumber(satoshis); $.shouldBeNumber(satoshis);
@ -188,35 +176,5 @@ Utils.parseVersion = function(version) {
return v; return v;
}; };
Utils.checkValueInCollection = function(value, collection) {
if (!value || !_.isString(value)) return false;
return _.contains(_.values(collection), value);
};
Utils.getAddressCoin = function(address) {
try {
new Bitcore_['btc'].Address(address);
return 'btc';
} catch (e) {
try {
new Bitcore_['bch'].Address(address);
return 'bch';
} catch (e) {
return;
}
}
};
Utils.translateAddress = function(address, coin) {
var origCoin = Utils.getAddressCoin(address);
var origAddress = new Bitcore_[origCoin].Address(address);
var origObj = origAddress.toObject();
var result = Bitcore_[coin].Address.fromObject(origObj)
return result.toString();
};
module.exports = Utils; module.exports = Utils;

View File

@ -11,7 +11,6 @@ var path = require('path');
var nodemailer = require('nodemailer'); var nodemailer = require('nodemailer');
var Utils = require('./common/utils'); var Utils = require('./common/utils');
var Defaults = require('./common/defaults');
var Storage = require('./storage'); var Storage = require('./storage');
var MessageBroker = require('./messagebroker'); var MessageBroker = require('./messagebroker');
var Lock = require('./lock'); var Lock = require('./lock');
@ -22,37 +21,26 @@ var EMAIL_TYPES = {
'NewCopayer': { 'NewCopayer': {
filename: 'new_copayer', filename: 'new_copayer',
notifyDoer: false, notifyDoer: false,
notifyOthers: true,
}, },
'WalletComplete': { 'WalletComplete': {
filename: 'wallet_complete', filename: 'wallet_complete',
notifyDoer: true, notifyDoer: true,
notifyOthers: true,
}, },
'NewTxProposal': { 'NewTxProposal': {
filename: 'new_tx_proposal', filename: 'new_tx_proposal',
notifyDoer: false, notifyDoer: false,
notifyOthers: true,
}, },
'NewOutgoingTx': { 'NewOutgoingTx': {
filename: 'new_outgoing_tx', filename: 'new_outgoing_tx',
notifyDoer: true, notifyDoer: true,
notifyOthers: true,
}, },
'NewIncomingTx': { 'NewIncomingTx': {
filename: 'new_incoming_tx', filename: 'new_incoming_tx',
notifyDoer: true, notifyDoer: true,
notifyOthers: true,
}, },
'TxProposalFinallyRejected': { 'TxProposalFinallyRejected': {
filename: 'txp_finally_rejected', filename: 'txp_finally_rejected',
notifyDoer: false, notifyDoer: false,
notifyOthers: true,
},
'TxConfirmation': {
filename: 'tx_confirmation',
notifyDoer: true,
notifyOthers: false,
}, },
}; };
@ -174,9 +162,6 @@ EmailService.prototype._applyTemplate = function(template, data, cb) {
EmailService.prototype._getRecipientsList = function(notification, emailType, cb) { EmailService.prototype._getRecipientsList = function(notification, emailType, cb) {
var self = this; var self = this;
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
if (err) return cb(err);
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) { self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
if (err) return cb(err); if (err) return cb(err);
if (_.isEmpty(preferences)) return cb(null, []); if (_.isEmpty(preferences)) return cb(null, []);
@ -187,7 +172,6 @@ EmailService.prototype._getRecipientsList = function(notification, emailType, cb
usedEmails[p.email] = true; usedEmails[p.email] = true;
if (notification.creatorId == p.copayerId && !emailType.notifyDoer) return; if (notification.creatorId == p.copayerId && !emailType.notifyDoer) return;
if (notification.creatorId != p.copayerId && !emailType.notifyOthers) return;
if (!_.contains(self.availableLanguages, p.language)) { if (!_.contains(self.availableLanguages, p.language)) {
if (p.language) { if (p.language) {
log.warn('Language for email "' + p.language + '" not available.'); log.warn('Language for email "' + p.language + '" not available.');
@ -195,24 +179,16 @@ EmailService.prototype._getRecipientsList = function(notification, emailType, cb
p.language = self.defaultLanguage; p.language = self.defaultLanguage;
} }
var unit;
if (wallet.coin != Defaults.COIN) {
unit = wallet.coin;
} else {
unit = p.unit || self.defaultUnit;
}
return { return {
copayerId: p.copayerId, copayerId: p.copayerId,
emailAddress: p.email, emailAddress: p.email,
language: p.language, language: p.language,
unit: unit, unit: p.unit || self.defaultUnit,
}; };
})); }));
return cb(null, recipients); return cb(null, recipients);
}); });
});
}; };
EmailService.prototype._getDataForTemplate = function(notification, recipient, cb) { EmailService.prototype._getDataForTemplate = function(notification, recipient, cb) {
@ -221,8 +197,7 @@ EmailService.prototype._getDataForTemplate = function(notification, recipient, c
// TODO: Declare these in BWU // TODO: Declare these in BWU
var UNIT_LABELS = { var UNIT_LABELS = {
btc: 'BTC', btc: 'BTC',
bit: 'bits', bit: 'bits'
bch: 'BCH',
}; };
var data = _.cloneDeep(notification.data); var data = _.cloneDeep(notification.data);

View File

@ -33,10 +33,9 @@ var errors = {
UPGRADE_NEEDED: 'Client app needs to be upgraded', UPGRADE_NEEDED: 'Client app needs to be upgraded',
WALLET_ALREADY_EXISTS: 'Wallet already exists', WALLET_ALREADY_EXISTS: 'Wallet already exists',
WALLET_FULL: 'Wallet full', WALLET_FULL: 'Wallet full',
WALLET_BUSY: 'Wallet is busy, try later', WALLET_LOCKED: 'Wallet is locked',
WALLET_NOT_COMPLETE: 'Wallet is not complete', WALLET_NOT_COMPLETE: 'Wallet is not complete',
WALLET_NOT_FOUND: 'Wallet not found', WALLET_NOT_FOUND: 'Wallet not found',
WALLET_NEED_SCAN: 'Wallet needs addresses scan',
}; };
var errorObjects = _.zipObject(_.map(errors, function(msg, code) { var errorObjects = _.zipObject(_.map(errors, function(msg, code) {

View File

@ -7,7 +7,6 @@ var log = require('npmlog');
var express = require('express'); var express = require('express');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var compression = require('compression'); var compression = require('compression');
var RateLimit = require('express-rate-limit');
var Common = require('./common'); var Common = require('./common');
var Defaults = Common.Defaults; var Defaults = Common.Defaults;
@ -17,7 +16,7 @@ var Stats = require('./stats');
log.disableColor(); log.disableColor();
log.debug = log.verbose; log.debug = log.verbose;
log.level = 'verbose'; log.level = 'info';
var ExpressApp = function() { var ExpressApp = function() {
this.app = express(); this.app = express();
@ -39,7 +38,7 @@ ExpressApp.prototype.start = function(opts, cb) {
this.app.use(function(req, res, next) { this.app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'x-signature,x-identity,x-session,x-client-version,x-wallet-id,X-Requested-With,Content-Type,Authorization'); res.setHeader('Access-Control-Allow-Headers', 'x-signature,x-identity,x-session,x-client-version,X-Requested-With,Content-Type,Authorization');
res.setHeader('x-service-version', WalletService.getServiceVersion()); res.setHeader('x-service-version', WalletService.getServiceVersion());
next(); next();
}); });
@ -54,16 +53,6 @@ ExpressApp.prototype.start = function(opts, cb) {
this.app.use(allowCORS); this.app.use(allowCORS);
this.app.enable('trust proxy'); this.app.enable('trust proxy');
// handle `abort` https://nodejs.org/api/http.html#http_event_abort
this.app.use(function(req, res, next) {
req.on('abort', function() {
log.warn('Request aborted by the client');
});
next();
});
var POST_LIMIT = 1024 * 100 /* Max POST 100 kb */ ; var POST_LIMIT = 1024 * 100 /* Max POST 100 kb */ ;
this.app.use(bodyParser.json({ this.app.use(bodyParser.json({
@ -75,10 +64,14 @@ ExpressApp.prototype.start = function(opts, cb) {
} else { } else {
var morgan = require('morgan'); var morgan = require('morgan');
morgan.token('walletId', function getId(req) { morgan.token('walletId', function getId(req) {
return req.walletId ? '<' + req.walletId + '>' : '<>'; return req.walletId
}); });
var logFormat = ':walletId :remote-addr :date[iso] ":method :url" :status :res[content-length] :response-time ":user-agent" '; morgan.token('copayerId', function getId(req) {
return req.copayerId
});
var logFormat = ':remote-addr :date[iso] ":method :url" :status :res[content-length] :response-time ":user-agent" :walletId :copayerId';
var logOpts = { var logOpts = {
skip: function(req, res) { skip: function(req, res) {
if (res.statusCode != 200) return false; if (res.statusCode != 200) return false;
@ -88,15 +81,15 @@ ExpressApp.prototype.start = function(opts, cb) {
this.app.use(morgan(logFormat, logOpts)); this.app.use(morgan(logFormat, logOpts));
} }
var router = express.Router();
var router = express.Router();
function returnError(err, res, req) { function returnError(err, res, req) {
if (err instanceof WalletService.ClientError) { if (err instanceof WalletService.ClientError) {
var status = (err.code == 'NOT_AUTHORIZED') ? 401 : 400; var status = (err.code == 'NOT_AUTHORIZED') ? 401 : 400;
if (!opts.disableLogs) if (!opts.disableLogs)
log.info('Client Err: ' + status + ' ' + req.url + ' ' + JSON.stringify(err)); log.info('Client Err: ' + status + ' ' + req.url + ' ' + err);
res.status(status).json({ res.status(status).json({
code: err.code, code: err.code,
@ -161,7 +154,6 @@ ExpressApp.prototype.start = function(opts, cb) {
message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body), message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
signature: credentials.signature, signature: credentials.signature,
clientVersion: req.header('x-client-version'), clientVersion: req.header('x-client-version'),
walletId: req.header('x-wallet-id'),
}; };
if (opts.allowSession) { if (opts.allowSession) {
auth.session = credentials.session; auth.session = credentials.session;
@ -169,34 +161,16 @@ ExpressApp.prototype.start = function(opts, cb) {
WalletService.getInstanceWithAuth(auth, function(err, server) { WalletService.getInstanceWithAuth(auth, function(err, server) {
if (err) return returnError(err, res, req); if (err) return returnError(err, res, req);
if (opts.onlySupportStaff && !server.copayerIsSupportStaff) {
return returnError(new WalletService.ClientError({
code: 'NOT_AUTHORIZED'
}), res, req);
}
// For logging // For logging
req.walletId = server.walletId; req.walletId = server.walletId;
req.copayerId = server.copayerId; req.copayerId = server.copayerId;
return cb(server); return cb(server);
}); });
}; };
var createWalletLimiter;
if (Defaults.RateLimit.createWallet && !opts.ignoreRateLimiter) {
log.info('', 'Limiting wallet creation per IP: %d req/h', (Defaults.RateLimit.createWallet.max / Defaults.RateLimit.createWallet.windowMs * 60 * 60 * 1000).toFixed(2))
createWalletLimiter = new RateLimit(Defaults.RateLimit.createWallet);
// router.use(/\/v\d+\/wallets\/$/, createWalletLimiter)
} else {
createWalletLimiter = function(req, res, next) {
next()
};
}
// DEPRECATED // DEPRECATED
router.post('/v1/wallets/', createWalletLimiter, function(req, res) { router.post('/v1/wallets/', function(req, res) {
logDeprecated(req); logDeprecated(req);
var server; var server;
try { try {
@ -213,7 +187,7 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
}); });
router.post('/v2/wallets/', createWalletLimiter, function(req, res) { router.post('/v2/wallets/', function(req, res) {
var server; var server;
try { try {
server = getServer(req, res); server = getServer(req, res);
@ -301,29 +275,6 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
}); });
router.get('/v1/wallets/:identifier/', function(req, res) {
getServerWithAuth(req, res, {
onlySupportStaff: true
}, function(server) {
var opts = {
identifier: req.params['identifier'],
};
server.getWalletFromIdentifier(opts, function(err, wallet) {
if (err) return returnError(err, res, req);
if (!wallet) return res.end();
server.walletId = wallet.id;
var opts = {};
if (req.query.includeExtendedInfo == '1') opts.includeExtendedInfo = true;
if (req.query.twoStep == '1') opts.twoStep = true;
server.getStatus(opts, function(err, status) {
if (err) return returnError(err, res, req);
res.json(status);
});
});
});
});
router.get('/v1/preferences/', function(req, res) { router.get('/v1/preferences/', function(req, res) {
getServerWithAuth(req, res, function(server) { getServerWithAuth(req, res, function(server) {
server.getPreferences({}, function(err, preferences) { server.getPreferences({}, function(err, preferences) {
@ -417,7 +368,6 @@ ExpressApp.prototype.start = function(opts, cb) {
router.get('/v1/balance/', function(req, res) { router.get('/v1/balance/', function(req, res) {
getServerWithAuth(req, res, function(server) { getServerWithAuth(req, res, function(server) {
var opts = {}; var opts = {};
if (req.query.coin) opts.coin = req.query.coin;
if (req.query.twoStep == '1') opts.twoStep = true; if (req.query.twoStep == '1') opts.twoStep = true;
server.getBalance(opts, function(err, balance) { server.getBalance(opts, function(err, balance) {
if (err) return returnError(err, res, req); if (err) return returnError(err, res, req);
@ -426,21 +376,8 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
}); });
var estimateFeeLimiter;
if (Defaults.RateLimit.estimateFee && !opts.ignoreRateLimiter) {
log.info('', 'Limiting estimate fee per IP: %d req/h', (Defaults.RateLimit.estimateFee.max / Defaults.RateLimit.estimateFee.windowMs * 60 * 60 * 1000).toFixed(2))
estimateFeeLimiter = new RateLimit(Defaults.RateLimit.estimateFee);
// router.use(/\/v\d+\/wallets\/$/, createWalletLimiter)
} else {
estimateFeeLimiter = function(req, res, next) {
next()
};
}
// DEPRECATED // DEPRECATED
router.get('/v1/feelevels/', estimateFeeLimiter, function(req, res) { router.get('/v1/feelevels/', function(req, res) {
logDeprecated(req); logDeprecated(req);
var opts = {}; var opts = {};
if (req.query.network) opts.network = req.query.network; if (req.query.network) opts.network = req.query.network;
@ -460,11 +397,9 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
}); });
router.get('/v2/feelevels/', estimateFeeLimiter, function(req, res) { router.get('/v2/feelevels/', function(req, res) {
var opts = {}; var opts = {};
if (req.query.coin) opts.coin = req.query.coin;
if (req.query.network) opts.network = req.query.network; if (req.query.network) opts.network = req.query.network;
var server; var server;
try { try {
server = getServer(req, res); server = getServer(req, res);
@ -611,7 +546,6 @@ ExpressApp.prototype.start = function(opts, cb) {
router.get('/v1/stats/', function(req, res) { router.get('/v1/stats/', function(req, res) {
var opts = {}; var opts = {};
if (req.query.network) opts.network = req.query.network; if (req.query.network) opts.network = req.query.network;
if (req.query.coin) opts.coin = req.query.coin;
if (req.query.from) opts.from = req.query.from; if (req.query.from) opts.from = req.query.from;
if (req.query.to) opts.to = req.query.to; if (req.query.to) opts.to = req.query.to;
@ -701,22 +635,18 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
router.get('/v1/fiatrates/:code/', function(req, res) { router.get('/v1/fiatrates/:code/', function(req, res) {
var server; getServerWithAuth(req, res, function(server) {
var opts = { var opts = {
code: req.params['code'], code: req.params['code'],
provider: req.query.provider, source: req.query.source,
ts: +req.query.ts, ts: +req.query.ts,
}; };
try {
server = getServer(req, res);
} catch (ex) {
return returnError(ex, res, req);
}
server.getFiatRate(opts, function(err, rates) { server.getFiatRate(opts, function(err, rates) {
if (err) return returnError(err, res, req); if (err) return returnError(err, res, req);
res.json(rates); res.json(rates);
}); });
}); });
});
router.post('/v1/pushnotifications/subscriptions/', function(req, res) { router.post('/v1/pushnotifications/subscriptions/', function(req, res) {
getServerWithAuth(req, res, function(server) { getServerWithAuth(req, res, function(server) {
@ -727,47 +657,9 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
}); });
// DEPRECATED
router.delete('/v1/pushnotifications/subscriptions/', function(req, res) { router.delete('/v1/pushnotifications/subscriptions/', function(req, res) {
logDeprecated(req);
getServerWithAuth(req, res, function(server) { getServerWithAuth(req, res, function(server) {
server.pushNotificationsUnsubscribe({ server.pushNotificationsUnsubscribe(function(err, response) {
token: 'dummy'
}, function(err, response) {
if (err) return returnError(err, res, req);
res.json(response);
});
});
});
router.delete('/v2/pushnotifications/subscriptions/:token', function(req, res) {
var opts = {
token: req.params['token'],
};
getServerWithAuth(req, res, function(server) {
server.pushNotificationsUnsubscribe(opts, function(err, response) {
if (err) return returnError(err, res, req);
res.json(response);
});
});
});
router.post('/v1/txconfirmations/', function(req, res) {
getServerWithAuth(req, res, function(server) {
server.txConfirmationSubscribe(req.body, function(err, response) {
if (err) return returnError(err, res, req);
res.json(response);
});
});
});
router.delete('/v1/txconfirmations/:txid', function(req, res) {
var opts = {
txid: req.params['txid'],
};
getServerWithAuth(req, res, function(server) {
server.txConfirmationUnsubscribe(opts, function(err, response) {
if (err) return returnError(err, res, req); if (err) return returnError(err, res, req);
res.json(response); res.json(response);
}); });

View File

@ -27,13 +27,11 @@ function Lock(opts) {
} }
}; };
Lock.prototype.runLocked = function(token, cb, task, waitTime) { Lock.prototype.runLocked = function(token, cb, task) {
$.shouldBeDefined(token); $.shouldBeDefined(token);
waitTime = waitTime || 5 * 1000; this.lock.locked(token, 5 * 1000, 5 * 60 * 1000, function(err, release) {
if (err) return cb(Errors.WALLET_LOCKED);
this.lock.locked(token, waitTime , 5 * 60 * 1000, function(err, release) {
if (err) return cb(Errors.WALLET_BUSY);
var _cb = function() { var _cb = function() {
cb.apply(null, arguments); cb.apply(null, arguments);
release(); release();

View File

@ -3,14 +3,8 @@
var $ = require('preconditions').singleton(); var $ = require('preconditions').singleton();
var _ = require('lodash'); var _ = require('lodash');
var Bitcore = { var Bitcore = require('bitcore-lib');
'btc': require('bitcore-lib'), var Constants = require('../common/constants');
'bch': require('bitcore-lib-cash'),
};
var Common = require('../common');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
function Address() {}; function Address() {};
@ -19,8 +13,6 @@ Address.create = function(opts) {
var x = new Address(); var x = new Address();
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
x.version = '1.0.0'; x.version = '1.0.0';
x.createdOn = Math.floor(Date.now() / 1000); x.createdOn = Math.floor(Date.now() / 1000);
x.address = opts.address; x.address = opts.address;
@ -28,8 +20,7 @@ Address.create = function(opts) {
x.isChange = opts.isChange; x.isChange = opts.isChange;
x.path = opts.path; x.path = opts.path;
x.publicKeys = opts.publicKeys; x.publicKeys = opts.publicKeys;
x.coin = opts.coin; x.network = Bitcore.Address(x.address).toObject().network;
x.network = Bitcore[opts.coin].Address(x.address).toObject().network;
x.type = opts.type || Constants.SCRIPT_TYPES.P2SH; x.type = opts.type || Constants.SCRIPT_TYPES.P2SH;
x.hasActivity = undefined; x.hasActivity = undefined;
return x; return x;
@ -42,7 +33,6 @@ Address.fromObj = function(obj) {
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
x.address = obj.address; x.address = obj.address;
x.walletId = obj.walletId; x.walletId = obj.walletId;
x.coin = obj.coin || Defaults.COIN;
x.network = obj.network; x.network = obj.network;
x.isChange = obj.isChange; x.isChange = obj.isChange;
x.path = obj.path; x.path = obj.path;
@ -52,22 +42,22 @@ Address.fromObj = function(obj) {
return x; return x;
}; };
Address._deriveAddress = function(scriptType, publicKeyRing, path, m, coin, network) { Address._deriveAddress = function(scriptType, publicKeyRing, path, m, network) {
$.checkArgument(Utils.checkValueInCollection(scriptType, Constants.SCRIPT_TYPES)); $.checkArgument(_.contains(_.values(Constants.SCRIPT_TYPES), scriptType));
var publicKeys = _.map(publicKeyRing, function(item) { var publicKeys = _.map(publicKeyRing, function(item) {
var xpub = new Bitcore[coin].HDPublicKey(item.xPubKey); var xpub = new Bitcore.HDPublicKey(item.xPubKey);
return xpub.deriveChild(path).publicKey; return xpub.derive(path).publicKey;
}); });
var bitcoreAddress; var bitcoreAddress;
switch (scriptType) { switch (scriptType) {
case Constants.SCRIPT_TYPES.P2SH: case Constants.SCRIPT_TYPES.P2SH:
bitcoreAddress = Bitcore[coin].Address.createMultisig(publicKeys, m, network); bitcoreAddress = Bitcore.Address.createMultisig(publicKeys, m, network);
break; break;
case Constants.SCRIPT_TYPES.P2PKH: case Constants.SCRIPT_TYPES.P2PKH:
$.checkState(_.isArray(publicKeys) && publicKeys.length == 1); $.checkState(_.isArray(publicKeys) && publicKeys.length == 1);
bitcoreAddress = Bitcore[coin].Address.fromPublicKey(publicKeys[0], network); bitcoreAddress = Bitcore.Address.fromPublicKey(publicKeys[0], network);
break; break;
} }
@ -78,10 +68,9 @@ Address._deriveAddress = function(scriptType, publicKeyRing, path, m, coin, netw
}; };
}; };
Address.derive = function(walletId, scriptType, publicKeyRing, path, m, coin, network, isChange) { Address.derive = function(walletId, scriptType, publicKeyRing, path, m, network, isChange) {
var raw = Address._deriveAddress(scriptType, publicKeyRing, path, m, coin, network); var raw = Address._deriveAddress(scriptType, publicKeyRing, path, m, network);
return Address.create(_.extend(raw, { return Address.create(_.extend(raw, {
coin: coin,
walletId: walletId, walletId: walletId,
type: scriptType, type: scriptType,
isChange: isChange, isChange: isChange,

View File

@ -1,8 +1,8 @@
var _ = require('lodash'); var _ = require('lodash');
var $ = require('preconditions').singleton(); var $ = require('preconditions').singleton();
var Bitcore = require('bitcore-lib');
var Constants = require('../common/constants'); var Constants = require('../common/constants');
var Utils = require('../common/utils');
function AddressManager() {}; function AddressManager() {};
@ -13,7 +13,7 @@ AddressManager.create = function(opts) {
x.version = 2; x.version = 2;
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45; x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
$.checkState(Utils.checkValueInCollection(x.derivationStrategy, Constants.DERIVATION_STRATEGIES)); $.checkState(_.contains(_.values(Constants.DERIVATION_STRATEGIES), x.derivationStrategy));
x.receiveAddressIndex = 0; x.receiveAddressIndex = 0;
x.changeAddressIndex = 0; x.changeAddressIndex = 0;
@ -55,18 +55,6 @@ AddressManager.prototype.rewindIndex = function(isChange, n) {
} }
}; };
AddressManager.prototype.getCurrentIndex = function(isChange) {
return isChange ? this.changeAddressIndex : this.receiveAddressIndex;
};
AddressManager.prototype.getBaseAddressPath = function(isChange) {
return 'm/' +
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
(isChange ? 1 : 0) + '/' +
0;
};
AddressManager.prototype.getCurrentAddressPath = function(isChange) { AddressManager.prototype.getCurrentAddressPath = function(isChange) {
return 'm/' + return 'm/' +
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') + (this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +

View File

@ -10,16 +10,12 @@ var Address = require('./address');
var AddressManager = require('./addressmanager'); var AddressManager = require('./addressmanager');
var Bitcore = require('bitcore-lib'); var Bitcore = require('bitcore-lib');
var Common = require('../common'); var Constants = require('../common/constants');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
function Copayer() {}; function Copayer() {};
Copayer._xPubToCopayerId = function(coin, xpub) { Copayer._xPubToCopayerId = function(xpub) {
var str = coin == Defaults.COIN ? xpub : coin + xpub; var hash = sjcl.hash.sha256.hash(xpub);
var hash = sjcl.hash.sha256.hash(str);
return sjcl.codec.hex.fromBits(hash); return sjcl.codec.hex.fromBits(hash);
}; };
@ -29,17 +25,14 @@ Copayer.create = function(opts) {
.checkArgument(opts.requestPubKey, 'Missing copayer request public key') .checkArgument(opts.requestPubKey, 'Missing copayer request public key')
.checkArgument(opts.signature, 'Missing copayer request public key signature'); .checkArgument(opts.signature, 'Missing copayer request public key signature');
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
opts.copayerIndex = opts.copayerIndex || 0; opts.copayerIndex = opts.copayerIndex || 0;
var x = new Copayer(); var x = new Copayer();
x.version = 2; x.version = 2;
x.createdOn = Math.floor(Date.now() / 1000); x.createdOn = Math.floor(Date.now() / 1000);
x.coin = opts.coin;
x.xPubKey = opts.xPubKey; x.xPubKey = opts.xPubKey;
x.id = Copayer._xPubToCopayerId(opts.coin, x.xPubKey); x.id = Copayer._xPubToCopayerId(x.xPubKey);
x.name = opts.name; x.name = opts.name;
x.requestPubKey = opts.requestPubKey; x.requestPubKey = opts.requestPubKey;
x.signature = opts.signature; x.signature = opts.signature;
@ -66,7 +59,6 @@ Copayer.fromObj = function(obj) {
x.version = obj.version; x.version = obj.version;
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
x.coin = obj.coin || Defaults.COIN;
x.id = obj.id; x.id = obj.id;
x.name = obj.name; x.name = obj.name;
x.xPubKey = obj.xPubKey; x.xPubKey = obj.xPubKey;
@ -95,7 +87,7 @@ Copayer.prototype.createAddress = function(wallet, isChange) {
$.checkState(wallet.isComplete()); $.checkState(wallet.isComplete());
var path = this.addressManager.getNewAddressPath(isChange); var path = this.addressManager.getNewAddressPath(isChange);
var address = Address.derive(wallet.id, wallet.addressType, wallet.publicKeyRing, path, wallet.m, wallet.coin, wallet.network, isChange); var address = Address.derive(wallet.id, wallet.addressType, wallet.publicKeyRing, path, wallet.m, wallet.network, isChange);
return address; return address;
}; };

View File

@ -9,7 +9,5 @@ Model.Preferences = require('./preferences');
Model.Email = require('./email'); Model.Email = require('./email');
Model.TxNote = require('./txnote'); Model.TxNote = require('./txnote');
Model.Session = require('./session'); Model.Session = require('./session');
Model.PushNotificationSub = require('./pushnotificationsub');
Model.TxConfirmationSub = require('./txconfirmationsub');
module.exports = Model; module.exports = Model;

View File

@ -1,32 +0,0 @@
'use strict';
function PushNotificationSub() {};
PushNotificationSub.create = function(opts) {
opts = opts || {};
var x = new PushNotificationSub();
x.version = '1.0.0';
x.createdOn = Math.floor(Date.now() / 1000);
x.copayerId = opts.copayerId;
x.token = opts.token;
x.packageName = opts.packageName;
x.platform = opts.platform;
return x;
};
PushNotificationSub.fromObj = function(obj) {
var x = new PushNotificationSub();
x.version = obj.version;
x.createdOn = obj.createdOn;
x.copayerId = obj.copayerId;
x.token = obj.token;
x.packageName = obj.packageName;
x.platform = obj.platform;
return x;
};
module.exports = PushNotificationSub;

View File

@ -1,32 +0,0 @@
'use strict';
function TxConfirmationSub() {};
TxConfirmationSub.create = function(opts) {
opts = opts || {};
var x = new TxConfirmationSub();
x.version = 1;
x.createdOn = Math.floor(Date.now() / 1000);
x.walletId = opts.walletId;
x.copayerId = opts.copayerId;
x.txid = opts.txid;
x.isActive = true;
return x;
};
TxConfirmationSub.fromObj = function(obj) {
var x = new TxConfirmationSub();
x.version = obj.version;
x.createdOn = obj.createdOn;
x.walletId = obj.walletId;
x.copayerId = obj.copayerId;
x.txid = obj.txid;
x.isActive = obj.isActive;
return x;
};
module.exports = TxConfirmationSub;

View File

@ -7,15 +7,11 @@ var log = require('npmlog');
log.debug = log.verbose; log.debug = log.verbose;
log.disableColor(); log.disableColor();
var Bitcore = { var Bitcore = require('bitcore-lib');
'btc': require('bitcore-lib'),
'bch': require('bitcore-lib-cash'),
};
var Common = require('../common'); var Common = require('../common');
var Constants = Common.Constants, var Constants = Common.Constants;
Defaults = Common.Defaults, var Defaults = Common.Defaults;
Utils = Common.Utils;
var TxProposalLegacy = require('./txproposal_legacy'); var TxProposalLegacy = require('./txproposal_legacy');
var TxProposalAction = require('./txproposalaction'); var TxProposalAction = require('./txproposalaction');
@ -25,9 +21,6 @@ function TxProposal() {};
TxProposal.create = function(opts) { TxProposal.create = function(opts) {
opts = opts || {}; opts = opts || {};
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
var x = new TxProposal(); var x = new TxProposal();
x.version = 3; x.version = 3;
@ -37,8 +30,6 @@ TxProposal.create = function(opts) {
x.id = opts.id || Uuid.v4(); x.id = opts.id || Uuid.v4();
x.walletId = opts.walletId; x.walletId = opts.walletId;
x.creatorId = opts.creatorId; x.creatorId = opts.creatorId;
x.coin = opts.coin;
x.network = opts.network;
x.message = opts.message; x.message = opts.message;
x.payProUrl = opts.payProUrl; x.payProUrl = opts.payProUrl;
x.changeAddress = opts.changeAddress; x.changeAddress = opts.changeAddress;
@ -60,11 +51,15 @@ TxProposal.create = function(opts) {
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos; x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
x.addressType = opts.addressType || (x.walletN > 1 ? Constants.SCRIPT_TYPES.P2SH : Constants.SCRIPT_TYPES.P2PKH); x.addressType = opts.addressType || (x.walletN > 1 ? Constants.SCRIPT_TYPES.P2SH : Constants.SCRIPT_TYPES.P2PKH);
$.checkState(Utils.checkValueInCollection(x.addressType, Constants.SCRIPT_TYPES)); $.checkState(_.contains(_.values(Constants.SCRIPT_TYPES), x.addressType));
x.customData = opts.customData; x.customData = opts.customData;
x.amount = x.getTotalAmount(); x.amount = x.getTotalAmount();
try {
x.network = opts.network || Bitcore.Address(x.outputs[0].toAddress).toObject().network;
} catch (ex) {}
$.checkState(_.contains(_.values(Constants.NETWORKS), x.network));
x.setInputs(opts.inputs); x.setInputs(opts.inputs);
x.fee = opts.fee; x.fee = opts.fee;
@ -84,7 +79,6 @@ TxProposal.fromObj = function(obj) {
x.id = obj.id; x.id = obj.id;
x.walletId = obj.walletId; x.walletId = obj.walletId;
x.creatorId = obj.creatorId; x.creatorId = obj.creatorId;
x.coin = obj.coin || Defaults.COIN;
x.network = obj.network; x.network = obj.network;
x.outputs = obj.outputs; x.outputs = obj.outputs;
x.amount = obj.amount; x.amount = obj.amount;
@ -142,9 +136,9 @@ TxProposal.prototype._updateStatus = function() {
TxProposal.prototype._buildTx = function() { TxProposal.prototype._buildTx = function() {
var self = this; var self = this;
var t = new Bitcore[self.coin].Transaction(); var t = new Bitcore.Transaction();
$.checkState(Utils.checkValueInCollection(self.addressType, Constants.SCRIPT_TYPES)); $.checkState(_.contains(_.values(Constants.SCRIPT_TYPES), self.addressType));
switch (self.addressType) { switch (self.addressType) {
case Constants.SCRIPT_TYPES.P2SH: case Constants.SCRIPT_TYPES.P2SH:
@ -161,7 +155,7 @@ TxProposal.prototype._buildTx = function() {
_.each(self.outputs, function(o) { _.each(self.outputs, function(o) {
$.checkState(o.script || o.toAddress, 'Output should have either toAddress or script specified'); $.checkState(o.script || o.toAddress, 'Output should have either toAddress or script specified');
if (o.script) { if (o.script) {
t.addOutput(new Bitcore[self.coin].Transaction.Output({ t.addOutput(new Bitcore.Transaction.Output({
script: o.script, script: o.script,
satoshis: o.amount satoshis: o.amount
})); }));
@ -226,6 +220,10 @@ TxProposal.prototype.getBitcoreTx = function() {
return t; return t;
}; };
TxProposal.prototype.getNetworkName = function() {
return this.network;
};
TxProposal.prototype.getRawTx = function() { TxProposal.prototype.getRawTx = function() {
var t = this.getBitcoreTx(); var t = this.getBitcoreTx();
@ -325,23 +323,21 @@ TxProposal.prototype.addAction = function(copayerId, type, comment, signatures,
TxProposal.prototype._addSignaturesToBitcoreTx = function(tx, signatures, xpub) { TxProposal.prototype._addSignaturesToBitcoreTx = function(tx, signatures, xpub) {
var self = this; var self = this;
var bitcore = Bitcore[self.coin];
if (signatures.length != this.inputs.length) if (signatures.length != this.inputs.length)
throw new Error('Number of signatures does not match number of inputs'); throw new Error('Number of signatures does not match number of inputs');
var i = 0, var i = 0,
x = new bitcore.HDPublicKey(xpub); x = new Bitcore.HDPublicKey(xpub);
_.each(signatures, function(signatureHex) { _.each(signatures, function(signatureHex) {
var input = self.inputs[i]; var input = self.inputs[i];
try { try {
var signature = bitcore.crypto.Signature.fromString(signatureHex); var signature = Bitcore.crypto.Signature.fromString(signatureHex);
var pub = x.deriveChild(self.inputPaths[i]).publicKey; var pub = x.derive(self.inputPaths[i]).publicKey;
var s = { var s = {
inputIndex: i, inputIndex: i,
signature: signature, signature: signature,
sigtype: bitcore.crypto.Signature.SIGHASH_ALL | bitcore.crypto.Signature.SIGHASH_FORKID, sigtype: Bitcore.crypto.Signature.SIGHASH_ALL,
publicKey: pub, publicKey: pub,
}; };
tx.inputs[i].addSignature(tx, s); tx.inputs[i].addSignature(tx, s);

View File

@ -54,7 +54,6 @@ TxProposal.fromObj = function(obj) {
return TxProposalAction.fromObj(action); return TxProposalAction.fromObj(action);
}); });
x.outputOrder = obj.outputOrder; x.outputOrder = obj.outputOrder;
x.coin = obj.coin || Defaults.COIN;
x.network = obj.network; x.network = obj.network;
x.fee = obj.fee; x.fee = obj.fee;
x.feePerKb = obj.feePerKb; x.feePerKb = obj.feePerKb;
@ -94,6 +93,10 @@ TxProposal.prototype.getBitcoreTx = function() {
throwUnsupportedError(); throwUnsupportedError();
}; };
TxProposal.prototype.getNetworkName = function() {
return Bitcore.Address(this.changeAddress.address).toObject().network;
};
TxProposal.prototype.getRawTx = function() { TxProposal.prototype.getRawTx = function() {
throwUnsupportedError(); throwUnsupportedError();
}; };

View File

@ -2,7 +2,6 @@
var _ = require('lodash'); var _ = require('lodash');
var util = require('util'); var util = require('util');
var log = require('npmlog');
var $ = require('preconditions').singleton(); var $ = require('preconditions').singleton();
var Uuid = require('uuid'); var Uuid = require('uuid');
@ -10,10 +9,7 @@ var Address = require('./address');
var Copayer = require('./copayer'); var Copayer = require('./copayer');
var AddressManager = require('./addressmanager'); var AddressManager = require('./addressmanager');
var Common = require('../common'); var Constants = require('../common/constants');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
function Wallet() {}; function Wallet() {};
@ -24,8 +20,6 @@ Wallet.create = function(opts) {
$.shouldBeNumber(opts.m); $.shouldBeNumber(opts.m);
$.shouldBeNumber(opts.n); $.shouldBeNumber(opts.n);
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
x.version = '1.0.0'; x.version = '1.0.0';
x.createdOn = Math.floor(Date.now() / 1000); x.createdOn = Math.floor(Date.now() / 1000);
@ -39,7 +33,6 @@ Wallet.create = function(opts) {
x.addressIndex = 0; x.addressIndex = 0;
x.copayers = []; x.copayers = [];
x.pubKey = opts.pubKey; x.pubKey = opts.pubKey;
x.coin = opts.coin;
x.network = opts.network; x.network = opts.network;
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45; x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
x.addressType = opts.addressType || Constants.SCRIPT_TYPES.P2SH; x.addressType = opts.addressType || Constants.SCRIPT_TYPES.P2SH;
@ -71,7 +64,6 @@ Wallet.fromObj = function(obj) {
return Copayer.fromObj(copayer); return Copayer.fromObj(copayer);
}); });
x.pubKey = obj.pubKey; x.pubKey = obj.pubKey;
x.coin = obj.coin || Defaults.COIN;
x.network = obj.network; x.network = obj.network;
x.derivationStrategy = obj.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45; x.derivationStrategy = obj.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
x.addressType = obj.addressType || Constants.SCRIPT_TYPES.P2SH; x.addressType = obj.addressType || Constants.SCRIPT_TYPES.P2SH;
@ -113,7 +105,6 @@ Wallet.prototype._updatePublicKeyRing = function() {
}; };
Wallet.prototype.addCopayer = function(copayer) { Wallet.prototype.addCopayer = function(copayer) {
$.checkState(copayer.coin == this.coin);
this.copayers.push(copayer); this.copayers.push(copayer);
if (this.copayers.length < this.n) return; if (this.copayers.length < this.n) return;
@ -143,6 +134,10 @@ Wallet.prototype.getCopayer = function(copayerId) {
}); });
}; };
Wallet.prototype.getNetworkName = function() {
return this.network;
};
Wallet.prototype.isComplete = function() { Wallet.prototype.isComplete = function() {
return this.status == 'complete'; return this.status == 'complete';
}; };
@ -157,8 +152,7 @@ Wallet.prototype.createAddress = function(isChange) {
var self = this; var self = this;
var path = this.addressManager.getNewAddressPath(isChange); var path = this.addressManager.getNewAddressPath(isChange);
log.verbose('Deriving addr:' + path); var address = Address.derive(self.id, this.addressType, this.publicKeyRing, path, this.m, this.network, isChange);
var address = Address.derive(self.id, this.addressType, this.publicKeyRing, path, this.m, this.coin, this.network, isChange);
return address; return address;
}; };

View File

@ -9,7 +9,6 @@ var Storage = require('./storage');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var Utils = require('./common/utils'); var Utils = require('./common/utils');
var Defaults = require('./common/defaults');
var Model = require('./model'); var Model = require('./model');
var sjcl = require('sjcl'); var sjcl = require('sjcl');
var log = require('npmlog'); var log = require('npmlog');
@ -34,10 +33,6 @@ var PUSHNOTIFICATIONS_TYPES = {
'TxProposalFinallyRejected': { 'TxProposalFinallyRejected': {
filename: 'txp_finally_rejected', filename: 'txp_finally_rejected',
}, },
'TxConfirmation': {
filename: 'tx_confirmation',
notifyCreatorOnly: true,
},
}; };
function PushNotificationsService() {}; function PushNotificationsService() {};
@ -65,10 +60,6 @@ PushNotificationsService.prototype.start = function(opts, cb) {
self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc'; self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc';
self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || ''; self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || '';
self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl; self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl;
self.authorizationKey = opts.pushNotificationsOpts.authorizationKey;
if (!self.authorizationKey) return cb(new Error('Missing authorizationKey attribute in configuration.'))
async.parallel([ async.parallel([
function(done) { function(done) {
@ -116,7 +107,7 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio
log.debug('Should send notification: ', should); log.debug('Should send notification: ', should);
if (!should) return cb(); if (!should) return cb();
self._getRecipientsList(notification, notifType, function(err, recipientsList) { self._getRecipientsList(notification, function(err, recipientsList) {
if (err) return cb(err); if (err) return cb(err);
async.waterfall([ async.waterfall([
@ -126,40 +117,34 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio
}, },
function(contents, next) { function(contents, next) {
async.map(recipientsList, function(recipient, next) { async.map(recipientsList, function(recipient, next) {
var opts = {};
var content = contents[recipient.language]; var content = contents[recipient.language];
opts.users = [notification.walletId + '$' + recipient.copayerId];
self.storage.fetchPushNotificationSubs(recipient.copayerId, function(err, subs) { opts.android = {
if (err) return next(err); "data": {
"title": content.plain.subject,
var notifications = _.map(subs, function(sub) { "message": content.plain.body,
return { "walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)),
to: sub.token, "notId": Math.floor(Math.random() * 100000) + 1
priority: 'high', }
restricted_package_name: sub.packageName,
notification: {
title: content.plain.subject,
body: content.plain.body,
sound: "default",
click_action: "FCM_PLUGIN_ACTIVITY",
icon: "fcm_push_icon",
},
data: {
walletId: sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)),
copayerId: sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(recipient.copayerId))
},
}; };
}); opts.ios = {
return next(err, notifications); "alert": {
}); "title": content.plain.subject,
}, function(err, allNotifications) { "body": content.plain.body
if (err) return next(err);
return next(null, _.flatten(allNotifications));
});
}, },
function(notifications, next) { "sound": "default",
async.each(notifications, "payload": {
function(notification, next) { "walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId))
self._makeRequest(notification, function(err, response) { }
};
return next(err, opts);
}, next);
},
function(optsList, next) {
async.each(optsList,
function(opts, next) {
self._makeRequest(opts, function(err, response) {
if (err) log.error(err); if (err) log.error(err);
if (response) { if (response) {
log.debug('Request status: ', response.statusCode); log.debug('Request status: ', response.statusCode);
@ -189,21 +174,16 @@ PushNotificationsService.prototype._checkShouldSendNotif = function(notification
if (notification.type != 'NewTxProposal') return cb(null, true); if (notification.type != 'NewTxProposal') return cb(null, true);
self.storage.fetchWallet(notification.walletId, function(err, wallet) { self.storage.fetchWallet(notification.walletId, function(err, wallet) {
return cb(err, wallet && wallet.m > 1); return cb(err, wallet.m > 1);
}); });
}; };
PushNotificationsService.prototype._getRecipientsList = function(notification, notificationType, cb) { PushNotificationsService.prototype._getRecipientsList = function(notification, cb) {
var self = this; var self = this;
self.storage.fetchWallet(notification.walletId, function(err, wallet) { self.storage.fetchWallet(notification.walletId, function(err, wallet) {
if (err) return cb(err); if (err) return cb(err);
var unit;
if (wallet.coin != Defaults.COIN) {
unit = wallet.coin;
}
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) { self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
if (err) log.error(err); if (err) log.error(err);
@ -220,23 +200,22 @@ PushNotificationsService.prototype._getRecipientsList = function(notification, n
return { return {
copayerId: p.copayerId, copayerId: p.copayerId,
language: p.language, language: p.language,
unit: unit || p.unit || self.defaultUnit, unit: p.unit,
}; };
})); }));
recipientPreferences = _.indexBy(recipientPreferences, 'copayerId'); recipientPreferences = _.indexBy(recipientPreferences, 'copayerId');
var recipientsList = _.compact(_.map(wallet.copayers, function(copayer) { var recipientsList = _.reject(_.map(wallet.copayers, function(copayer) {
if ((copayer.id == notification.creatorId && notificationType.notifyCreatorOnly) ||
(copayer.id != notification.creatorId && !notificationType.notifyCreatorOnly)) {
var p = recipientPreferences[copayer.id] || {}; var p = recipientPreferences[copayer.id] || {};
return { return {
copayerId: copayer.id, copayerId: copayer.id,
language: p.language || self.defaultLanguage, language: p.language || self.defaultLanguage,
unit: unit || p.unit || self.defaultUnit, unit: p.unit || self.defaultUnit,
} }
} }), {
})); copayerId: notification.creatorId
});
return cb(null, recipientsList); return cb(null, recipientsList);
}); });
@ -281,8 +260,7 @@ PushNotificationsService.prototype._getDataForTemplate = function(notification,
var self = this; var self = this;
var UNIT_LABELS = { var UNIT_LABELS = {
btc: 'BTC', btc: 'BTC',
bit: 'bits', bit: 'bits'
bch: 'BCH',
}; };
var data = _.cloneDeep(notification.data); var data = _.cloneDeep(notification.data);
@ -297,7 +275,7 @@ PushNotificationsService.prototype._getDataForTemplate = function(notification,
} }
self.storage.fetchWallet(notification.walletId, function(err, wallet) { self.storage.fetchWallet(notification.walletId, function(err, wallet) {
if (err || !wallet) return cb(err); if (err) return cb(err);
data.walletId = wallet.id; data.walletId = wallet.id;
data.walletName = wallet.name; data.walletName = wallet.name;
@ -382,12 +360,10 @@ PushNotificationsService.prototype._makeRequest = function(opts, cb) {
url: self.pushServerUrl + '/send', url: self.pushServerUrl + '/send',
method: 'POST', method: 'POST',
json: true, json: true,
headers: { body: opts
'Content-Type': 'application/json', }, function(err, response) {
'Authorization': 'key=' + self.authorizationKey, return cb(err, response);
}, });
body: opts,
}, cb);
}; };
module.exports = PushNotificationsService; module.exports = PushNotificationsService;

File diff suppressed because it is too large Load Diff

View File

@ -15,13 +15,12 @@ var config = require('../config');
var storage = require('./storage'); var storage = require('./storage');
var INITIAL_DATE = '2018-01-01'; var INITIAL_DATE = '2015-01-01';
function Stats(opts) { function Stats(opts) {
opts = opts || {}; opts = opts || {};
this.network = opts.network || 'livenet'; this.network = opts.network || 'livenet';
this.coin = opts.coin || 'btc';
this.from = moment(opts.from || INITIAL_DATE); this.from = moment(opts.from || INITIAL_DATE);
this.to = moment(opts.to); this.to = moment(opts.to);
this.fromTs = this.from.startOf('day').valueOf(); this.fromTs = this.from.startOf('day').valueOf();
@ -71,7 +70,7 @@ Stats.prototype._getNewWallets = function(cb) {
function getLastDate(cb) { function getLastDate(cb) {
self.db.collection('stats_wallets') self.db.collection('stats_wallets')
.find({'_id.coin': self.coin}) .find({})
.sort({ .sort({
'_id.day': -1 '_id.day': -1
}) })
@ -92,7 +91,6 @@ Stats.prototype._getNewWallets = function(cb) {
var key = { var key = {
day: +day, day: +day,
network: this.network, network: this.network,
coin: this.coin
}; };
var value = { var value = {
count: 1 count: 1
@ -129,7 +127,6 @@ Stats.prototype._getNewWallets = function(cb) {
self.db.collection('stats_wallets') self.db.collection('stats_wallets')
.find({ .find({
'_id.network': self.network, '_id.network': self.network,
'_id.coin': self.coin,
'_id.day': { '_id.day': {
$gte: self.fromTs, $gte: self.fromTs,
$lte: self.toTs, $lte: self.toTs,
@ -145,7 +142,6 @@ Stats.prototype._getNewWallets = function(cb) {
var day = moment(record._id.day).format('YYYYMMDD'); var day = moment(record._id.day).format('YYYYMMDD');
return { return {
day: day, day: day,
coin: record._id.coin,
count: record.value.count, count: record.value.count,
}; };
}); });
@ -185,7 +181,7 @@ Stats.prototype._getTxProposals = function(cb) {
function getLastDate(cb) { function getLastDate(cb) {
self.db.collection('stats_txps') self.db.collection('stats_txps')
.find({'_id.coin': self.coin }) .find({})
.sort({ .sort({
'_id.day': -1 '_id.day': -1
}) })
@ -206,7 +202,6 @@ Stats.prototype._getTxProposals = function(cb) {
var key = { var key = {
day: +day, day: +day,
network: this.network, network: this.network,
coin: this.coin
}; };
var value = { var value = {
count: 1, count: 1,
@ -248,7 +243,6 @@ Stats.prototype._getTxProposals = function(cb) {
self.db.collection('stats_txps') self.db.collection('stats_txps')
.find({ .find({
'_id.network': self.network, '_id.network': self.network,
'_id.coin': self.coin,
'_id.day': { '_id.day': {
$gte: self.fromTs, $gte: self.fromTs,
$lte: self.toTs, $lte: self.toTs,
@ -268,7 +262,6 @@ Stats.prototype._getTxProposals = function(cb) {
var day = moment(record._id.day).format('YYYYMMDD'); var day = moment(record._id.day).format('YYYYMMDD');
stats.nbByDay.push({ stats.nbByDay.push({
day: day, day: day,
coin: record._id.coin,
count: record.value.count, count: record.value.count,
}); });
stats.amountByDay.push({ stats.amountByDay.push({

View File

@ -7,7 +7,7 @@ var log = require('npmlog');
log.debug = log.verbose; log.debug = log.verbose;
log.disableColor(); log.disableColor();
var util = require('util'); var util = require('util');
var Bitcore = require('bitcore-lib');
var mongodb = require('mongodb'); var mongodb = require('mongodb');
var Model = require('./model'); var Model = require('./model');
@ -24,8 +24,6 @@ var collections = {
FIAT_RATES: 'fiat_rates', FIAT_RATES: 'fiat_rates',
TX_NOTES: 'tx_notes', TX_NOTES: 'tx_notes',
SESSIONS: 'sessions', SESSIONS: 'sessions',
PUSH_NOTIFICATION_SUBS: 'push_notification_subs',
TX_CONFIRMATION_SUBS: 'tx_confirmation_subs',
}; };
var Storage = function(opts) { var Storage = function(opts) {
@ -40,9 +38,6 @@ Storage.prototype._createIndexes = function() {
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({ this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
copayerId: 1 copayerId: 1
}); });
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
walletId: 1
});
this.db.collection(collections.TXS).createIndex({ this.db.collection(collections.TXS).createIndex({
walletId: 1, walletId: 1,
id: 1, id: 1,
@ -56,9 +51,6 @@ Storage.prototype._createIndexes = function() {
walletId: 1, walletId: 1,
createdOn: -1, createdOn: -1,
}); });
this.db.collection(collections.TXS).createIndex({
txid: 1
});
this.db.collection(collections.NOTIFICATIONS).createIndex({ this.db.collection(collections.NOTIFICATIONS).createIndex({
walletId: 1, walletId: 1,
id: 1, id: 1,
@ -70,13 +62,6 @@ Storage.prototype._createIndexes = function() {
this.db.collection(collections.ADDRESSES).createIndex({ this.db.collection(collections.ADDRESSES).createIndex({
address: 1, address: 1,
}); });
this.db.collection(collections.ADDRESSES).createIndex({
walletId: 1,
address: 1,
});
this.db.collection(collections.EMAIL_QUEUE).createIndex({
id: 1,
});
this.db.collection(collections.EMAIL_QUEUE).createIndex({ this.db.collection(collections.EMAIL_QUEUE).createIndex({
notificationId: 1, notificationId: 1,
}); });
@ -89,24 +74,6 @@ Storage.prototype._createIndexes = function() {
walletId: 1, walletId: 1,
txid: 1, txid: 1,
}); });
this.db.collection(collections.PREFERENCES).createIndex({
walletId: 1
});
this.db.collection(collections.FIAT_RATES).createIndex({
provider: 1,
code: 1,
ts: 1
});
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).createIndex({
copayerId: 1,
});
this.db.collection(collections.TX_CONFIRMATION_SUBS).createIndex({
copayerId: 1,
txid: 1,
});
this.db.collection(collections.SESSIONS).createIndex({
copayerId: 1
});
}; };
Storage.prototype.connect = function(opts, cb) { Storage.prototype.connect = function(opts, cb) {
@ -140,8 +107,6 @@ Storage.prototype.disconnect = function(cb) {
}; };
Storage.prototype.fetchWallet = function(id, cb) { Storage.prototype.fetchWallet = function(id, cb) {
if (!this.db) return cb('not ready');
this.db.collection(collections.WALLETS).findOne({ this.db.collection(collections.WALLETS).findOne({
id: id id: id
}, function(err, result) { }, function(err, result) {
@ -230,7 +195,6 @@ Storage.prototype._completeTxData = function(walletId, txs, cb) {
// TODO: remove walletId from signature // TODO: remove walletId from signature
Storage.prototype.fetchTx = function(walletId, txProposalId, cb) { Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
var self = this; var self = this;
if (!this.db) return cb();
this.db.collection(collections.TXS).findOne({ this.db.collection(collections.TXS).findOne({
id: txProposalId, id: txProposalId,
@ -244,7 +208,6 @@ Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
Storage.prototype.fetchTxByHash = function(hash, cb) { Storage.prototype.fetchTxByHash = function(hash, cb) {
var self = this; var self = this;
if (!this.db) return cb();
this.db.collection(collections.TXS).findOne({ this.db.collection(collections.TXS).findOne({
txid: hash, txid: hash,
@ -476,28 +439,6 @@ Storage.prototype.fetchAddresses = function(walletId, cb) {
}); });
}; };
Storage.prototype.fetchNewAddresses = function(walletId, fromTs, cb) {
var self = this;
this.db.collection(collections.ADDRESSES).find({
walletId: walletId,
createdOn: {
$gte: fromTs,
},
}).sort({
createdOn: 1
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var addresses = _.map(result, function(address) {
return Model.Address.fromObj(address);
});
return cb(null, addresses);
});
};
Storage.prototype.countAddresses = function(walletId, cb) { Storage.prototype.countAddresses = function(walletId, cb) {
this.db.collection(collections.ADDRESSES).find({ this.db.collection(collections.ADDRESSES).find({
walletId: walletId, walletId: walletId,
@ -508,7 +449,6 @@ Storage.prototype.storeAddress = function(address, cb) {
var self = this; var self = this;
self.db.collection(collections.ADDRESSES).update({ self.db.collection(collections.ADDRESSES).update({
walletId: address.walletId,
address: address.address address: address.address
}, address, { }, address, {
w: 1, w: 1,
@ -520,21 +460,37 @@ Storage.prototype.storeAddressAndWallet = function(wallet, addresses, cb) {
var self = this; var self = this;
var addresses = [].concat(addresses); var addresses = [].concat(addresses);
if (_.isEmpty(addresses)) return cb(); if (addresses.length == 0) return cb();
self.db.collection(collections.ADDRESSES).insert(addresses, { async.filter(addresses, function(address, next) {
self.db.collection(collections.ADDRESSES).findOne({
address: address.address,
}, {
walletId: true,
}, function(err, result) {
if (err || !result) return next(true);
if (result.walletId != wallet.id) {
log.warn('Address ' + address.address + ' exists in more than one wallet.');
return next(true);
}
// Ignore if address was already in wallet
return next(false);
});
}, function(newAddresses) {
if (newAddresses.length == 0) return cb();
self.db.collection(collections.ADDRESSES).insert(newAddresses, {
w: 1 w: 1
}, function(err) { }, function(err) {
if (err) return cb(err); if (err) return cb(err);
self.storeWallet(wallet, cb); self.storeWallet(wallet, cb);
}); });
});
}; };
Storage.prototype.fetchAddressByWalletId = function(walletId, address, cb) { Storage.prototype.fetchAddress = function(address, cb) {
var self = this; var self = this;
this.db.collection(collections.ADDRESSES).findOne({ this.db.collection(collections.ADDRESSES).findOne({
walletId: walletId,
address: address, address: address,
}, function(err, result) { }, function(err, result) {
if (err) return cb(err); if (err) return cb(err);
@ -544,28 +500,6 @@ Storage.prototype.fetchAddressByWalletId = function(walletId, address, cb) {
}); });
}; };
Storage.prototype.fetchAddressByCoin = function(coin, address, cb) {
var self = this;
if (!this.db) return cb();
this.db.collection(collections.ADDRESSES).find({
address: address,
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result || _.isEmpty(result)) return cb();
if (result.length > 1) {
result = _.find(result, function(address) {
return coin == (address.coin || 'btc');
});
} else {
result = _.first(result);
}
if (!result) return cb();
return cb(null, Model.Address.fromObj(result));
});
};
Storage.prototype.fetchPreferences = function(walletId, copayerId, cb) { Storage.prototype.fetchPreferences = function(walletId, copayerId, cb) {
this.db.collection(collections.PREFERENCES).find({ this.db.collection(collections.PREFERENCES).find({
walletId: walletId, walletId: walletId,
@ -629,91 +563,51 @@ Storage.prototype.fetchEmailByNotification = function(notificationId, cb) {
}); });
}; };
Storage.prototype.storeTwoStepCache = function(walletId, cacheStatus, cb) { Storage.prototype.cleanActiveAddresses = function(walletId, cb) {
var self = this;
self.db.collection(collections.CACHE).update( {
walletId: walletId,
type: 'twoStep',
key: null,
}, {
"$set":
{
addressCount: cacheStatus.addressCount,
lastEmpty: cacheStatus.lastEmpty,
}
}, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype.getTwoStepCache = function(walletId, cb) {
var self = this; var self = this;
async.series([
self.db.collection(collections.CACHE).findOne({ function(next) {
self.db.collection(collections.CACHE).remove({
walletId: walletId, walletId: walletId,
type: 'twoStep', type: 'activeAddresses',
}, {
w: 1
}, next);
},
function(next) {
self.db.collection(collections.CACHE).insert({
walletId: walletId,
type: 'activeAddresses',
key: null key: null
}, function(err, result) { }, {
if (err) return cb(err); w: 1
if (!result) return cb(); }, next);
return cb(null, result); },
}); ], cb);
}; };
Storage.prototype.storeActiveAddresses = function(walletId, addresses, cb) {
Storage.prototype.storeAddressesWithBalance = function(walletId, addresses, cb) {
var self = this; var self = this;
if (_.isEmpty(addresses)) async.each(addresses, function(address, next) {
addresses = []; var record = {
self.db.collection(collections.CACHE).update({
walletId: walletId, walletId: walletId,
type: 'addressesWithBalance', type: 'activeAddresses',
key: null, key: address,
}, { };
"$set": self.db.collection(collections.CACHE).update({
{ walletId: record.walletId,
addresses: addresses, type: record.type,
} key: record.key,
}, { }, record, {
w: 1, w: 1,
upsert: true, upsert: true,
}, next);
}, cb); }, cb);
}; };
Storage.prototype.fetchAddressesWithBalance = function(walletId, cb) {
var self = this;
self.db.collection(collections.CACHE).findOne({
walletId: walletId,
type: 'addressesWithBalance',
key: null,
}, function(err, result) {
if (err) return cb(err);
if (_.isEmpty(result)) return cb(null, []);
self.db.collection(collections.ADDRESSES).find({
walletId: walletId,
address: { $in: result.addresses },
}).toArray(function(err, result2) {
if (err) return cb(err);
if (!result2) return cb(null, []);
var addresses = _.map(result2, function(address) {
return Model.Address.fromObj(address);
});
return cb(null, addresses);
});
});
};
// -------- --------------------------- Total // -------- --------------------------- Total
// > Time > // > Time >
// ^to <= ^from // ^to <= ^from
@ -764,7 +658,7 @@ Storage.prototype.getTxHistoryCache = function(walletId, from, to, cb) {
return cb(); return cb();
} }
var txs = _.map(result, 'tx'); var txs = _.pluck(result, 'tx');
return cb(null, txs); return cb(null, txs);
}); });
}) })
@ -866,6 +760,23 @@ Storage.prototype.storeTxHistoryCache = function(walletId, totalItems, firstPosi
}; };
Storage.prototype.fetchActiveAddresses = function(walletId, cb) {
var self = this;
self.db.collection(collections.CACHE).find({
walletId: walletId,
type: 'activeAddresses',
}).toArray(function(err, result) {
if (err) return cb(err);
if (_.isEmpty(result)) return cb();
return cb(null, _.compact(_.pluck(result, 'key')));
});
};
Storage.prototype.storeFiatRate = function(providerName, rates, cb) { Storage.prototype.storeFiatRate = function(providerName, rates, cb) {
var self = this; var self = this;
@ -979,79 +890,6 @@ Storage.prototype.storeSession = function(session, cb) {
}, cb); }, cb);
}; };
Storage.prototype.fetchPushNotificationSubs = function(copayerId, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).find({
copayerId: copayerId,
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var tokens = _.map([].concat(result), function(r) {
return Model.PushNotificationSub.fromObj(r);
});
return cb(null, tokens);
});
};
Storage.prototype.storePushNotificationSub = function(pushNotificationSub, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).update({
copayerId: pushNotificationSub.copayerId,
token: pushNotificationSub.token,
}, pushNotificationSub, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype.removePushNotificationSub = function(copayerId, token, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).remove({
copayerId: copayerId,
token: token,
}, {
w: 1
}, cb);
};
Storage.prototype.fetchActiveTxConfirmationSubs = function(copayerId, cb) {
var filter = {
isActive: true
};
if (copayerId) filter.copayerId = copayerId;
this.db.collection(collections.TX_CONFIRMATION_SUBS).find(filter)
.toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var subs = _.map([].concat(result), function(r) {
return Model.TxConfirmationSub.fromObj(r);
});
return cb(null, subs);
});
};
Storage.prototype.storeTxConfirmationSub = function(txConfirmationSub, cb) {
this.db.collection(collections.TX_CONFIRMATION_SUBS).update({
copayerId: txConfirmationSub.copayerId,
txid: txConfirmationSub.txid,
}, txConfirmationSub, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype.removeTxConfirmationSub = function(copayerId, txid, cb) {
this.db.collection(collections.TX_CONFIRMATION_SUBS).remove({
copayerId: copayerId,
txid: txid,
}, {
w: 1
}, cb);
};
Storage.prototype._dump = function(cb, fn) { Storage.prototype._dump = function(cb, fn) {
fn = fn || console.log; fn = fn || console.log;
cb = cb || function() {}; cb = cb || function() {};
@ -1070,140 +908,5 @@ Storage.prototype._dump = function(cb, fn) {
}); });
}; };
Storage.prototype.fetchAddressIndexCache = function (walletId, key, cb) {
this.db.collection(collections.CACHE).findOne({
walletId: walletId,
type: 'addressIndexCache',
key: key,
}, function(err, ret) {
if (err) return cb(err);
if (!ret) return cb();
cb(null, ret.index);
});
}
Storage.prototype.storeAddressIndexCache = function (walletId, key, index, cb) {
this.db.collection(collections.CACHE).update({
walletId: walletId,
type: 'addressIndexCache',
key: key,
}, {
"$set":
{
index: index,
}
}, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype._addressHash = function(addresses) {
var all = addresses.join();
return Bitcore.crypto.Hash.ripemd160(new Buffer(all)).toString('hex');
};
Storage.prototype.checkAndUseBalanceCache = function(walletId, addresses, duration, cb) {
var self = this;
var key = self._addressHash(addresses);
var now = Date.now();
self.db.collection(collections.CACHE).findOne({
walletId: walletId || key,
type: 'balanceCache',
key: key,
}, function(err, ret) {
if (err) return cb(err);
if (!ret) return cb();
var validFor = ret.ts + duration * 1000 - now;
if (validFor > 0) {
log.debug('','Using Balance Cache valid for %d ms more', validFor);
cb(null, ret.result);
return true;
}
cb();
log.debug('','Balance cache expired, deleting');
self.db.collection(collections.CACHE).remove({
walletId: walletId,
type: 'balanceCache',
key: key,
}, {}, function() {});
return false;
});
};
Storage.prototype.storeBalanceCache = function (walletId, addresses, balance, cb) {
var key = this._addressHash(addresses);
var now = Date.now();
this.db.collection(collections.CACHE).update({
walletId: walletId || key,
type: 'balanceCache',
key: key,
}, {
"$set":
{
ts: now,
result: balance,
}
}, {
w: 1,
upsert: true,
}, cb);
};
// FEE_LEVEL_DURATION = 5min
var FEE_LEVEL_DURATION = 5 * 60 * 1000;
Storage.prototype.checkAndUseFeeLevelsCache = function(opts, cb) {
var self = this;
var key = JSON.stringify(opts);
var now = Date.now();
self.db.collection(collections.CACHE).findOne({
walletId: null,
type: 'feeLevels',
key: key,
}, function(err, ret) {
if (err) return cb(err);
if (!ret) return cb();
var validFor = ret.ts + FEE_LEVEL_DURATION - now;
return cb(null, validFor > 0 ? ret.result : null);
});
};
Storage.prototype.storeFeeLevelsCache = function (opts, values, cb) {
var key = JSON.stringify(opts);
var now = Date.now();
this.db.collection(collections.CACHE).update({
walletId: null,
type: 'feeLevels',
key: key,
}, {
"$set":
{
ts: now,
result: values,
}
}, {
w: 1,
upsert: true,
}, cb);
};
Storage.collections = collections; Storage.collections = collections;
module.exports = Storage; module.exports = Storage;

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}New copayer {{subjectPrefix}}New copayer
A new copayer just joined your wallet. A new copayer just joined your wallet {{walletName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}New payment received {{subjectPrefix}}New payment received
A payment of {{amount}} has been received into your wallet. A payment of {{amount}} has been received into your wallet {{walletName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Payment sent {{subjectPrefix}}Payment sent
A Payment of {{amount}} has been sent from your wallet. A Payment of {{amount}} has been sent from your wallet {{walletName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}New payment proposal {{subjectPrefix}}New payment proposal
A new payment proposal has been created in your wallet. A new payment proposal has been created in your wallet {{walletName}} by {{copayerName}}.

View File

@ -1,2 +0,0 @@
{{subjectPrefix}} Transaction confirmed
The transaction you were waiting for has been confirmed.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Payment proposal rejected {{subjectPrefix}}Payment proposal rejected
A payment proposal in your wallet has been rejected. A payment proposal in your wallet {{walletName}} has been rejected by {{rejectorsNames}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Wallet complete {{subjectPrefix}}Wallet complete
Your wallet is complete. Your wallet {{walletName}} is complete.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nuevo copayer {{subjectPrefix}}Nuevo copayer
Un nuevo copayer ha ingresado a su billetera. Un nuevo copayer ha ingresado a su monedero {{walletName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nuevo pago recibido {{subjectPrefix}}Nuevo pago recibido
Un pago de {{amount}} fue recibido en su billetera. Un pago de {{amount}} fue recibido en su monedero {{walletName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Pago enviado {{subjectPrefix}}Pago enviado
Un pago de {{amount}} ha sido enviado de su billetera. Un pago de {{amount}} ha sido enviado de su monedero {{walletName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nueva propuesta de pago {{subjectPrefix}}Nueva propuesta de pago
Una nueva propuesta de pago ha sido creada en su billetera. Una nueva propuesta de pago ha sido creada en su monedero {{walletName}} por {{copayerName}}.

View File

@ -1,2 +0,0 @@
{{subjectPrefix}} Transacción confirmada
La transacción que estabas esperando se ha confirmado.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Propuesta de pago rechazada {{subjectPrefix}}Propuesta de pago rechazada
Una propuesta de pago en su billetera ha sido rechazada. Una propuesta de pago en su monedero {{walletName}} ha sido rechazada por {{rejectorsNames}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Billetera completa {{subjectPrefix}}Monedero completo
Su billetera está completa. Su monedero {{walletName}} está completo.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nouveau copayer {{subjectPrefix}}Nouveau copayer
Un nouveau copayer vient de rejoindre votre portefeuille. Un nouveau copayer vient de rejoindre votre portefeuille {{walletName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nouveau paiement reçu {{subjectPrefix}}Nouveau paiement reçu
Un paiement de {{amount}} a été reçu dans votre portefeuille. Un paiement de {{amount}} a été reçu dans votre portefeuille {{walletName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Paiement envoyé {{subjectPrefix}}Paiement envoyé
Un paiement de {{amount}} a été envoyé de votre portefeuille. Un paiement de {{amount}} a été envoyé de votre portefeuille {{walletName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nouvelle proposition de paiement {{subjectPrefix}}Nouvelle proposition de paiement
Une nouvelle proposition de paiement a été créée dans votre portefeuille. Une nouvelle proposition de paiement a été créée dans votre portefeuille {{walletName}} par {{copayerName}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Proposition de paiement rejetée {{subjectPrefix}}Proposition de paiement rejetée
Une proposition de paiement dans votre portefeuille a été rejetée. Une proposition de paiement dans votre portefeuille {{walletName}} a été rejetée par {{rejectorsNames}}.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Portefeuille terminé {{subjectPrefix}}Portefeuille terminé
Votre portefeuille est terminé. Votre portefeuille {{walletName}} est terminé.

View File

@ -2,8 +2,7 @@
"name": "bitcore-wallet-service", "name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets", "description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc", "author": "BitPay Inc",
"version": "2.4.0", "version": "1.13.0",
"licence": "MIT",
"keywords": [ "keywords": [
"bitcoin", "bitcoin",
"copay", "copay",
@ -13,22 +12,20 @@
"BWS" "BWS"
], ],
"repository": { "repository": {
"url": "git@github.com:BTCPrivate/bitcore-wallet-service.git", "url": "git@github.com:bitpay/bitcore-wallet-service.git",
"type": "git" "type": "git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/BTCPrivate/bitcore-wallet-service/issues" "url": "https://github.com/bitpay/bitcore-wallet-service/issues"
}, },
"dependencies": { "dependencies": {
"async": "^0.9.2", "async": "^0.9.2",
"bitcore-lib": "ch4ot1c/bitcore-lib", "bitcore-lib": "^0.13.7",
"bitcore-lib-cash": "^0.17.0",
"body-parser": "^1.11.0", "body-parser": "^1.11.0",
"compression": "^1.6.2", "compression": "^1.6.2",
"coveralls": "^2.11.2", "coveralls": "^2.11.2",
"email-validator": "^1.0.1", "email-validator": "^1.0.1",
"express": "^4.10.0", "express": "^4.10.0",
"express-rate-limit": "^2.6.0",
"inherits": "^2.0.1", "inherits": "^2.0.1",
"json-stable-stringify": "^1.0.0", "json-stable-stringify": "^1.0.0",
"locker": "^0.1.0", "locker": "^0.1.0",
@ -55,13 +52,13 @@
"devDependencies": { "devDependencies": {
"chai": "^1.9.1", "chai": "^1.9.1",
"istanbul": "*", "istanbul": "*",
"jsdoc": "^3.5.5", "jsdoc": "^3.3.0-beta1",
"memdown": "^1.0.0", "memdown": "^1.0.0",
"mocha": "^1.18.2", "mocha": "^1.18.2",
"proxyquire": "^1.7.2", "proxyquire": "^1.7.2",
"sinon": "1.10.3", "sinon": "1.10.3",
"supertest": "*", "supertest": "*",
"tingodb": "^0.5.1" "tingodb": "^0.3.4"
}, },
"scripts": { "scripts": {
"start": "./start.sh", "start": "./start.sh",
@ -71,18 +68,14 @@
"coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" "coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
}, },
"bitcoreNode": "./bitcorenode", "bitcoreNode": "./bitcorenode",
"contributors": [ "contributors": [{
{
"name": "Braydon Fuller", "name": "Braydon Fuller",
"email": "braydon@bitpay.com" "email": "braydon@bitpay.com"
}, }, {
{
"name": "Ivan Socolsky", "name": "Ivan Socolsky",
"email": "ivan@bitpay.com" "email": "ivan@bitpay.com"
}, }, {
{
"name": "Matias Alejo Garcia", "name": "Matias Alejo Garcia",
"email": "ematiu@gmail.com" "email": "ematiu@gmail.com"
} }]
]
} }

View File

@ -1,4 +0,0 @@
db.email_queue.remove({createdOn: {$lt: Date.now()/1000-86400*10 }});
db.notifications.remove({createdOn: {$lt: Date.now()/1000-86400*10 }});

View File

@ -1,18 +0,0 @@
// json support
a='f42c8c47-0f7e-4cb0-9056-6c50bb821d7d';
b= {'walletId':a};
db.addresses.remove(b);
db.cache.remove(b);
db.copayers_lookup.remove(b);
db.notifications.remove(b);
db.preferences.remove(b);
db.sessions.remove(b);
db.tx_confirmation_subs.remove(b);
db.txs.remove(b);
db.tx_notes.remove(b);
db.wallets.remove({'id':a});
db.push_notification_subs.remove(b);
db.email_queue.remove(b);

View File

@ -1,265 +0,0 @@
'use strict';
var _ = require('lodash');
var async = require('async');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var log = require('npmlog');
log.debug = log.verbose;
log.level = 'info';
var WalletService = require('../../lib/server');
var BlockchainMonitor = require('../../lib/blockchainmonitor');
var helpers = require('./helpers');
var storage, blockchainExplorer;
var socket = {
handlers: {},
};
socket.on = function(eventName, handler) {
this.handlers[eventName] = handler;
};
describe('Blockchain monitor', function() {
var server, wallet;
before(function(done) {
helpers.before(done);
});
after(function(done) {
helpers.after(done);
});
beforeEach(function(done) {
helpers.beforeEach(function(res) {
storage = res.storage;
blockchainExplorer = res.blockchainExplorer;
blockchainExplorer.initSocket = sinon.stub().returns(socket);
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
var bcmonitor = new BlockchainMonitor();
bcmonitor.start({
lockOpts: {},
messageBroker: server.messageBroker,
storage: storage,
blockchainExplorers: {
'btc': {
'testnet': blockchainExplorer,
'livenet': blockchainExplorer
}
},
}, function(err) {
should.not.exist(err);
done();
});
});
});
});
it('should notify copayers of incoming txs', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
vout: [{}],
};
incoming.vout[0][address.address] = 1500;
socket.handlers['tx'](incoming);
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.find(notifications, {
type: 'NewIncomingTx'
});
should.exist(notification);
notification.walletId.should.equal(wallet.id);
notification.data.txid.should.equal('123');
notification.data.address.should.equal(address.address);
notification.data.amount.should.equal(1500);
done();
});
}, 100);
});
});
it('should update addressWithBalance cache on 1 incoming tx', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
vout: [{}],
};
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
_.isEmpty(ret).should.equal(true);
incoming.vout[0][address.address] = 1500;
socket.handlers['tx'](incoming);
setTimeout(function() {
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
ret.length.should.equal(1);
ret[0].address.should.equal(address.address);
done();
});
}, 100);
});
});
});
it('should update addressWithBalance cache on 2 incoming tx, same address', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
_.isEmpty(ret).should.equal(true);
var incoming = {
txid: '123',
vout: [{}],
};
incoming.vout[0][address.address] = 1500;
socket.handlers['tx'](incoming);
setTimeout(function() {
var incoming2 = {
txid: '456',
vout: [{}],
};
incoming2.vout[0][address.address] = 2500;
socket.handlers['tx'](incoming2);
setTimeout(function() {
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
ret.length.should.equal(1);
ret[0].address.should.equal(address.address);
done();
});
}, 100);
}, 100);
});
});
});
it('should update addressWithBalance cache on 2 incoming tx, different address', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.createAddress({}, function(err, address2) {
should.not.exist(err);
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
_.isEmpty(ret).should.equal(true);
var incoming = {
txid: '123',
vout: [{}],
};
incoming.vout[0][address.address] = 1500;
socket.handlers['tx'](incoming);
setTimeout(function() {
var incoming2 = {
txid: '456',
vout: [{}],
};
incoming2.vout[0][address2.address] = 500;
socket.handlers['tx'](incoming2);
setTimeout(function() {
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
ret.length.should.equal(2);
ret[0].address.should.equal(address.address);
done();
});
}, 100);
}, 100);
});
});
});
});
it('should not notify copayers of incoming txs more than once', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
vout: [{}],
};
incoming.vout[0][address.address] = 1500;
socket.handlers['tx'](incoming);
setTimeout(function() {
socket.handlers['tx'](incoming);
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.filter(notifications, {
type: 'NewIncomingTx'
});
notification.length.should.equal(1);
done();
});
}, 100);
}, 50);
});
});
it('should notify copayers of tx confirmation', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
vout: [{}],
};
incoming.vout[0][address.address] = 1500;
server.txConfirmationSubscribe({
txid: '123'
}, function(err) {
should.not.exist(err);
blockchainExplorer.getTxidsInBlock = sinon.stub().callsArgWith(1, null, ['123', '456']);
socket.handlers['block']('block1');
setTimeout(function() {
blockchainExplorer.getTxidsInBlock = sinon.stub().callsArgWith(1, null, ['123', '456']);
socket.handlers['block']('block2');
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notifications = _.filter(notifications, {
type: 'TxConfirmation'
});
notifications.length.should.equal(1);
var n = notifications[0];
n.walletId.should.equal(wallet.id);
n.creatorId.should.equal(server.copayerId);
n.data.txid.should.equal('123');
done();
});
}, 50);
}, 50);
});
});
});
});

View File

@ -98,8 +98,11 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment proposal'); one.subject.should.contain('New payment proposal');
one.text.should.contain(wallet.name);
one.text.should.contain(wallet.copayers[0].name);
should.exist(one.html); should.exist(one.html);
one.html.indexOf('<html>').should.equal(0); one.html.indexOf('<html>').should.equal(0);
one.html.should.contain(wallet.name);
server.storage.fetchUnsentEmails(function(err, unsent) { server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err); should.not.exist(err);
unsent.should.be.empty; unsent.should.be.empty;
@ -169,7 +172,7 @@ describe('Email notifications', function() {
txp = t; txp = t;
async.eachSeries(_.range(2), function(i, next) { async.eachSeries(_.range(2), function(i, next) {
var copayer = TestData.copayers[i]; var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44btc, function(server) { helpers.getAuthServer(copayer.id44, function(server) {
var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H); var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H);
server.signTx({ server.signTx({
txProposalId: txp.id, txProposalId: txp.id,
@ -199,6 +202,7 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('Payment sent'); one.subject.should.contain('Payment sent');
one.text.should.contain(wallet.name);
one.text.should.contain('800,000'); one.text.should.contain('800,000');
should.exist(one.html); should.exist(one.html);
one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid); one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid);
@ -235,7 +239,7 @@ describe('Email notifications', function() {
txpId = txp.id; txpId = txp.id;
async.eachSeries(_.range(1, 3), function(i, next) { async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i]; var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44btc, function(server) { helpers.getAuthServer(copayer.id44, function(server) {
server.rejectTx({ server.rejectTx({
txProposalId: txp.id, txProposalId: txp.id,
}, next); }, next);
@ -254,6 +258,9 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('Payment proposal rejected'); one.subject.should.contain('Payment proposal rejected');
one.text.should.contain(wallet.name);
one.text.should.contain('copayer 2, copayer 3');
one.text.should.not.contain('copayer 1');
server.storage.fetchUnsentEmails(function(err, unsent) { server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err); should.not.exist(err);
unsent.should.be.empty; unsent.should.be.empty;
@ -284,6 +291,7 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment received'); one.subject.should.contain('New payment received');
one.text.should.contain(wallet.name);
one.text.should.contain('123,000'); one.text.should.contain('123,000');
server.storage.fetchUnsentEmails(function(err, unsent) { server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err); should.not.exist(err);
@ -295,38 +303,6 @@ describe('Email notifications', function() {
}); });
}); });
it('should notify copayers when tx is confirmed if they are subscribed', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.txConfirmationSubscribe({
txid: '123'
}, function(err) {
should.not.exist(err);
// Simulate tx confirmation notification
server._notify('TxConfirmation', {
txid: '123',
}, function(err) {
setTimeout(function() {
var calls = mailerStub.sendMail.getCalls();
calls.length.should.equal(1);
var email = calls[0].args[0];
email.to.should.equal('copayer1@domain.com');
email.from.should.equal('bws@dummy.net');
email.subject.should.contain('Transaction confirmed');
server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err);
unsent.should.be.empty;
done();
});
}, 100);
});
});
});
});
it('should notify each email address only once', function(done) { it('should notify each email address only once', function(done) {
// Set same email address for copayer1 and copayer2 // Set same email address for copayer1 and copayer2
server.savePreferences({ server.savePreferences({
@ -351,6 +327,7 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment received'); one.subject.should.contain('New payment received');
one.text.should.contain(wallet.name);
one.text.should.contain('123,000'); one.text.should.contain('123,000');
server.storage.fetchUnsentEmails(function(err, unsent) { server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err); should.not.exist(err);
@ -390,12 +367,14 @@ describe('Email notifications', function() {
}); });
spanish.from.should.equal('bws@dummy.net'); spanish.from.should.equal('bws@dummy.net');
spanish.subject.should.contain('Nuevo pago recibido'); spanish.subject.should.contain('Nuevo pago recibido');
spanish.text.should.contain(wallet.name);
spanish.text.should.contain('0.123 BTC'); spanish.text.should.contain('0.123 BTC');
var english = _.find(emails, { var english = _.find(emails, {
to: 'copayer2@domain.com' to: 'copayer2@domain.com'
}); });
english.from.should.equal('bws@dummy.net'); english.from.should.equal('bws@dummy.net');
english.subject.should.contain('New payment received'); english.subject.should.contain('New payment received');
english.text.should.contain(wallet.name);
english.text.should.contain('123,000 bits'); english.text.should.contain('123,000 bits');
done(); done();
}, 100); }, 100);

View File

@ -13,10 +13,6 @@ var tingodb = require('tingodb')({
}); });
var Bitcore = require('bitcore-lib'); var Bitcore = require('bitcore-lib');
var Bitcore_ = {
btc: Bitcore,
bch: require('bitcore-lib-cash')
};
var Common = require('../../lib/common'); var Common = require('../../lib/common');
var Utils = Common.Utils; var Utils = Common.Utils;
@ -92,14 +88,13 @@ helpers.signMessage = function(text, privKey) {
}; };
helpers.signRequestPubKey = function(requestPubKey, xPrivKey) { helpers.signRequestPubKey = function(requestPubKey, xPrivKey) {
var priv = new Bitcore.HDPrivateKey(xPrivKey).deriveChild(Constants.PATHS.REQUEST_KEY_AUTH).privateKey; var priv = new Bitcore.HDPrivateKey(xPrivKey).derive(Constants.PATHS.REQUEST_KEY_AUTH).privateKey;
return helpers.signMessage(requestPubKey, priv); return helpers.signMessage(requestPubKey, priv);
}; };
helpers.getAuthServer = function(copayerId, cb) { helpers.getAuthServer = function(copayerId, cb) {
var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature'); var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature');
verifyStub.returns(true); verifyStub.returns(true);
WalletService.getInstanceWithAuth({ WalletService.getInstanceWithAuth({
copayerId: copayerId, copayerId: copayerId,
message: 'dummy', message: 'dummy',
@ -112,40 +107,26 @@ helpers.getAuthServer = function(copayerId, cb) {
}); });
}; };
helpers._generateCopayersTestData = function() { helpers._generateCopayersTestData = function(n) {
var xPrivKeys = ['xprv9s21ZrQH143K2n4rV4AtAJFptEmd1tNMKCcSyQBCSuN5eq1dCUhcv6KQJS49joRxu8NNdFxy8yuwTtzCPNYUZvVGC7EPRm2st2cvE7oyTbB',
'xprv9s21ZrQH143K3BwkLceWNLUsgES15JoZuv8BZfnmDRcCGtDooUAPhY8KovhCWcRLXUun5AYL5vVtUNRrmPEibtfk9ongxAGLXZzEHifpvwZ',
'xprv9s21ZrQH143K3xgLzxd6SuWqG5Zp1iUmyGgSsJVhdQNeTzAqBFvXXLZqZzFZqocTx4HD9vUVYU27At5i8q46LmBXXL97fo4H9C3tHm4BnjY',
'xprv9s21ZrQH143K48nfuK14gKJtML7eQzV2dAH1RaqAMj8v2zs79uaavA9UTWMxpBdgbMH2mhJLeKGq8AFA6GDnFyWP4rLmknqZAfgFFV718vo',
'xprv9s21ZrQH143K44Bb9G3EVNmLfAUKjTBAA2YtKxF4zc8SLV1o15JBoddhGHE9PGLXePMbEsSjCCvTvP3fUv6yMXZrnHigBboRBn2DmNoJkJg',
'xprv9s21ZrQH143K48PpVxrh71KdViTFhAaiDSVtNFkmbWNYjwwwPbTrcqoVXsgBfue3Gq9b71hQeEbk67JgtTBcpYgKLF8pTwVnGz56f1BaCYt',
'xprv9s21ZrQH143K3pgRcRBRnmcxNkNNLmJrpneMkEXY6o5TWBuJLMfdRpAWdb2cG3yxbL4DxfpUnQpjfQUmwPdVrRGoDJmtAf5u8cyqKCoDV97',
'xprv9s21ZrQH143K3nvcmdjDDDZbDJHpfWZCUiunwraZdcamYcafHvUnZfV51fivH9FPyfo12NyKH5JDxGLsQePyWKtTiJx3pkEaiwxsMLkVapp',
'xprv9s21ZrQH143K2uYgqtYtphEQkFAgiWSqahFUWjgCdKykJagiNDz6Lf7xRVQdtZ7MvkhX9V3pEcK3xTAWZ6Y6ecJqrXnCpzrH9GSHn8wyrT5',
'xprv9s21ZrQH143K2wcRMP75tAEL5JnUx4xU2AbUBQzVVUDP7DHZJkjF3kaRE7tcnPLLLL9PGjYTWTJmCQPaQ4GGzgWEUFJ6snwJG9YnQHBFRNR'
];
console.log('var copayers = ['); console.log('var copayers = [');
_.each(xPrivKeys, function(xPrivKeyStr, c) { _.each(_.range(n), function(c) {
var xpriv = Bitcore.HDPrivateKey(xPrivKeyStr); var xpriv = new Bitcore.HDPrivateKey();
var xpub = Bitcore.HDPublicKey(xpriv); var xpub = Bitcore.HDPublicKey(xpriv);
var xpriv_45H = xpriv.deriveChild(45, true); var xpriv_45H = xpriv.derive(45, true);
var xpub_45H = Bitcore.HDPublicKey(xpriv_45H); var xpub_45H = Bitcore.HDPublicKey(xpriv_45H);
var id45 = Model.Copayer._xPubToCopayerId('btc', xpub_45H.toString()); var id45 = Copayer._xPubToCopayerId(xpub_45H.toString());
var xpriv_44H_0H_0H = xpriv.deriveChild(44, true).deriveChild(0, true).deriveChild(0, true); var xpriv_44H_0H_0H = xpriv.derive(44, true).derive(0, true).derive(0, true);
var xpub_44H_0H_0H = Bitcore.HDPublicKey(xpriv_44H_0H_0H); var xpub_44H_0H_0H = Bitcore.HDPublicKey(xpriv_44H_0H_0H);
var id44btc = Model.Copayer._xPubToCopayerId('btc', xpub_44H_0H_0H.toString()); var id44 = Copayer._xPubToCopayerId(xpub_44H_0H_0H.toString());
var id44bch = Model.Copayer._xPubToCopayerId('bch', xpub_44H_0H_0H.toString());
var xpriv_1H = xpriv.deriveChild(1, true); var xpriv_1H = xpriv.derive(1, true);
var xpub_1H = Bitcore.HDPublicKey(xpriv_1H); var xpub_1H = Bitcore.HDPublicKey(xpriv_1H);
var priv = xpriv_1H.deriveChild(0).privateKey; var priv = xpriv_1H.derive(0).privateKey;
var pub = xpub_1H.deriveChild(0).publicKey; var pub = xpub_1H.derive(0).publicKey;
console.log('{id44btc: ', "'" + id44btc + "',"); console.log('{id44: ', "'" + id44 + "',");
console.log('id44bch: ', "'" + id44bch + "',");
console.log('id45: ', "'" + id45 + "',"); console.log('id45: ', "'" + id45 + "',");
console.log('xPrivKey: ', "'" + xpriv.toString() + "',"); console.log('xPrivKey: ', "'" + xpriv.toString() + "',");
console.log('xPubKey: ', "'" + xpub.toString() + "',"); console.log('xPubKey: ', "'" + xpub.toString() + "',");
@ -184,8 +165,6 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
n: n, n: n,
pubKey: TestData.keyPair.pub, pubKey: TestData.keyPair.pub,
singleAddress: !!opts.singleAddress, singleAddress: !!opts.singleAddress,
coin: opts.coin || 'btc',
network: opts.network || 'livenet',
}; };
if (_.isBoolean(opts.supportBIP44AndP2PKH)) if (_.isBoolean(opts.supportBIP44AndP2PKH))
walletOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH; walletOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
@ -195,18 +174,10 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
async.each(_.range(n), function(i, cb) { async.each(_.range(n), function(i, cb) {
var copayerData = TestData.copayers[i + offset]; var copayerData = TestData.copayers[i + offset];
var pub = (_.isBoolean(opts.supportBIP44AndP2PKH) && !opts.supportBIP44AndP2PKH) ? copayerData.xPubKey_45H : copayerData.xPubKey_44H_0H_0H;
if (opts.network == 'testnet')
pub = copayerData.xPubKey_44H_0H_0Ht;
var copayerOpts = helpers.getSignedCopayerOpts({ var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId, walletId: walletId,
coin: opts.coin,
name: 'copayer ' + (i + 1), name: 'copayer ' + (i + 1),
xPubKey: pub, xPubKey: (_.isBoolean(opts.supportBIP44AndP2PKH) && !opts.supportBIP44AndP2PKH) ? copayerData.xPubKey_45H : copayerData.xPubKey_44H_0H_0H,
requestPubKey: copayerData.pubKey_1H_0, requestPubKey: copayerData.pubKey_1H_0,
customData: 'custom data ' + (i + 1), customData: 'custom data ' + (i + 1),
}); });
@ -214,7 +185,6 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
copayerOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH; copayerOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
server.joinWallet(copayerOpts, function(err, result) { server.joinWallet(copayerOpts, function(err, result) {
if (err) console.log(err);
should.not.exist(err); should.not.exist(err);
copayerIds.push(result.copayerId); copayerIds.push(result.copayerId);
return cb(err); return cb(err);
@ -286,8 +256,6 @@ helpers.stubUtxos = function(server, wallet, amounts, opts, cb) {
if (!helpers._utxos) helpers._utxos = {}; if (!helpers._utxos) helpers._utxos = {};
var S = Bitcore_[wallet.coin].Script;
async.waterfall([ async.waterfall([
function(next) { function(next) {
@ -309,10 +277,10 @@ helpers.stubUtxos = function(server, wallet, amounts, opts, cb) {
var scriptPubKey; var scriptPubKey;
switch (wallet.addressType) { switch (wallet.addressType) {
case Constants.SCRIPT_TYPES.P2SH: case Constants.SCRIPT_TYPES.P2SH:
scriptPubKey = S.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut(); scriptPubKey = Bitcore.Script.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut();
break; break;
case Constants.SCRIPT_TYPES.P2PKH: case Constants.SCRIPT_TYPES.P2PKH:
scriptPubKey = S.buildPublicKeyHashOut(address.address); scriptPubKey = Bitcore.Script.buildPublicKeyHashOut(address.address);
break; break;
} }
should.exist(scriptPubKey); should.exist(scriptPubKey);
@ -389,22 +357,8 @@ helpers.stubFeeLevels = function(levels) {
}; };
}; };
helpers.stubAddressActivity = function(activeAddresses) {
var stubAddressActivityFailsOn = null;
var stubAddressActivityFailsOnCount=1;
helpers.stubAddressActivity = function(activeAddresses, failsOn) {
stubAddressActivityFailsOnCount=1;
// could be null
stubAddressActivityFailsOn = failsOn;
blockchainExplorer.getAddressActivity = function(address, cb) { blockchainExplorer.getAddressActivity = function(address, cb) {
if (stubAddressActivityFailsOnCount === stubAddressActivityFailsOn)
return cb('failed on request');
stubAddressActivityFailsOnCount++;
return cb(null, _.contains(activeAddresses, address)); return cb(null, _.contains(activeAddresses, address));
}; };
}; };
@ -420,7 +374,7 @@ helpers.clientSign = function(txp, derivedXPrivKey) {
_.each(txp.inputs, function(i) { _.each(txp.inputs, function(i) {
if (!derived[i.path]) { if (!derived[i.path]) {
derived[i.path] = xpriv.deriveChild(i.path).privateKey; derived[i.path] = xpriv.derive(i.path).privateKey;
privs.push(derived[i.path]); privs.push(derived[i.path]);
} }
}); });

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,6 @@ var log = require('npmlog');
log.debug = log.verbose; log.debug = log.verbose;
log.level = 'info'; log.level = 'info';
var sjcl = require('sjcl');
var WalletService = require('../../lib/server'); var WalletService = require('../../lib/server');
var PushNotificationsService = require('../../lib/pushnotificationsservice'); var PushNotificationsService = require('../../lib/pushnotificationsservice');
@ -38,24 +36,11 @@ describe('Push notifications', function() {
var i = 0; var i = 0;
async.eachSeries(w.copayers, function(copayer, next) { async.eachSeries(w.copayers, function(copayer, next) {
helpers.getAuthServer(copayer.id, function(server) { helpers.getAuthServer(copayer.id, function(server) {
async.parallel([
function(done) {
server.savePreferences({ server.savePreferences({
email: 'copayer' + (++i) + '@domain.com', email: 'copayer' + (++i) + '@domain.com',
language: 'en', language: 'en',
unit: 'bit', unit: 'bit',
}, done); }, next);
},
function(done) {
server.pushNotificationsSubscribe({
token: '1234',
packageName: 'com.wallet',
platform: 'Android',
}, done);
},
], next);
}); });
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -74,8 +59,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en', defaultLanguage: 'en',
defaultUnit: 'btc', defaultUnit: 'btc',
subjectPrefix: '', subjectPrefix: '',
pushServerUrl: 'http://localhost:8000',
authorizationKey: 'secret', pushServerUrl: 'http://localhost:8000/send',
}, },
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -108,9 +93,8 @@ describe('Push notifications', function() {
return c.args[0]; return c.args[0];
}); });
calls.length.should.equal(1); calls.length.should.equal(1);
args[0].body.notification.title.should.contain('New payment received'); args[0].body.android.data.title.should.contain('New payment received');
args[0].body.notification.body.should.contain('123,000'); args[0].body.android.data.message.should.contain('123,000');
args[0].body.notification.body.should.contain('bits');
done(); done();
}, 100); }, 100);
}); });
@ -159,29 +143,6 @@ describe('Push notifications', function() {
}); });
}); });
}); });
it('should notify copayers when tx is confirmed if they are subscribed', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.txConfirmationSubscribe({
txid: '123'
}, function(err) {
should.not.exist(err);
// Simulate tx confirmation notification
server._notify('TxConfirmation', {
txid: '123',
}, function(err) {
setTimeout(function() {
var calls = requestStub.getCalls();
calls.length.should.equal(1);
done();
}, 100);
});
});
});
});
}); });
describe('Shared wallet', function() { describe('Shared wallet', function() {
@ -193,24 +154,11 @@ describe('Push notifications', function() {
var i = 0; var i = 0;
async.eachSeries(w.copayers, function(copayer, next) { async.eachSeries(w.copayers, function(copayer, next) {
helpers.getAuthServer(copayer.id, function(server) { helpers.getAuthServer(copayer.id, function(server) {
async.parallel([
function(done) {
server.savePreferences({ server.savePreferences({
email: 'copayer' + (++i) + '@domain.com', email: 'copayer' + (++i) + '@domain.com',
language: 'en', language: 'en',
unit: 'bit', unit: 'bit',
}, done); }, next);
},
function(done) {
server.pushNotificationsSubscribe({
token: '1234',
packageName: 'com.wallet',
platform: 'Android',
}, done);
},
], next);
}); });
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -229,8 +177,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en', defaultLanguage: 'en',
defaultUnit: 'btc', defaultUnit: 'btc',
subjectPrefix: '', subjectPrefix: '',
pushServerUrl: 'http://localhost:8000',
authorizationKey: 'secret', pushServerUrl: 'http://localhost:8000/send',
}, },
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -266,14 +214,14 @@ describe('Push notifications', function() {
calls.length.should.equal(3); calls.length.should.equal(3);
args[0].body.notification.title.should.contain('Nuevo pago recibido'); args[0].body.android.data.title.should.contain('Nuevo pago recibido');
args[0].body.notification.body.should.contain('0.123'); args[0].body.android.data.message.should.contain('0.123');
args[1].body.notification.title.should.contain('New payment received'); args[1].body.android.data.title.should.contain('New payment received');
args[1].body.notification.body.should.contain('123,000'); args[1].body.android.data.message.should.contain('123,000');
args[2].body.notification.title.should.contain('New payment received'); args[2].body.android.data.title.should.contain('New payment received');
args[2].body.notification.body.should.contain('123,000'); args[2].body.android.data.message.should.contain('123,000');
done(); done();
}, 100); }, 100);
}); });
@ -369,7 +317,7 @@ describe('Push notifications', function() {
txpId = txp.id; txpId = txp.id;
async.eachSeries(_.range(1, 3), function(i, next) { async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i]; var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44btc, function(server) { helpers.getAuthServer(copayer.id44, function(server) {
server.rejectTx({ server.rejectTx({
txProposalId: txp.id, txProposalId: txp.id,
}, next); }, next);
@ -385,7 +333,9 @@ describe('Push notifications', function() {
return c.args[0]; return c.args[0];
}); });
args[0].body.notification.title.should.contain('Payment proposal rejected'); args[0].body.android.data.title.should.contain('Payment proposal rejected');
args[0].body.android.data.message.should.contain('copayer 2, copayer 3');
args[0].body.android.data.message.should.not.contain('copayer 1');
done(); done();
}, 100); }, 100);
}); });
@ -414,7 +364,7 @@ describe('Push notifications', function() {
txp = t; txp = t;
async.eachSeries(_.range(1, 3), function(i, next) { async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i]; var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44btc, function(s) { helpers.getAuthServer(copayer.id44, function(s) {
server = s; server = s;
var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H); var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H);
server.signTx({ server.signTx({
@ -442,11 +392,11 @@ describe('Push notifications', function() {
return c.args[0]; return c.args[0];
}); });
args[0].body.notification.title.should.contain('Payment sent'); args[0].body.android.data.title.should.contain('Payment sent');
args[1].body.notification.title.should.contain('Payment sent'); args[1].body.android.data.title.should.contain('Payment sent');
sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(server.copayerId)).should.not.equal(args[0].body.data.copayerId); server.copayerId.should.not.equal((args[0].body.users[0]).split('$')[1]);
sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(server.copayerId)).should.not.equal(args[1].body.data.copayerId); server.copayerId.should.not.equal((args[1].body.users[0]).split('$')[1]);
done(); done();
}, 100); }, 100);
}); });
@ -482,8 +432,7 @@ describe('Push notifications', function() {
defaultLanguage: 'en', defaultLanguage: 'en',
defaultUnit: 'btc', defaultUnit: 'btc',
subjectPrefix: '', subjectPrefix: '',
pushServerUrl: 'http://localhost:8000', pushServerUrl: 'http://localhost:8000/send',
authorizationKey: 'secret',
}, },
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -503,54 +452,44 @@ describe('Push notifications', function() {
customData: 'custom data ' + (i + 1), customData: 'custom data ' + (i + 1),
}); });
server.joinWallet(copayerOpts, function(err, res) { server.joinWallet(copayerOpts, next);
if (err) return next(err);
helpers.getAuthServer(res.copayerId, function(server) {
server.pushNotificationsSubscribe({
token: 'token:' + copayerOpts.name,
packageName: 'com.wallet',
platform: 'Android',
}, next);
});
});
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
setTimeout(function() { setTimeout(function() {
var calls = requestStub.getCalls(); var calls = requestStub.getCalls();
var args = _.filter(_.map(calls, function(call) { var args = _.map(calls, function(c) {
return call.args[0]; return c.args[0];
}), function(arg) {
return arg.body.notification.title == 'New copayer';
}); });
server.getWallet(null, function(err, wallet) { var argu = _.compact(_.map(args, function(a) {
if (a.body.android.data.title == 'New copayer')
return a;
}));
server.getWallet(null, function(err, w) {
/* /*
First call - copayer2 joined First call - copayer2 joined
copayer2 should notify to copayer1 copayer2 should notify to copayer1
copayer2 should NOT be notifyed copayer2 should NOT be notifyed
*/ */
var hashedCopayerIds = _.map(wallet.copayers, function(copayer) { w.copayers[0].id.should.contain((argu[0].body.users[0]).split('$')[1]);
return sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(copayer.id)); w.copayers[1].id.should.not.contain((argu[0].body.users[0]).split('$')[1]);
});
hashedCopayerIds[0].should.equal((args[0].body.data.copayerId));
hashedCopayerIds[1].should.not.equal((args[0].body.data.copayerId));
/* /*
Second call - copayer3 joined Second call - copayer3 joined
copayer3 should notify to copayer1 copayer3 should notify to copayer1
*/ */
hashedCopayerIds[0].should.equal((args[1].body.data.copayerId)); w.copayers[0].id.should.contain((argu[1].body.users[0]).split('$')[1]);
/* /*
Third call - copayer3 joined Third call - copayer3 joined
copayer3 should notify to copayer2 copayer3 should notify to copayer2
*/ */
hashedCopayerIds[1].should.equal((args[2].body.data.copayerId)); w.copayers[1].id.should.contain((argu[2].body.users[0]).split('$')[1]);
// copayer3 should NOT notify any other copayer // copayer3 should NOT notify any other copayer
hashedCopayerIds[2].should.not.equal((args[1].body.data.copayerId)); w.copayers[2].id.should.not.contain((argu[1].body.users[0]).split('$')[1]);
hashedCopayerIds[2].should.not.equal((args[2].body.data.copayerId)); w.copayers[2].id.should.not.contain((argu[2].body.users[0]).split('$')[1]);
done(); done();
}); });
}, 100); }, 100);

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,6 @@ describe('Address', function() {
it('should create livenet address', function() { it('should create livenet address', function() {
var x = Address.create({ var x = Address.create({
address: '3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg', address: '3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg',
coin: 'btc',
walletId: '123', walletId: '123',
isChange: false, isChange: false,
path: 'm/0/1', path: 'm/0/1',
@ -24,7 +23,6 @@ describe('Address', function() {
it('should create testnet address', function() { it('should create testnet address', function() {
var x = Address.create({ var x = Address.create({
address: 'mp5xaa4uBj16DJt1fuA3D9fejHuCzeb7hj', address: 'mp5xaa4uBj16DJt1fuA3D9fejHuCzeb7hj',
coin: 'btc',
walletId: '123', walletId: '123',
isChange: false, isChange: false,
path: 'm/0/1', path: 'm/0/1',
@ -41,7 +39,7 @@ describe('Address', function() {
}, { }, {
xPubKey: 'xpub68tpbrfk747AvDUCdtEUgK2yDPmtGKf7YXzEcUUqnF3jmAMeZgcpoZqgXwwoi8CpwDkyzVX6wxUktTw2wh9EhhVjh5S71MLL3FkZDGF5GeY' xPubKey: 'xpub68tpbrfk747AvDUCdtEUgK2yDPmtGKf7YXzEcUUqnF3jmAMeZgcpoZqgXwwoi8CpwDkyzVX6wxUktTw2wh9EhhVjh5S71MLL3FkZDGF5GeY'
// PubKey(xPubKey/0/0) -> 03162179906dbe6a67979d4f8f46ee1db6ff81715f465e6615a4f5969478ad2171 // PubKey(xPubKey/0/0) -> 03162179906dbe6a67979d4f8f46ee1db6ff81715f465e6615a4f5969478ad2171
}], 'm/0/0', 1, 'btc', 'livenet', false); }], 'm/0/0', 1, 'livenet', false);
should.exist(address); should.exist(address);
address.walletId.should.equal('wallet-id'); address.walletId.should.equal('wallet-id');
address.address.should.equal('3QN2CiSxcUsFuRxZJwXMNDQ2esnr5RXTvw'); address.address.should.equal('3QN2CiSxcUsFuRxZJwXMNDQ2esnr5RXTvw');
@ -54,7 +52,7 @@ describe('Address', function() {
var address = Address.derive('wallet-id', 'P2SH', [{ var address = Address.derive('wallet-id', 'P2SH', [{
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1' xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
// PubKey(xPubKey/0/0) -> 03fe466ea829aa4c9a1c289f9ba61ebc26a61816500860c8d23f94aad9af152ecd // PubKey(xPubKey/0/0) -> 03fe466ea829aa4c9a1c289f9ba61ebc26a61816500860c8d23f94aad9af152ecd
}], 'm/0/0', 1, 'btc', 'livenet', false); }], 'm/0/0', 1, 'livenet', false);
should.exist(address); should.exist(address);
address.walletId.should.equal('wallet-id'); address.walletId.should.equal('wallet-id');
address.address.should.equal('3BY4K8dfsHryhWh2MJ6XHxxsRfcvPAyseH'); address.address.should.equal('3BY4K8dfsHryhWh2MJ6XHxxsRfcvPAyseH');
@ -67,7 +65,7 @@ describe('Address', function() {
var address = Address.derive('wallet-id', 'P2PKH', [{ var address = Address.derive('wallet-id', 'P2PKH', [{
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1' xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
// PubKey(xPubKey/1/2) -> 0232c09a6edd8e2189628132d530c038e0b15b414cf3984e532358cbcfb83a7bd7 // PubKey(xPubKey/1/2) -> 0232c09a6edd8e2189628132d530c038e0b15b414cf3984e532358cbcfb83a7bd7
}], 'm/1/2', 1, 'btc', 'livenet', true); }], 'm/1/2', 1, 'livenet', true);
should.exist(address); should.exist(address);
address.walletId.should.equal('wallet-id'); address.walletId.should.equal('wallet-id');
address.address.should.equal('1G4wgi9YzmSSwQaQVLXQ5HUVquQDgJf8oT'); address.address.should.equal('1G4wgi9YzmSSwQaQVLXQ5HUVquQDgJf8oT');

View File

@ -17,7 +17,7 @@ describe('Copayer', function() {
}); });
}); });
describe('#createAddress', function() { describe('#createAddress', function() {
it('should create an address', function() { it('create an address', function() {
var w = Wallet.fromObj(testWallet); var w = Wallet.fromObj(testWallet);
var c = Copayer.fromObj(testWallet.copayers[2]); var c = Copayer.fromObj(testWallet.copayers[2]);
should.exist(c.requestPubKeys); should.exist(c.requestPubKeys);
@ -42,7 +42,6 @@ var testWallet = {
createdOn: 1422904188, createdOn: 1422904188,
id: '123', id: '123',
name: '123 wallet', name: '123 wallet',
network: 'livenet',
m: 2, m: 2,
n: 3, n: 3,
status: 'complete', status: 'complete',

View File

@ -24,11 +24,6 @@ describe('TxProposal', function() {
should.exist(txp); should.exist(txp);
txp.amount.should.equal(aTXP().amount); txp.amount.should.equal(aTXP().amount);
}); });
it('should default to BTC coin', function() {
var txp = TxProposal.fromObj(aTXP());
should.exist(txp);
txp.coin.should.equal('btc');
});
}); });
describe('#getBitcoreTx', function() { describe('#getBitcoreTx', function() {
@ -113,10 +108,8 @@ var theXPub = 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2
var theSignatures = ['304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6']; var theSignatures = ['304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6'];
var theRawTx = '0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e08000000910047304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0380969800000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac002d3101000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70f62b040000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000'; var theRawTx = '0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e08000000910047304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0380969800000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac002d3101000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70f62b040000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000';
var aTxpOpts = function() { var aTxpOpts = function(type) {
var opts = { var opts = {
coin: 'btc',
network: 'livenet',
message: 'some message' message: 'some message'
}; };
opts.outputs = [{ opts.outputs = [{
@ -132,7 +125,7 @@ var aTxpOpts = function() {
return opts; return opts;
}; };
var aTXP = function() { var aTXP = function(type) {
var txp = { var txp = {
"version": 3, "version": 3,
"createdOn": 1423146231, "createdOn": 1423146231,

View File

@ -54,8 +54,6 @@ describe('Storage', function() {
name: 'my wallet', name: 'my wallet',
m: 2, m: 2,
n: 3, n: 3,
coin: 'btc',
network: 'livenet',
}); });
should.exist(wallet); should.exist(wallet);
storage.storeWallet(wallet, function(err) { storage.storeWallet(wallet, function(err) {
@ -87,12 +85,9 @@ describe('Storage', function() {
name: 'my wallet', name: 'my wallet',
m: 2, m: 2,
n: 3, n: 3,
coin: 'btc',
network: 'livenet',
}); });
_.each(_.range(3), function(i) { _.each(_.range(3), function(i) {
var copayer = Model.Copayer.create({ var copayer = Model.Copayer.create({
coin: 'btc',
name: 'copayer ' + i, name: 'copayer ' + i,
xPubKey: 'xPubKey ' + i, xPubKey: 'xPubKey ' + i,
requestPubKey: 'requestPubKey ' + i, requestPubKey: 'requestPubKey ' + i,
@ -132,12 +127,9 @@ describe('Storage', function() {
name: 'my wallet', name: 'my wallet',
m: 2, m: 2,
n: 3, n: 3,
coin: 'btc',
network: 'livenet',
}); });
_.each(_.range(3), function(i) { _.each(_.range(3), function(i) {
var copayer = Model.Copayer.create({ var copayer = Model.Copayer.create({
coin: 'btc',
name: 'copayer ' + i, name: 'copayer ' + i,
xPubKey: 'xPubKey ' + i, xPubKey: 'xPubKey ' + i,
requestPubKey: 'requestPubKey ' + i, requestPubKey: 'requestPubKey ' + i,
@ -152,8 +144,6 @@ describe('Storage', function() {
proposals = _.map(_.range(4), function(i) { proposals = _.map(_.range(4), function(i) {
var tx = Model.TxProposal.create({ var tx = Model.TxProposal.create({
walletId: '123', walletId: '123',
coin: 'btc',
network: 'livenet',
outputs: [{ outputs: [{
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: i + 100, amount: i + 100,

View File

@ -4,8 +4,7 @@ var keyPair = {
}; };
var copayers = [{ var copayers = [{
id44btc: '626452e5e0e35df4d9ae4d3e60653c9ae9a814f00c84dc40f5887069b18e2110', id44: '626452e5e0e35df4d9ae4d3e60653c9ae9a814f00c84dc40f5887069b18e2110',
id44bch: '671fee02a6c1c4de2e2609f9f9a6180dc03acfff6b759fe0b13a616ed4880065',
id45: 'e7467366d5754be2b7d386c9737ab87214c26314bdc3489702e09c719be1bdb7', id45: 'e7467366d5754be2b7d386c9737ab87214c26314bdc3489702e09c719be1bdb7',
xPrivKey: 'xprv9s21ZrQH143K2n4rV4AtAJFptEmd1tNMKCcSyQBCSuN5eq1dCUhcv6KQJS49joRxu8NNdFxy8yuwTtzCPNYUZvVGC7EPRm2st2cvE7oyTbB', xPrivKey: 'xprv9s21ZrQH143K2n4rV4AtAJFptEmd1tNMKCcSyQBCSuN5eq1dCUhcv6KQJS49joRxu8NNdFxy8yuwTtzCPNYUZvVGC7EPRm2st2cvE7oyTbB',
xPubKey: 'xpub661MyMwAqRbcFG9Kb5htXSCZSGc7RM6CgRY3mnap1Eu4XdLmk21sTtdt9iWAiL64KazU3QWrYEYSRAKgLvkMRe8JMxffDvt4AhCDzyMsnsT', xPubKey: 'xpub661MyMwAqRbcFG9Kb5htXSCZSGc7RM6CgRY3mnap1Eu4XdLmk21sTtdt9iWAiL64KazU3QWrYEYSRAKgLvkMRe8JMxffDvt4AhCDzyMsnsT',
@ -13,18 +12,12 @@ var copayers = [{
xPubKey_45H: 'xpub68pKcb8jHWqWuTgPz2czjFSJJBJTsTNdd87Mgh5bVz4sNFBJBus5KyptGBWgA4V6LGCi12s4Mw4S1JC2GkqX4NJ4kfQ47XqRZLbyM2DY9Jd', xPubKey_45H: 'xpub68pKcb8jHWqWuTgPz2czjFSJJBJTsTNdd87Mgh5bVz4sNFBJBus5KyptGBWgA4V6LGCi12s4Mw4S1JC2GkqX4NJ4kfQ47XqRZLbyM2DY9Jd',
xPrivKey_44H_0H_0H: 'xprv9zWRZ7CXrC4z9xA9RRBFXohmPKbyCajWaCNTHPtwNeJwTnysHG5QK7WMqpNLVtvqGxts7WNcNtqBLfdaFdCGknDPXjLKt2E2BUrPaFDqrLh', xPrivKey_44H_0H_0H: 'xprv9zWRZ7CXrC4z9xA9RRBFXohmPKbyCajWaCNTHPtwNeJwTnysHG5QK7WMqpNLVtvqGxts7WNcNtqBLfdaFdCGknDPXjLKt2E2BUrPaFDqrLh',
xPubKey_44H_0H_0H: 'xpub6DVmxcjRgZdHNSEcXSiFtweVwMSTc3TMwRJ45nJYvyqvLbK1poPerupqh87rSoz27wvckb1CKnGZoLmLXSZyNGZtVd7neqSvdwJL6fceQpe', xPubKey_44H_0H_0H: 'xpub6DVmxcjRgZdHNSEcXSiFtweVwMSTc3TMwRJ45nJYvyqvLbK1poPerupqh87rSoz27wvckb1CKnGZoLmLXSZyNGZtVd7neqSvdwJL6fceQpe',
xPrivKey_44H_0H_0Ht: 'tprv8ZgxMBicQKsPcxUEtgtQ2wKpkmuNKS6R2w3UmFTUHHURv4PKGE2aGkkbQEcQs9gGsoW4zPr7VM98xdbjQuWc3cZ6bkEyKy1sywhV9gLUcUi',
xPubKey_44H_0H_0Ht: 'tpubD6NzVbkrYhZ4WRW2nLYzSLywKoRJUmHKcEeG3mVmhZGpkYe5tcrATFNTaQRAWM3dzL2QyXoctpjkaAXruDXyc6xkF4EDGu3eQdwZXFzoFSW',
xPrivKey_1H: 'xprv9upyD5bqT9HBkWym7TvTH3njEzTnjrtkLB2sg3DD2CxxA5hZKGee1sYJUtD8C4QaeATLXQ33TirRzRhuTGDBA6XRoYDMwfXAj1KSmGyNBio', xPrivKey_1H: 'xprv9upyD5bqT9HBkWym7TvTH3njEzTnjrtkLB2sg3DD2CxxA5hZKGee1sYJUtD8C4QaeATLXQ33TirRzRhuTGDBA6XRoYDMwfXAj1KSmGyNBio',
xPubKey_1H: 'xpub68pKcb8jHWqUy14EDVTTeBjTo2JH9KcbhPxUURcpaYVw2t2hroxtZfrnLBw1bWzBrHbEJA48QmZ8DB9gTvhphKSitC15SiYx9k2ncGh55Hq', xPubKey_1H: 'xpub68pKcb8jHWqUy14EDVTTeBjTo2JH9KcbhPxUURcpaYVw2t2hroxtZfrnLBw1bWzBrHbEJA48QmZ8DB9gTvhphKSitC15SiYx9k2ncGh55Hq',
privKey_1H_0: 'a710be25950738a7d13637e2e09affd7f579a3479fd7cc024bd9459f8fba6659', privKey_1H_0: 'a710be25950738a7d13637e2e09affd7f579a3479fd7cc024bd9459f8fba6659',
pubKey_1H_0: '026e3020913420a5b9425952627f0a074c9235e7a329869b322061f786e997ae0d' pubKey_1H_0: '026e3020913420a5b9425952627f0a074c9235e7a329869b322061f786e997ae0d'
}, { }, {
id44btc: '842c048066e7d10ae1bbf67edccf69f2e5ff9a754d0c2b5524f0d01a87d6acbb', id44: '842c048066e7d10ae1bbf67edccf69f2e5ff9a754d0c2b5524f0d01a87d6acbb',
id44bch: '0d8f0c0ebfb11ad589002fd4539075c6fb625fb1725406ca442726c6bc6746b1',
id45: 'ee75154b646277c8d0d256fc1a0aa0470e4c3435497f208092c865737040b55b', id45: 'ee75154b646277c8d0d256fc1a0aa0470e4c3435497f208092c865737040b55b',
xPrivKey: 'xprv9s21ZrQH143K3BwkLceWNLUsgES15JoZuv8BZfnmDRcCGtDooUAPhY8KovhCWcRLXUun5AYL5vVtUNRrmPEibtfk9ongxAGLXZzEHifpvwZ', xPrivKey: 'xprv9s21ZrQH143K3BwkLceWNLUsgES15JoZuv8BZfnmDRcCGtDooUAPhY8KovhCWcRLXUun5AYL5vVtUNRrmPEibtfk9ongxAGLXZzEHifpvwZ',
xPubKey: 'xpub661MyMwAqRbcFg2DSeBWjURcEGGVUmXRH93nN4CNmm9B9gYxM1UeFLSofD6gtMnRYeucgPjfrWNxaAEhiT4di6HLty8Un6aheCKev4REvhZ', xPubKey: 'xpub661MyMwAqRbcFg2DSeBWjURcEGGVUmXRH93nN4CNmm9B9gYxM1UeFLSofD6gtMnRYeucgPjfrWNxaAEhiT4di6HLty8Un6aheCKev4REvhZ',
@ -37,8 +30,7 @@ var copayers = [{
privKey_1H_0: 'ee062ce6dc5ece50e8110646b5e858c98dba9315cdfdd19da85ab0d33dcac74a', privKey_1H_0: 'ee062ce6dc5ece50e8110646b5e858c98dba9315cdfdd19da85ab0d33dcac74a',
pubKey_1H_0: '02c679bf169233a273dec87fae5a1830481866c4e96a350d56346ac267808c905d' pubKey_1H_0: '02c679bf169233a273dec87fae5a1830481866c4e96a350d56346ac267808c905d'
}, { }, {
id44btc: '719f4ee61c691fbf0ebefa34e2151a1a3dbe39cf2fa4a498cb6af53600d30d1a', id44: '719f4ee61c691fbf0ebefa34e2151a1a3dbe39cf2fa4a498cb6af53600d30d1a',
id44bch: '56ed2c8d04c4aa29e9d6408724197c27d1fa0b71e2c2a6b91a4cf9710f09eb0a',
id45: 'acd666d7c677d9f2c85b55a5fad1610fe272eac46ef7a577c7aeeab0b1474e43', id45: 'acd666d7c677d9f2c85b55a5fad1610fe272eac46ef7a577c7aeeab0b1474e43',
xPrivKey: 'xprv9s21ZrQH143K3xgLzxd6SuWqG5Zp1iUmyGgSsJVhdQNeTzAqBFvXXLZqZzFZqocTx4HD9vUVYU27At5i8q46LmBXXL97fo4H9C3tHm4BnjY', xPrivKey: 'xprv9s21ZrQH143K3xgLzxd6SuWqG5Zp1iUmyGgSsJVhdQNeTzAqBFvXXLZqZzFZqocTx4HD9vUVYU27At5i8q46LmBXXL97fo4H9C3tHm4BnjY',
xPubKey: 'xpub661MyMwAqRbcGSkp6zA6p3TZp7QJRBCdLVc3fguKBjudLnVyioEn58tKRFPmGMkdGJWMX69mgZWHKrKmpQ3fwBXeFjLc5Sd2rnxcQthSW42', xPubKey: 'xpub661MyMwAqRbcGSkp6zA6p3TZp7QJRBCdLVc3fguKBjudLnVyioEn58tKRFPmGMkdGJWMX69mgZWHKrKmpQ3fwBXeFjLc5Sd2rnxcQthSW42',
@ -51,8 +43,7 @@ var copayers = [{
privKey_1H_0: '5009c8488e9a364fc24a999d99a81ae955271de1d06d46c2f2f09e20c6281b04', privKey_1H_0: '5009c8488e9a364fc24a999d99a81ae955271de1d06d46c2f2f09e20c6281b04',
pubKey_1H_0: '03338a3b7c08e9d9832e1baff0758e08f9cc691497dd6e91d4c191cd960fb2f043' pubKey_1H_0: '03338a3b7c08e9d9832e1baff0758e08f9cc691497dd6e91d4c191cd960fb2f043'
}, { }, {
id44btc: 'e225a29864060823df67b98432b070a40aad1bf9af517005b0b5fe09c96e29c9', id44: 'e225a29864060823df67b98432b070a40aad1bf9af517005b0b5fe09c96e29c9',
id44bch: '2baf290be693407fd9c32597608b6fd90ba60f65b2b81b58e9fe9c960938de11',
id45: 'c65a89f64794cb7e1886c7010a32dd6fa362d3e81710bac32e97e325b9109fd8', id45: 'c65a89f64794cb7e1886c7010a32dd6fa362d3e81710bac32e97e325b9109fd8',
xPrivKey: 'xprv9s21ZrQH143K48nfuK14gKJtML7eQzV2dAH1RaqAMj8v2zs79uaavA9UTWMxpBdgbMH2mhJLeKGq8AFA6GDnFyWP4rLmknqZAfgFFV718vo', xPrivKey: 'xprv9s21ZrQH143K48nfuK14gKJtML7eQzV2dAH1RaqAMj8v2zs79uaavA9UTWMxpBdgbMH2mhJLeKGq8AFA6GDnFyWP4rLmknqZAfgFFV718vo',
xPubKey: 'xpub661MyMwAqRbcGcs91LY53TFcuMx8pTCszPCcDyEmv4ftuoCFhStqTxTxJoy35yjp2H3qQtxDYGe1gtkZu4T7mR7ARK1MLYte2fptZVt6hkD', xPubKey: 'xpub661MyMwAqRbcGcs91LY53TFcuMx8pTCszPCcDyEmv4ftuoCFhStqTxTxJoy35yjp2H3qQtxDYGe1gtkZu4T7mR7ARK1MLYte2fptZVt6hkD',
@ -65,8 +56,7 @@ var copayers = [{
privKey_1H_0: '460ee692f05de66b5d8e2fa1d005a8b6bdb1442e2ce6b3facfcee2f9012c9474', privKey_1H_0: '460ee692f05de66b5d8e2fa1d005a8b6bdb1442e2ce6b3facfcee2f9012c9474',
pubKey_1H_0: '03d0e0c526619b158aac9a8de8082f439df43d389ec50cb54386c3d87cfde4c99b' pubKey_1H_0: '03d0e0c526619b158aac9a8de8082f439df43d389ec50cb54386c3d87cfde4c99b'
}, { }, {
id44btc: '120416cd4c427a7e4d94213cebe242f56a06bc6dd5c5c6cae27dc920a0ddf1fb', id44: '120416cd4c427a7e4d94213cebe242f56a06bc6dd5c5c6cae27dc920a0ddf1fb',
id44bch: '4abc36e7731c08e0a93483691c3cb451013463ccee1b676e4a20d98cd1de8af3',
id45: '65ae087eb9efdc7e0ada3a7ef954285e9e5ba4b8c7ab2d36747ddd286f7a334f', id45: '65ae087eb9efdc7e0ada3a7ef954285e9e5ba4b8c7ab2d36747ddd286f7a334f',
xPrivKey: 'xprv9s21ZrQH143K44Bb9G3EVNmLfAUKjTBAA2YtKxF4zc8SLV1o15JBoddhGHE9PGLXePMbEsSjCCvTvP3fUv6yMXZrnHigBboRBn2DmNoJkJg', xPrivKey: 'xprv9s21ZrQH143K44Bb9G3EVNmLfAUKjTBAA2YtKxF4zc8SLV1o15JBoddhGHE9PGLXePMbEsSjCCvTvP3fUv6yMXZrnHigBboRBn2DmNoJkJg',
xPubKey: 'xpub661MyMwAqRbcGYG4FHaErWi5DCJp8uu1XFUV8LegYwfRDHLwYccSMRxB7Z3L1NgKychKdXQvbVEyDhSwNnNnnNKh9mBEAdQ5tv2guK8ywKU', xPubKey: 'xpub661MyMwAqRbcGYG4FHaErWi5DCJp8uu1XFUV8LegYwfRDHLwYccSMRxB7Z3L1NgKychKdXQvbVEyDhSwNnNnnNKh9mBEAdQ5tv2guK8ywKU',
@ -79,8 +69,7 @@ var copayers = [{
privKey_1H_0: '7a5158b92d9ed4cb9644ddbd472b43428832a5f3bb91a481532a081908e62b2e', privKey_1H_0: '7a5158b92d9ed4cb9644ddbd472b43428832a5f3bb91a481532a081908e62b2e',
pubKey_1H_0: '02b47d5c977c93c883f369165ebc2b564d14a52712ec6892f7097fa99e0d36ca20' pubKey_1H_0: '02b47d5c977c93c883f369165ebc2b564d14a52712ec6892f7097fa99e0d36ca20'
}, { }, {
id44btc: '85de9f025ee190fab7cb1bd9b6772c64df26188ce705d4f258c5adaf7bc610f9', id44: '85de9f025ee190fab7cb1bd9b6772c64df26188ce705d4f258c5adaf7bc610f9',
id44bch: '0845739e508fb8f7b28e10bed9d827968a12d2dbd6ecbac3303305fcaf535bfe',
id45: 'dacc5c350cef4449a3ca12939711c7449d0d6189e5e7f33cff60095a7a29b0f9', id45: 'dacc5c350cef4449a3ca12939711c7449d0d6189e5e7f33cff60095a7a29b0f9',
xPrivKey: 'xprv9s21ZrQH143K48PpVxrh71KdViTFhAaiDSVtNFkmbWNYjwwwPbTrcqoVXsgBfue3Gq9b71hQeEbk67JgtTBcpYgKLF8pTwVnGz56f1BaCYt', xPrivKey: 'xprv9s21ZrQH143K48PpVxrh71KdViTFhAaiDSVtNFkmbWNYjwwwPbTrcqoVXsgBfue3Gq9b71hQeEbk67JgtTBcpYgKLF8pTwVnGz56f1BaCYt',
xPubKey: 'xpub661MyMwAqRbcGcUHbzPhU9GN3kHk6dJZafRVAeAP9quXckH5w8n7Ae7yP8e2Zh6SPPKFn2K6oE3GBpcz9QzfJTNRWXbY7w1L3nGLE5beZL1', xPubKey: 'xpub661MyMwAqRbcGcUHbzPhU9GN3kHk6dJZafRVAeAP9quXckH5w8n7Ae7yP8e2Zh6SPPKFn2K6oE3GBpcz9QzfJTNRWXbY7w1L3nGLE5beZL1',
@ -93,8 +82,7 @@ var copayers = [{
privKey_1H_0: '3c49816d4e83d8758f89e8e104e3566a8a61426a9b7d4945b34212fbbb8e8290', privKey_1H_0: '3c49816d4e83d8758f89e8e104e3566a8a61426a9b7d4945b34212fbbb8e8290',
pubKey_1H_0: '0307ab8c0d8eea1fe3c3781050a69e71f9e7c8cc8476a77103e08a461506a0e780' pubKey_1H_0: '0307ab8c0d8eea1fe3c3781050a69e71f9e7c8cc8476a77103e08a461506a0e780'
}, { }, {
id44btc: '4d0c1eaab0aafc08aea7328f9ed1d3fc2812791ad2ebb9cbc1a8537b51b18afa', id44: '4d0c1eaab0aafc08aea7328f9ed1d3fc2812791ad2ebb9cbc1a8537b51b18afa',
id44bch: '63ed91d8b7c4f06028d4a795cbb30d91772d93c99e7cc612d9f0b33a4fa215de',
id45: '9129a0454adcf659f4f9d65a9b4dc4f9793bd1f59664268b56a7ef73f29f1b8a', id45: '9129a0454adcf659f4f9d65a9b4dc4f9793bd1f59664268b56a7ef73f29f1b8a',
xPrivKey: 'xprv9s21ZrQH143K3pgRcRBRnmcxNkNNLmJrpneMkEXY6o5TWBuJLMfdRpAWdb2cG3yxbL4DxfpUnQpjfQUmwPdVrRGoDJmtAf5u8cyqKCoDV97', xPrivKey: 'xprv9s21ZrQH143K3pgRcRBRnmcxNkNNLmJrpneMkEXY6o5TWBuJLMfdRpAWdb2cG3yxbL4DxfpUnQpjfQUmwPdVrRGoDJmtAf5u8cyqKCoDV97',
xPubKey: 'xpub661MyMwAqRbcGJktiSiS9uZgvnCrkE2iC1ZxYcw9f8cSNzESstysycUzUsDCU6KnnjR29VZ1eRAXDgEXfYxGw1B9E7VLSAcHa9UuifSozmy', xPubKey: 'xpub661MyMwAqRbcGJktiSiS9uZgvnCrkE2iC1ZxYcw9f8cSNzESstysycUzUsDCU6KnnjR29VZ1eRAXDgEXfYxGw1B9E7VLSAcHa9UuifSozmy',
@ -107,8 +95,7 @@ var copayers = [{
privKey_1H_0: '87f8a2b92dd04d2782c3d40a34f09f2ab42076bd02b81fbe4a4a72f87ad2e6df', privKey_1H_0: '87f8a2b92dd04d2782c3d40a34f09f2ab42076bd02b81fbe4a4a72f87ad2e6df',
pubKey_1H_0: '02a0370d6f1213ab3390ac666585614ad71146f3f28ec326e2e779f999c1a497eb' pubKey_1H_0: '02a0370d6f1213ab3390ac666585614ad71146f3f28ec326e2e779f999c1a497eb'
}, { }, {
id44btc: '5ae7b75deb3b4d7e251f1fc5613904c9ef8548af7601d93ef668299be4f75ddd', id44: '5ae7b75deb3b4d7e251f1fc5613904c9ef8548af7601d93ef668299be4f75ddd',
id44bch: '375a87b5614473ad359fee0385e9ffcb01d78c7880b34987e59da06eeac8029a',
id45: '37b81e2544b43ce7f37a132a748426e1566ecbb758564d4d7d07b716fbe1b368', id45: '37b81e2544b43ce7f37a132a748426e1566ecbb758564d4d7d07b716fbe1b368',
xPrivKey: 'xprv9s21ZrQH143K3nvcmdjDDDZbDJHpfWZCUiunwraZdcamYcafHvUnZfV51fivH9FPyfo12NyKH5JDxGLsQePyWKtTiJx3pkEaiwxsMLkVapp', xPrivKey: 'xprv9s21ZrQH143K3nvcmdjDDDZbDJHpfWZCUiunwraZdcamYcafHvUnZfV51fivH9FPyfo12NyKH5JDxGLsQePyWKtTiJx3pkEaiwxsMLkVapp',
xPubKey: 'xpub661MyMwAqRbcGH15sfGDaMWKmL8K4yH3qwqPkEzBBx7kRQuoqTo37ToYrvLJh7JpV5FQSverERMcdF4HcP1UCiie2ayeMXRq67zr75PzMKs', xPubKey: 'xpub661MyMwAqRbcGH15sfGDaMWKmL8K4yH3qwqPkEzBBx7kRQuoqTo37ToYrvLJh7JpV5FQSverERMcdF4HcP1UCiie2ayeMXRq67zr75PzMKs',
@ -121,8 +108,7 @@ var copayers = [{
privKey_1H_0: '66230b6b8b65725162ea43313fcc233f4f0dd135cea00d04b73a84d3f681ef25', privKey_1H_0: '66230b6b8b65725162ea43313fcc233f4f0dd135cea00d04b73a84d3f681ef25',
pubKey_1H_0: '03f148bde0784c80051acd159b28a30022e685aca56418f8f50100d9f8a0192c37' pubKey_1H_0: '03f148bde0784c80051acd159b28a30022e685aca56418f8f50100d9f8a0192c37'
}, { }, {
id44btc: '98e78a9cb2ab340a245c5082897eadb28c367319f97b93e7b51b4d5ca5cdc68e', id44: '98e78a9cb2ab340a245c5082897eadb28c367319f97b93e7b51b4d5ca5cdc68e',
id44bch: 'f390e03140593c0c724e0d3a2a9cf39d63319edc833024a149a72efacb368737',
id45: 'e1557d3421a8884fe007674f3f0b6f0feafa76289a0edcc5ec736161b4d02257', id45: 'e1557d3421a8884fe007674f3f0b6f0feafa76289a0edcc5ec736161b4d02257',
xPrivKey: 'xprv9s21ZrQH143K2uYgqtYtphEQkFAgiWSqahFUWjgCdKykJagiNDz6Lf7xRVQdtZ7MvkhX9V3pEcK3xTAWZ6Y6ecJqrXnCpzrH9GSHn8wyrT5', xPrivKey: 'xprv9s21ZrQH143K2uYgqtYtphEQkFAgiWSqahFUWjgCdKykJagiNDz6Lf7xRVQdtZ7MvkhX9V3pEcK3xTAWZ6Y6ecJqrXnCpzrH9GSHn8wyrT5',
xPubKey: 'xpub661MyMwAqRbcFPd9wv5uBqB9JH1B7yAgwvB5K85pBfWjBP1rumJLtTSSGnCdsJSXfwmTyexsRjbUhzB4J6LWfL8mC2Ka117JrnXetyCzk3r', xPubKey: 'xpub661MyMwAqRbcFPd9wv5uBqB9JH1B7yAgwvB5K85pBfWjBP1rumJLtTSSGnCdsJSXfwmTyexsRjbUhzB4J6LWfL8mC2Ka117JrnXetyCzk3r',
@ -135,8 +121,7 @@ var copayers = [{
privKey_1H_0: '9e215580c8e5876215ad101ded325bcacc5ab9d97b26e8fdfab89ef5bb6e0ab7', privKey_1H_0: '9e215580c8e5876215ad101ded325bcacc5ab9d97b26e8fdfab89ef5bb6e0ab7',
pubKey_1H_0: '0265d33caaa128a77cc38ab8751c7d730e0274a212f1f65b73f637eddb3a3fb151' pubKey_1H_0: '0265d33caaa128a77cc38ab8751c7d730e0274a212f1f65b73f637eddb3a3fb151'
}, { }, {
id44btc: 'f716dbeec58e44c698b34c2d81bae4699ed5a5a522281733ec50aa03caf76a19', id44: 'f716dbeec58e44c698b34c2d81bae4699ed5a5a522281733ec50aa03caf76a19',
id44bch: 'da39b3d560d2d99d9557a5a70ca3dc4561c3930e2850748fa80bdcecb650a9bf',
id45: '8a6d840580549a34422c9b150dbd1e96e369c5db69ee736caab95616f8abb22b', id45: '8a6d840580549a34422c9b150dbd1e96e369c5db69ee736caab95616f8abb22b',
xPrivKey: 'xprv9s21ZrQH143K2wcRMP75tAEL5JnUx4xU2AbUBQzVVUDP7DHZJkjF3kaRE7tcnPLLLL9PGjYTWTJmCQPaQ4GGzgWEUFJ6snwJG9YnQHBFRNR', xPrivKey: 'xprv9s21ZrQH143K2wcRMP75tAEL5JnUx4xU2AbUBQzVVUDP7DHZJkjF3kaRE7tcnPLLLL9PGjYTWTJmCQPaQ4GGzgWEUFJ6snwJG9YnQHBFRNR',
xPubKey: 'xpub661MyMwAqRbcFRgtTQe6FJB4dLcyMXgKPPX4yoQ73okMz1chrJ3VbYtu5PRTxMBGuXt6eyqwAuG2BEBzQPLc1x8gnSQiATS3GRzKi1BuQAR', xPubKey: 'xpub661MyMwAqRbcFRgtTQe6FJB4dLcyMXgKPPX4yoQ73okMz1chrJ3VbYtu5PRTxMBGuXt6eyqwAuG2BEBzQPLc1x8gnSQiATS3GRzKi1BuQAR',
@ -150,8 +135,8 @@ var copayers = [{
pubKey_1H_0: '0266cdb57b8a4d7c1b5b20ddeea43705420c6e3aef2c2979a3768b7b585839a0d3' pubKey_1H_0: '0266cdb57b8a4d7c1b5b20ddeea43705420c6e3aef2c2979a3768b7b585839a0d3'
}, ]; }, ];
var history = [
var history = [{ {
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04", txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
vin: [{ vin: [{
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d", txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",
@ -190,7 +175,8 @@ var history = [{
valueOut: 0.01345753, valueOut: 0.01345753,
valueIn: 0.01371235, valueIn: 0.01371235,
fees: 0.00025482 fees: 0.00025482
}, { },
{
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04", txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
vin: [{ vin: [{
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d", txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",

View File

@ -131,42 +131,4 @@ describe('Utils', function() {
}); });
}); });
}); });
describe('#getAddressCoin', function() {
it('should identify btc as coin for 1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', function() {
Utils.getAddressCoin('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA').should.equal('btc');
});
it('should identify bch as coin for CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz', function() {
Utils.getAddressCoin('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz').should.equal('bch');
});
it('should return null for 1L', function() {
should.not.exist(Utils.getAddressCoin('1L'));
});
});
describe('#translateAddress', function() {
it('should translate address from btc to bch', function() {
var res = Utils.translateAddress('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'bch');
res.should.equal('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz');
});
it('should translate address from bch to btc', function() {
var res = Utils.translateAddress('HBf8isgS8EXG1r3X6GP89FmooUmiJ42wHS', 'btc');
res.should.equal('36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r');
});
it('should keep the address if there is nothing to do (bch)', function() {
var res = Utils.translateAddress('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz', 'bch');
res.should.equal('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz');
});
it('should keep the address if there is nothing to do (btc)', function() {
var res = Utils.translateAddress('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'btc');
should.exist(res);
res.should.equal('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA');
});
});
}); });