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
- clang
node_js:
- '8'
- '4'
before_install:
- export CXX="g++-4.8" CC="gcc-4.8"
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.
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/
# Getting Started
# Install
```
git clone https://github.com/bitpay/bitcore-wallet-service.git
cd bitcore-wallet-service && npm start
npm install bitcore-wallet-service
npm start
```
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 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
* 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.
@ -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.
* 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
Note: all currency amounts are in units of satoshis (1/100,000,000 of a bitcoin).
## Authentication
In order to access a wallet, clients are required to send the headers:
@ -132,18 +99,6 @@ Returns:
* availableConfirmedAmount: Same as availableAmount for confirmed UTXOs only.
* 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).
`/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
`/v1/wallets/`: Create a new Wallet
@ -160,7 +115,6 @@ Returns:
`/v1/wallets/:id/copayers/`: Join a Wallet in creation
Required Arguments:
* walletId: Id of the wallet to join
* name: Copayer Name
@ -173,11 +127,10 @@ Returns:
* wallet: Object with wallet's information
`/v1/txproposals/`: Add a new transaction proposal
Required Arguments:
* toAddress: RCPT Bitcoin address.
* 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) payProUrl: Paypro URL for peers to verify 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.
`/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:
* 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,24 +168,20 @@ Returns:
Optional Arguments:
* 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
`/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
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.
`/v1/txconfirmations/:txid`: Unsubscribe from transaction `txid` and no longer listen to its confirmation.
# 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:
* [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.
## DELETE Endpoints
`/v2/pushnotifications/subscriptions/`: Remove subscriptions for push notifications service from database.
## DELETE Endopints
`/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,31 +38,16 @@ var config = {
},
},
blockchainExplorerOpts: {
btc: {
livenet: {
provider: 'insight',
url: 'https://explorer.btcprivate.org:443',
},
testnet: {
provider: 'insight',
url: 'https://explorer.testnet.btcprivate.org:443',
// Multiple servers (in priority order)
// url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'],
},
livenet: {
provider: 'insight',
url: 'https://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
},
testnet: {
provider: 'insight',
url: 'https://test-insight.bitpay.com:443',
// url: 'http://localhost:3001',
// Multiple servers (in priority order)
// url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'],
},
},
pushNotificationsOpts: {
@ -70,8 +55,7 @@ var config = {
defaultLanguage: 'en',
defaultUnit: 'btc',
subjectPrefix: '',
pushServerUrl: 'https://fcm.googleapis.com/fcm',
authorizationKey: '',
pushServerUrl: 'http://localhost:8000',
},
fiatRateServiceOpts: {
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;
var Insight = require('./blockchainexplorers/insight');
var Common = require('./common');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
var PROVIDERS = {
'insight': {
'btc': {
'livenet': 'https://explorer.btcprivate.org:443',
'testnet': 'https://explorer.testnet.btcprivate.org:443',
},
'bch': {
'livenet': 'https://bch-insight.bitpay.com:443',
'testnet': 'https://test-bch-insight.bitpay.com:443',
},
'livenet': 'https://insight.bitpay.com:443',
'testnet': 'https://test-insight.bitpay.com:443',
},
};
@ -28,32 +18,20 @@ function BlockChainExplorer(opts) {
$.checkArgument(opts);
var provider = opts.provider || 'insight';
var coin = opts.coin || Defaults.COIN;
var network = opts.network || 'livenet';
$.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][coin]), network), 'Network ' + network + ' not supported by this provider for coin ' + coin);
$.checkState(_.contains(_.keys(PROVIDERS[provider]), network), 'Network ' + network + ' not supported by this provider');
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) {
case 'insight':
return new Insight({
coin: coin,
network: network,
url: url,
apiPrefix: opts.apiPrefix,
userAgent: opts.userAgent,
addressFormat: opts.addressFormat,
});
default:
throw new Error('Provider ' + provider + ' not supported.');

View File

@ -1,38 +1,23 @@
'use strict';
var _ = require('lodash');
var async = require('async');
var $ = require('preconditions').singleton();
var log = require('npmlog');
log.debug = log.verbose;
var io = require('socket.io-client');
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) {
$.checkArgument(opts);
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
$.checkArgument(_.contains(['livenet', 'testnet'], opts.network));
$.checkArgument(opts.url);
this.apiPrefix = _.isUndefined(opts.apiPrefix)? '/api' : opts.apiPrefix;
this.coin = opts.coin || Defaults.COIN;
this.apiPrefix = opts.apiPrefix || '/api';
this.network = opts.network || 'livenet';
this.hosts = opts.url;
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) {
if (err) {
@ -43,42 +28,6 @@ var _parseErr = function(err, res) {
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) {
var opts = {
hosts: this.hosts,
@ -86,43 +35,28 @@ Insight.prototype._doRequest = function(args, cb) {
'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);
};
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
*/
Insight.prototype.getUtxos = function(addresses, cb) {
var self = this;
var url = this.url + this.apiPrefix + '/addrs/utxo';
var args = {
method: 'POST',
path: this.apiPrefix + '/addrs/utxo',
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 (self.addressFormat) {
_.each(unspent, function(x) {
x.address = self.translateResultAddresses(x.address);
});
}
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));
return cb(null, body ? body.txid : null);
});
};
Insight.prototype.getTransaction = function(txid, cb) {
var self = this;
var args = {
method: 'GET',
path: this.apiPrefix + '/tx/' + txid,
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 (err || res.statusCode !== 200)
return cb(_parseErr(err, res));
self.translateTx(tx);
return cb(null, tx);
});
};
Insight.prototype.getTransactions = function(addresses, from, to, cb) {
var self = this;
var qs = [];
var total;
if (_.isNumber(from)) qs.push('from=' + from);
@ -182,33 +110,24 @@ Insight.prototype.getTransactions = function(addresses, from, to, cb) {
method: 'POST',
path: this.apiPrefix + '/addrs/txs' + (qs.length > 0 ? '?' + qs.join('&') : ''),
json: {
addrs: this.translateQueryAddresses(_.uniq([].concat(addresses))).join(',')
addrs: [].concat(addresses).join(',')
},
timeout: 120000,
};
this.requestQueue.push(args, function(err, res, txs) {
this._doRequest(args, function(err, res, txs) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
if (_.isObject(txs)) {
if (txs.totalItems)
total = txs.totalItems;
if (txs.items)
if (txs.items)
txs = txs.items;
}
// 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 (self.addressFormat) {
_.each(txs, function(tx){
self.translateTx(tx);
});
}
return cb(null, txs, total);
});
};
@ -218,17 +137,15 @@ Insight.prototype.getAddressActivity = function(address, cb) {
var args = {
method: 'GET',
path: self.apiPrefix + '/addr/' + this.translateQueryAddresses(address),
path: self.apiPrefix + '/addr/' + address,
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 (err || res.statusCode !== 200)
return cb(_parseErr(err, res));
// note: result.addrStr is not translated, but not used.
var nbTxs = result.unconfirmedTxApperances + result.txApperances;
return cb(null, nbTxs > 0);
});
@ -245,7 +162,7 @@ Insight.prototype.estimateFee = function(nbBlocks, cb) {
path: path,
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));
return cb(null, body);
});
@ -259,27 +176,12 @@ Insight.prototype.getBlockchainHeight = function(cb) {
path: path,
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));
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() {
// sockets always use the first server on the pull

View File

@ -31,23 +31,11 @@ var requestList = function(args, cb) {
async.whilst(
function() {
nextUrl = urls.shift();
if (!nextUrl && success === 'false')
log.warn('no more servers to test for the request');
return nextUrl && !success;
},
function(a_cb) {
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) {
clearInterval(interval);
sucess = false;
if (err) {
log.warn('REQUEST FAIL: ' + nextUrl + ' ERROR: ' + err);
}

View File

@ -14,9 +14,6 @@ var Lock = require('./lock');
var Notification = require('./model/notification');
var WalletService = require('./server');
var Common = require('./common');
var Constants = Common.Constants;
var Utils = Common.Utils;
function BlockchainMonitor() {};
@ -28,42 +25,25 @@ BlockchainMonitor.prototype.start = function(opts, cb) {
async.parallel([
function(done) {
self.explorers = {
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) {
self.explorers = _.map(['livenet', 'testnet'], function(network) {
var explorer;
if (opts.blockchainExplorers && opts.blockchainExplorers[pair.coin] && opts.blockchainExplorers[pair.coin][pair.network]) {
explorer = opts.blockchainExplorers[pair.coin][pair.network];
if (opts.blockchainExplorers) {
explorer = opts.blockchainExplorers[network];
} else {
var config = {}
if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[pair.coin] && opts.blockchainExplorerOpts[pair.coin][pair.network]) {
config = opts.blockchainExplorerOpts[pair.coin][pair.network];
} else {
return;
if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[network]) {
config = opts.blockchainExplorerOpts[network];
}
var explorer = new BlockchainExplorer({
provider: config.provider,
coin: pair.coin,
network: pair.network,
network: network,
url: config.url,
userAgent: WalletService.getServiceVersion(),
});
}
$.checkState(explorer);
self._initExplorer(pair.coin, pair.network, explorer);
self.explorers[pair.coin][pair.network] = explorer;
self._initExplorer(explorer);
return explorer;
});
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 socket = explorer.initSocket();
@ -104,11 +84,11 @@ BlockchainMonitor.prototype._initExplorer = function(coin, network, explorer) {
socket.on('connect_error', function() {
log.error('Error connecting to ' + explorer.getConnectionInfo());
});
socket.on('tx', _.bind(self._handleIncomingTx, self, coin, network));
socket.on('block', _.bind(self._handleNewBlock, self, coin, network));
socket.on('tx', _.bind(self._handleIncommingTx, self));
socket.on('block', _.bind(self._handleNewBlock, self, explorer.network));
};
BlockchainMonitor.prototype._handleThirdPartyBroadcasts = function(data, processIt) {
BlockchainMonitor.prototype._handleTxId = function(data, processIt) {
var self = this;
if (!data || !data.txid) return;
@ -123,7 +103,7 @@ BlockchainMonitor.prototype._handleThirdPartyBroadcasts = function(data, process
if (!processIt) {
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 ]');
@ -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;
if (!data || !data.vout) return;
var outs = _.compact(_.map(data.vout, function(v) {
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 {
address: addr,
amount: amount,
amount: +v[addr]
};
}));
if (_.isEmpty(outs)) return;
async.each(outs, function(out, next) {
// toDo, remove coin here: no more same address for diff coins
self.storage.fetchAddressByCoin(coin, out.address, function(err, address) {
self.storage.fetchAddress(out.address, function(err, address) {
if (err) {
log.error('Could not fetch addresses from the db');
return next(err);
@ -185,30 +160,18 @@ BlockchainMonitor.prototype._handleIncomingPayments = function(coin, network, da
var walletId = address.walletId;
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({
type: 'NewIncomingTx',
data: {
txid: data.txid,
address: out.address,
amount: out.amount,
},
walletId: walletId,
});
self.storage.softResetTxHistoryCache(walletId, function() {
self._updateAddressesWithBalance(address, function() {
self._storeAndBroadcastNotification(notification, next);
});
var notification = Notification.create({
type: 'NewIncomingTx',
data: {
txid: data.txid,
address: out.address,
amount: out.amount,
},
walletId: walletId,
});
self.storage.softResetTxHistoryCache(walletId, function() {
self._updateActiveAddresses(address, function() {
self._storeAndBroadcastNotification(notification, next);
});
});
});
@ -217,47 +180,31 @@ BlockchainMonitor.prototype._handleIncomingPayments = function(coin, network, da
});
};
BlockchainMonitor.prototype._updateAddressesWithBalance = function(address, cb) {
BlockchainMonitor.prototype._updateActiveAddresses = function(address, cb) {
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) {
log.warn('Could not update wallet cache', err);
}
return cb(err);
});
return cb(err);
});
};
BlockchainMonitor.prototype._handleIncomingTx = function(coin, network, data) {
this._handleThirdPartyBroadcasts(data);
this._handleIncomingPayments(coin, network, data);
BlockchainMonitor.prototype._handleIncommingTx = function(data) {
this._handleTxId(data);
this._handleTxOuts(data);
};
BlockchainMonitor.prototype._notifyNewBlock = function(coin, network, hash) {
BlockchainMonitor.prototype._handleNewBlock = function(network, hash) {
var self = this;
log.info('New ' + network + ' block: ' + hash);
log.info('New ' + network + ' block: ', hash);
var notification = Notification.create({
type: 'NewBlock',
walletId: network, // use network name as wallet id for global notifications
data: {
hash: hash,
coin: coin,
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) {
var self = this;

View File

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

View File

@ -3,7 +3,7 @@
var Defaults = {};
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.MAX_TX_FEE = 0.1 * 1e8;
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
Defaults.SCAN_ADDRESS_GAP = Defaults.MAX_MAIN_ADDRESS_GAP + 20;
Defaults.FEE_LEVELS = {
btc: [{
name: 'urgent',
nbBlocks: 2,
multiplier: 1.5,
defaultValue: 150000,
}, {
name: 'priority',
nbBlocks: 2,
defaultValue: 100000
}, {
name: 'normal',
nbBlocks: 3,
defaultValue: 80000
}, {
name: 'economy',
nbBlocks: 6,
defaultValue: 50000
}, {
name: 'superEconomy',
nbBlocks: 24,
defaultValue: 20000
}],
bch: [{
name: 'normal',
nbBlocks: 2,
defaultValue: 2000,
}]
};
Defaults.FEE_LEVELS = [{
name: 'priority',
nbBlocks: 2,
defaultValue: 50000
}, {
name: 'normal',
nbBlocks: 3,
defaultValue: 40000
}, {
name: 'economy',
nbBlocks: 6,
defaultValue: 25000
}, {
name: 'superEconomy',
nbBlocks: 24,
defaultValue: 10000
}];
Defaults.DEFAULT_FEE_PER_KB = Defaults.FEE_LEVELS[1].defaultValue;
// How many levels to fallback to if the value returned by the network for a given nbBlocks is -1
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
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_FETCH_INTERVAL = 10; // In minutes
Defaults.FIAT_RATE_MAX_LOOK_BACK_TIME = 120; // In minutes
@ -89,19 +73,13 @@ Defaults.UTXO_SELECTION_MAX_FEE_VS_SINGLE_UTXO_FEE_FACTOR = 5;
// Minimum allowed amount for tx outputs (including change) in SAT
Defaults.MIN_OUTPUT_AMOUNT = 5000;
// Number of confirmations from which tx in history will be cached
// Number of confirmations from which tx in history will be cached
// (ie we consider them inmutables)
Defaults.CONFIRMATIONS_TO_START_CACHING = 6 * 6; // ~ 6hrs
// Number of addresses from which tx history is enabled in a wallet
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)
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.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;

View File

@ -7,13 +7,6 @@ var encoding = bitcore.encoding;
var secp256k1 = require('secp256k1');
var Utils = {};
var Bitcore = require('bitcore-lib');
var Bitcore_ = {
btc: Bitcore,
bch: require('bitcore-lib-cash')
};
Utils.getMissingFields = function(obj, args) {
args = [].concat(args);
@ -71,7 +64,7 @@ Utils._tryImportPublicKey = function(publicKey) {
publicKeyBuffer = new Buffer(publicKey, 'hex');
}
return publicKeyBuffer;
} catch (e) {
} catch(e) {
return false;
}
};
@ -83,7 +76,7 @@ Utils._tryImportSignature = function(signature) {
signatureBuffer = new Buffer(signature, 'hex');
}
return secp256k1.signatureImport(signatureBuffer);
} catch (e) {
} catch(e) {
return false;
}
};
@ -91,7 +84,7 @@ Utils._tryImportSignature = function(signature) {
Utils._tryVerifyMessage = function(hash, sig, publicKeyBuffer) {
try {
return secp256k1.verify(hash, sig, publicKeyBuffer);
} catch (e) {
} catch(e) {
return false;
}
};
@ -112,12 +105,7 @@ Utils.formatAmount = function(satoshis, unit, opts) {
toSatoshis: 1,
maxDecimals: 0,
minDecimals: 0,
},
bch: {
toSatoshis: 100000000,
maxDecimals: 6,
minDecimals: 2,
},
}
};
$.shouldBeNumber(satoshis);
@ -188,35 +176,5 @@ Utils.parseVersion = function(version) {
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;

View File

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

View File

@ -33,10 +33,9 @@ var errors = {
UPGRADE_NEEDED: 'Client app needs to be upgraded',
WALLET_ALREADY_EXISTS: 'Wallet already exists',
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_FOUND: 'Wallet not found',
WALLET_NEED_SCAN: 'Wallet needs addresses scan',
};
var errorObjects = _.zipObject(_.map(errors, function(msg, code) {

View File

@ -7,7 +7,6 @@ var log = require('npmlog');
var express = require('express');
var bodyParser = require('body-parser');
var compression = require('compression');
var RateLimit = require('express-rate-limit');
var Common = require('./common');
var Defaults = Common.Defaults;
@ -17,7 +16,7 @@ var Stats = require('./stats');
log.disableColor();
log.debug = log.verbose;
log.level = 'verbose';
log.level = 'info';
var ExpressApp = function() {
this.app = express();
@ -39,7 +38,7 @@ ExpressApp.prototype.start = function(opts, cb) {
this.app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
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());
next();
});
@ -54,16 +53,6 @@ ExpressApp.prototype.start = function(opts, cb) {
this.app.use(allowCORS);
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 */ ;
this.app.use(bodyParser.json({
@ -75,10 +64,14 @@ ExpressApp.prototype.start = function(opts, cb) {
} else {
var morgan = require('morgan');
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 = {
skip: function(req, res) {
if (res.statusCode != 200) return false;
@ -88,15 +81,15 @@ ExpressApp.prototype.start = function(opts, cb) {
this.app.use(morgan(logFormat, logOpts));
}
var router = express.Router();
var router = express.Router();
function returnError(err, res, req) {
if (err instanceof WalletService.ClientError) {
var status = (err.code == 'NOT_AUTHORIZED') ? 401 : 400;
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({
code: err.code,
@ -161,7 +154,6 @@ ExpressApp.prototype.start = function(opts, cb) {
message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
signature: credentials.signature,
clientVersion: req.header('x-client-version'),
walletId: req.header('x-wallet-id'),
};
if (opts.allowSession) {
auth.session = credentials.session;
@ -169,34 +161,16 @@ ExpressApp.prototype.start = function(opts, cb) {
WalletService.getInstanceWithAuth(auth, function(err, server) {
if (err) return returnError(err, res, req);
if (opts.onlySupportStaff && !server.copayerIsSupportStaff) {
return returnError(new WalletService.ClientError({
code: 'NOT_AUTHORIZED'
}), res, req);
}
// For logging
req.walletId = server.walletId;
req.copayerId = server.copayerId;
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
router.post('/v1/wallets/', createWalletLimiter, function(req, res) {
router.post('/v1/wallets/', function(req, res) {
logDeprecated(req);
var server;
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;
try {
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) {
getServerWithAuth(req, res, function(server) {
server.getPreferences({}, function(err, preferences) {
@ -417,7 +368,6 @@ ExpressApp.prototype.start = function(opts, cb) {
router.get('/v1/balance/', function(req, res) {
getServerWithAuth(req, res, function(server) {
var opts = {};
if (req.query.coin) opts.coin = req.query.coin;
if (req.query.twoStep == '1') opts.twoStep = true;
server.getBalance(opts, function(err, balance) {
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
router.get('/v1/feelevels/', estimateFeeLimiter, function(req, res) {
router.get('/v1/feelevels/', function(req, res) {
logDeprecated(req);
var opts = {};
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 = {};
if (req.query.coin) opts.coin = req.query.coin;
if (req.query.network) opts.network = req.query.network;
var server;
try {
server = getServer(req, res);
@ -611,7 +546,6 @@ ExpressApp.prototype.start = function(opts, cb) {
router.get('/v1/stats/', function(req, res) {
var opts = {};
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.to) opts.to = req.query.to;
@ -701,20 +635,16 @@ ExpressApp.prototype.start = function(opts, cb) {
});
router.get('/v1/fiatrates/:code/', function(req, res) {
var server;
var opts = {
code: req.params['code'],
provider: req.query.provider,
ts: +req.query.ts,
};
try {
server = getServer(req, res);
} catch (ex) {
return returnError(ex, res, req);
}
server.getFiatRate(opts, function(err, rates) {
if (err) return returnError(err, res, req);
res.json(rates);
getServerWithAuth(req, res, function(server) {
var opts = {
code: req.params['code'],
source: req.query.source,
ts: +req.query.ts,
};
server.getFiatRate(opts, function(err, rates) {
if (err) return returnError(err, res, req);
res.json(rates);
});
});
});
@ -727,47 +657,9 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
// DEPRECATED
router.delete('/v1/pushnotifications/subscriptions/', function(req, res) {
logDeprecated(req);
getServerWithAuth(req, res, function(server) {
server.pushNotificationsUnsubscribe({
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) {
server.pushNotificationsUnsubscribe(function(err, response) {
if (err) return returnError(err, res, req);
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);
waitTime = waitTime || 5 * 1000;
this.lock.locked(token, waitTime , 5 * 60 * 1000, function(err, release) {
if (err) return cb(Errors.WALLET_BUSY);
this.lock.locked(token, 5 * 1000, 5 * 60 * 1000, function(err, release) {
if (err) return cb(Errors.WALLET_LOCKED);
var _cb = function() {
cb.apply(null, arguments);
release();

View File

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

View File

@ -1,8 +1,8 @@
var _ = require('lodash');
var $ = require('preconditions').singleton();
var Bitcore = require('bitcore-lib');
var Constants = require('../common/constants');
var Utils = require('../common/utils');
function AddressManager() {};
@ -13,7 +13,7 @@ AddressManager.create = function(opts) {
x.version = 2;
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.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) {
return 'm/' +
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,6 @@ var Storage = require('./storage');
var fs = require('fs');
var path = require('path');
var Utils = require('./common/utils');
var Defaults = require('./common/defaults');
var Model = require('./model');
var sjcl = require('sjcl');
var log = require('npmlog');
@ -34,10 +33,6 @@ var PUSHNOTIFICATIONS_TYPES = {
'TxProposalFinallyRejected': {
filename: 'txp_finally_rejected',
},
'TxConfirmation': {
filename: 'tx_confirmation',
notifyCreatorOnly: true,
},
};
function PushNotificationsService() {};
@ -65,10 +60,6 @@ PushNotificationsService.prototype.start = function(opts, cb) {
self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc';
self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || '';
self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl;
self.authorizationKey = opts.pushNotificationsOpts.authorizationKey;
if (!self.authorizationKey) return cb(new Error('Missing authorizationKey attribute in configuration.'))
async.parallel([
function(done) {
@ -116,7 +107,7 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio
log.debug('Should send notification: ', should);
if (!should) return cb();
self._getRecipientsList(notification, notifType, function(err, recipientsList) {
self._getRecipientsList(notification, function(err, recipientsList) {
if (err) return cb(err);
async.waterfall([
@ -126,40 +117,34 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio
},
function(contents, next) {
async.map(recipientsList, function(recipient, next) {
var opts = {};
var content = contents[recipient.language];
self.storage.fetchPushNotificationSubs(recipient.copayerId, function(err, subs) {
if (err) return next(err);
var notifications = _.map(subs, function(sub) {
return {
to: sub.token,
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))
},
};
});
return next(err, notifications);
});
}, function(err, allNotifications) {
if (err) return next(err);
return next(null, _.flatten(allNotifications));
});
opts.users = [notification.walletId + '$' + recipient.copayerId];
opts.android = {
"data": {
"title": content.plain.subject,
"message": content.plain.body,
"walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)),
"notId": Math.floor(Math.random() * 100000) + 1
}
};
opts.ios = {
"alert": {
"title": content.plain.subject,
"body": content.plain.body
},
"sound": "default",
"payload": {
"walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId))
}
};
return next(err, opts);
}, next);
},
function(notifications, next) {
async.each(notifications,
function(notification, next) {
self._makeRequest(notification, function(err, response) {
function(optsList, next) {
async.each(optsList,
function(opts, next) {
self._makeRequest(opts, function(err, response) {
if (err) log.error(err);
if (response) {
log.debug('Request status: ', response.statusCode);
@ -189,21 +174,16 @@ PushNotificationsService.prototype._checkShouldSendNotif = function(notification
if (notification.type != 'NewTxProposal') return cb(null, true);
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;
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
if (err) return cb(err);
var unit;
if (wallet.coin != Defaults.COIN) {
unit = wallet.coin;
}
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
if (err) log.error(err);
@ -220,23 +200,22 @@ PushNotificationsService.prototype._getRecipientsList = function(notification, n
return {
copayerId: p.copayerId,
language: p.language,
unit: unit || p.unit || self.defaultUnit,
unit: p.unit,
};
}));
recipientPreferences = _.indexBy(recipientPreferences, 'copayerId');
var recipientsList = _.compact(_.map(wallet.copayers, function(copayer) {
if ((copayer.id == notification.creatorId && notificationType.notifyCreatorOnly) ||
(copayer.id != notification.creatorId && !notificationType.notifyCreatorOnly)) {
var p = recipientPreferences[copayer.id] || {};
return {
copayerId: copayer.id,
language: p.language || self.defaultLanguage,
unit: unit || p.unit || self.defaultUnit,
}
var recipientsList = _.reject(_.map(wallet.copayers, function(copayer) {
var p = recipientPreferences[copayer.id] || {};
return {
copayerId: copayer.id,
language: p.language || self.defaultLanguage,
unit: p.unit || self.defaultUnit,
}
}));
}), {
copayerId: notification.creatorId
});
return cb(null, recipientsList);
});
@ -281,8 +260,7 @@ PushNotificationsService.prototype._getDataForTemplate = function(notification,
var self = this;
var UNIT_LABELS = {
btc: 'BTC',
bit: 'bits',
bch: 'BCH',
bit: 'bits'
};
var data = _.cloneDeep(notification.data);
@ -297,7 +275,7 @@ PushNotificationsService.prototype._getDataForTemplate = function(notification,
}
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
if (err || !wallet) return cb(err);
if (err) return cb(err);
data.walletId = wallet.id;
data.walletName = wallet.name;
@ -382,12 +360,10 @@ PushNotificationsService.prototype._makeRequest = function(opts, cb) {
url: self.pushServerUrl + '/send',
method: 'POST',
json: true,
headers: {
'Content-Type': 'application/json',
'Authorization': 'key=' + self.authorizationKey,
},
body: opts,
}, cb);
body: opts
}, function(err, response) {
return cb(err, response);
});
};
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 INITIAL_DATE = '2018-01-01';
var INITIAL_DATE = '2015-01-01';
function Stats(opts) {
opts = opts || {};
this.network = opts.network || 'livenet';
this.coin = opts.coin || 'btc';
this.from = moment(opts.from || INITIAL_DATE);
this.to = moment(opts.to);
this.fromTs = this.from.startOf('day').valueOf();
@ -71,13 +70,13 @@ Stats.prototype._getNewWallets = function(cb) {
function getLastDate(cb) {
self.db.collection('stats_wallets')
.find({'_id.coin': self.coin})
.find({})
.sort({
'_id.day': -1
})
.limit(1)
.toArray(function(err, lastRecord) {
if (_.isEmpty(lastRecord)) return cb(null, moment(INITIAL_DATE));
if (_.isEmpty(lastRecord)) return cb(null, moment(INITIAL_DATE));
return cb(null, moment(lastRecord[0]._id.day));
});
};
@ -92,7 +91,6 @@ Stats.prototype._getNewWallets = function(cb) {
var key = {
day: +day,
network: this.network,
coin: this.coin
};
var value = {
count: 1
@ -129,7 +127,6 @@ Stats.prototype._getNewWallets = function(cb) {
self.db.collection('stats_wallets')
.find({
'_id.network': self.network,
'_id.coin': self.coin,
'_id.day': {
$gte: self.fromTs,
$lte: self.toTs,
@ -145,7 +142,6 @@ Stats.prototype._getNewWallets = function(cb) {
var day = moment(record._id.day).format('YYYYMMDD');
return {
day: day,
coin: record._id.coin,
count: record.value.count,
};
});
@ -185,7 +181,7 @@ Stats.prototype._getTxProposals = function(cb) {
function getLastDate(cb) {
self.db.collection('stats_txps')
.find({'_id.coin': self.coin })
.find({})
.sort({
'_id.day': -1
})
@ -206,7 +202,6 @@ Stats.prototype._getTxProposals = function(cb) {
var key = {
day: +day,
network: this.network,
coin: this.coin
};
var value = {
count: 1,
@ -248,7 +243,6 @@ Stats.prototype._getTxProposals = function(cb) {
self.db.collection('stats_txps')
.find({
'_id.network': self.network,
'_id.coin': self.coin,
'_id.day': {
$gte: self.fromTs,
$lte: self.toTs,
@ -268,7 +262,6 @@ Stats.prototype._getTxProposals = function(cb) {
var day = moment(record._id.day).format('YYYYMMDD');
stats.nbByDay.push({
day: day,
coin: record._id.coin,
count: record.value.count,
});
stats.amountByDay.push({

View File

@ -7,7 +7,7 @@ var log = require('npmlog');
log.debug = log.verbose;
log.disableColor();
var util = require('util');
var Bitcore = require('bitcore-lib');
var mongodb = require('mongodb');
var Model = require('./model');
@ -24,8 +24,6 @@ var collections = {
FIAT_RATES: 'fiat_rates',
TX_NOTES: 'tx_notes',
SESSIONS: 'sessions',
PUSH_NOTIFICATION_SUBS: 'push_notification_subs',
TX_CONFIRMATION_SUBS: 'tx_confirmation_subs',
};
var Storage = function(opts) {
@ -40,9 +38,6 @@ Storage.prototype._createIndexes = function() {
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
copayerId: 1
});
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
walletId: 1
});
this.db.collection(collections.TXS).createIndex({
walletId: 1,
id: 1,
@ -56,9 +51,6 @@ Storage.prototype._createIndexes = function() {
walletId: 1,
createdOn: -1,
});
this.db.collection(collections.TXS).createIndex({
txid: 1
});
this.db.collection(collections.NOTIFICATIONS).createIndex({
walletId: 1,
id: 1,
@ -70,13 +62,6 @@ Storage.prototype._createIndexes = function() {
this.db.collection(collections.ADDRESSES).createIndex({
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({
notificationId: 1,
});
@ -89,24 +74,6 @@ Storage.prototype._createIndexes = function() {
walletId: 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) {
@ -140,8 +107,6 @@ Storage.prototype.disconnect = function(cb) {
};
Storage.prototype.fetchWallet = function(id, cb) {
if (!this.db) return cb('not ready');
this.db.collection(collections.WALLETS).findOne({
id: id
}, function(err, result) {
@ -230,7 +195,6 @@ Storage.prototype._completeTxData = function(walletId, txs, cb) {
// TODO: remove walletId from signature
Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
var self = this;
if (!this.db) return cb();
this.db.collection(collections.TXS).findOne({
id: txProposalId,
@ -244,7 +208,6 @@ Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
Storage.prototype.fetchTxByHash = function(hash, cb) {
var self = this;
if (!this.db) return cb();
this.db.collection(collections.TXS).findOne({
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) {
this.db.collection(collections.ADDRESSES).find({
walletId: walletId,
@ -508,7 +449,6 @@ Storage.prototype.storeAddress = function(address, cb) {
var self = this;
self.db.collection(collections.ADDRESSES).update({
walletId: address.walletId,
address: address.address
}, address, {
w: 1,
@ -520,21 +460,37 @@ Storage.prototype.storeAddressAndWallet = function(wallet, addresses, cb) {
var self = this;
var addresses = [].concat(addresses);
if (_.isEmpty(addresses)) return cb();
if (addresses.length == 0) return cb();
self.db.collection(collections.ADDRESSES).insert(addresses, {
w: 1
}, function(err) {
if (err) return cb(err);
self.storeWallet(wallet, cb);
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
}, function(err) {
if (err) return cb(err);
self.storeWallet(wallet, cb);
});
});
};
Storage.prototype.fetchAddressByWalletId = function(walletId, address, cb) {
Storage.prototype.fetchAddress = function(address, cb) {
var self = this;
this.db.collection(collections.ADDRESSES).findOne({
walletId: walletId,
address: address,
}, function(err, result) {
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) {
this.db.collection(collections.PREFERENCES).find({
walletId: walletId,
@ -629,93 +563,53 @@ 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);
async.series([
function(next) {
self.db.collection(collections.CACHE).remove({
walletId: walletId,
type: 'activeAddresses',
}, {
w: 1
}, next);
},
function(next) {
self.db.collection(collections.CACHE).insert({
walletId: walletId,
type: 'activeAddresses',
key: null
}, {
w: 1
}, next);
},
], cb);
};
Storage.prototype.getTwoStepCache = function(walletId, cb) {
Storage.prototype.storeActiveAddresses = function(walletId, addresses, cb) {
var self = this;
self.db.collection(collections.CACHE).findOne({
walletId: walletId,
type: 'twoStep',
key: null
}, function(err, result) {
if (err) return cb(err);
if (!result) return cb();
return cb(null, result);
});
};
Storage.prototype.storeAddressesWithBalance = function(walletId, addresses, cb) {
var self = this;
if (_.isEmpty(addresses))
addresses = [];
self.db.collection(collections.CACHE).update({
walletId: walletId,
type: 'addressesWithBalance',
key: null,
}, {
"$set":
{
addresses: addresses,
}
}, {
w: 1,
upsert: true,
}, 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({
async.each(addresses, function(address, next) {
var record = {
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);
});
});
type: 'activeAddresses',
key: address,
};
self.db.collection(collections.CACHE).update({
walletId: record.walletId,
type: record.type,
key: record.key,
}, record, {
w: 1,
upsert: true,
}, next);
}, cb);
};
// -------- --------------------------- Total
// > Time >
// > Time >
// ^to <= ^from
// ^fwdIndex => ^end
Storage.prototype.getTxHistoryCache = function(walletId, from, to, cb) {
@ -764,7 +658,7 @@ Storage.prototype.getTxHistoryCache = function(walletId, from, to, cb) {
return cb();
}
var txs = _.map(result, 'tx');
var txs = _.pluck(result, 'tx');
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) {
var self = this;
@ -979,79 +890,6 @@ Storage.prototype.storeSession = function(session, 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) {
fn = fn || console.log;
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;
module.exports = Storage;

View File

@ -1,2 +1,2 @@
{{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
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
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
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
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
Your wallet is complete.
Your wallet {{walletName}} is complete.

View File

@ -1,2 +1,2 @@
{{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
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
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
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
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
Su billetera está completa.
{{subjectPrefix}}Monedero completo
Su monedero {{walletName}} está completo.

View File

@ -1,2 +1,2 @@
{{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
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é
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
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
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é
Votre portefeuille est terminé.
Votre portefeuille {{walletName}} est terminé.

View File

@ -2,8 +2,7 @@
"name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc",
"version": "2.4.0",
"licence": "MIT",
"version": "1.13.0",
"keywords": [
"bitcoin",
"copay",
@ -13,22 +12,20 @@
"BWS"
],
"repository": {
"url": "git@github.com:BTCPrivate/bitcore-wallet-service.git",
"url": "git@github.com:bitpay/bitcore-wallet-service.git",
"type": "git"
},
"bugs": {
"url": "https://github.com/BTCPrivate/bitcore-wallet-service/issues"
"url": "https://github.com/bitpay/bitcore-wallet-service/issues"
},
"dependencies": {
"async": "^0.9.2",
"bitcore-lib": "ch4ot1c/bitcore-lib",
"bitcore-lib-cash": "^0.17.0",
"bitcore-lib": "^0.13.7",
"body-parser": "^1.11.0",
"compression": "^1.6.2",
"coveralls": "^2.11.2",
"email-validator": "^1.0.1",
"express": "^4.10.0",
"express-rate-limit": "^2.6.0",
"inherits": "^2.0.1",
"json-stable-stringify": "^1.0.0",
"locker": "^0.1.0",
@ -55,13 +52,13 @@
"devDependencies": {
"chai": "^1.9.1",
"istanbul": "*",
"jsdoc": "^3.5.5",
"jsdoc": "^3.3.0-beta1",
"memdown": "^1.0.0",
"mocha": "^1.18.2",
"proxyquire": "^1.7.2",
"sinon": "1.10.3",
"supertest": "*",
"tingodb": "^0.5.1"
"tingodb": "^0.3.4"
},
"scripts": {
"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"
},
"bitcoreNode": "./bitcorenode",
"contributors": [
{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
},
{
"name": "Ivan Socolsky",
"email": "ivan@bitpay.com"
},
{
"name": "Matias Alejo Garcia",
"email": "ematiu@gmail.com"
}
]
"contributors": [{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
}, {
"name": "Ivan Socolsky",
"email": "ivan@bitpay.com"
}, {
"name": "Matias Alejo Garcia",
"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];
one.from.should.equal('bws@dummy.net');
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);
one.html.indexOf('<html>').should.equal(0);
one.html.should.contain(wallet.name);
server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err);
unsent.should.be.empty;
@ -169,7 +172,7 @@ describe('Email notifications', function() {
txp = t;
async.eachSeries(_.range(2), function(i, next) {
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);
server.signTx({
txProposalId: txp.id,
@ -199,6 +202,7 @@ describe('Email notifications', function() {
var one = emails[0];
one.from.should.equal('bws@dummy.net');
one.subject.should.contain('Payment sent');
one.text.should.contain(wallet.name);
one.text.should.contain('800,000');
should.exist(one.html);
one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid);
@ -235,7 +239,7 @@ describe('Email notifications', function() {
txpId = txp.id;
async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44btc, function(server) {
helpers.getAuthServer(copayer.id44, function(server) {
server.rejectTx({
txProposalId: txp.id,
}, next);
@ -254,6 +258,9 @@ describe('Email notifications', function() {
var one = emails[0];
one.from.should.equal('bws@dummy.net');
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) {
should.not.exist(err);
unsent.should.be.empty;
@ -284,6 +291,7 @@ describe('Email notifications', function() {
var one = emails[0];
one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment received');
one.text.should.contain(wallet.name);
one.text.should.contain('123,000');
server.storage.fetchUnsentEmails(function(err, unsent) {
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) {
// Set same email address for copayer1 and copayer2
server.savePreferences({
@ -351,6 +327,7 @@ describe('Email notifications', function() {
var one = emails[0];
one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment received');
one.text.should.contain(wallet.name);
one.text.should.contain('123,000');
server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err);
@ -390,12 +367,14 @@ describe('Email notifications', function() {
});
spanish.from.should.equal('bws@dummy.net');
spanish.subject.should.contain('Nuevo pago recibido');
spanish.text.should.contain(wallet.name);
spanish.text.should.contain('0.123 BTC');
var english = _.find(emails, {
to: 'copayer2@domain.com'
});
english.from.should.equal('bws@dummy.net');
english.subject.should.contain('New payment received');
english.text.should.contain(wallet.name);
english.text.should.contain('123,000 bits');
done();
}, 100);

View File

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

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.level = 'info';
var sjcl = require('sjcl');
var WalletService = require('../../lib/server');
var PushNotificationsService = require('../../lib/pushnotificationsservice');
@ -38,24 +36,11 @@ describe('Push notifications', function() {
var i = 0;
async.eachSeries(w.copayers, function(copayer, next) {
helpers.getAuthServer(copayer.id, function(server) {
async.parallel([
function(done) {
server.savePreferences({
email: 'copayer' + (++i) + '@domain.com',
language: 'en',
unit: 'bit',
}, done);
},
function(done) {
server.pushNotificationsSubscribe({
token: '1234',
packageName: 'com.wallet',
platform: 'Android',
}, done);
},
], next);
server.savePreferences({
email: 'copayer' + (++i) + '@domain.com',
language: 'en',
unit: 'bit',
}, next);
});
}, function(err) {
should.not.exist(err);
@ -74,8 +59,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en',
defaultUnit: 'btc',
subjectPrefix: '',
pushServerUrl: 'http://localhost:8000',
authorizationKey: 'secret',
pushServerUrl: 'http://localhost:8000/send',
},
}, function(err) {
should.not.exist(err);
@ -108,9 +93,8 @@ describe('Push notifications', function() {
return c.args[0];
});
calls.length.should.equal(1);
args[0].body.notification.title.should.contain('New payment received');
args[0].body.notification.body.should.contain('123,000');
args[0].body.notification.body.should.contain('bits');
args[0].body.android.data.title.should.contain('New payment received');
args[0].body.android.data.message.should.contain('123,000');
done();
}, 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() {
@ -193,24 +154,11 @@ describe('Push notifications', function() {
var i = 0;
async.eachSeries(w.copayers, function(copayer, next) {
helpers.getAuthServer(copayer.id, function(server) {
async.parallel([
function(done) {
server.savePreferences({
email: 'copayer' + (++i) + '@domain.com',
language: 'en',
unit: 'bit',
}, done);
},
function(done) {
server.pushNotificationsSubscribe({
token: '1234',
packageName: 'com.wallet',
platform: 'Android',
}, done);
},
], next);
server.savePreferences({
email: 'copayer' + (++i) + '@domain.com',
language: 'en',
unit: 'bit',
}, next);
});
}, function(err) {
should.not.exist(err);
@ -229,8 +177,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en',
defaultUnit: 'btc',
subjectPrefix: '',
pushServerUrl: 'http://localhost:8000',
authorizationKey: 'secret',
pushServerUrl: 'http://localhost:8000/send',
},
}, function(err) {
should.not.exist(err);
@ -266,14 +214,14 @@ describe('Push notifications', function() {
calls.length.should.equal(3);
args[0].body.notification.title.should.contain('Nuevo pago recibido');
args[0].body.notification.body.should.contain('0.123');
args[0].body.android.data.title.should.contain('Nuevo pago recibido');
args[0].body.android.data.message.should.contain('0.123');
args[1].body.notification.title.should.contain('New payment received');
args[1].body.notification.body.should.contain('123,000');
args[1].body.android.data.title.should.contain('New payment received');
args[1].body.android.data.message.should.contain('123,000');
args[2].body.notification.title.should.contain('New payment received');
args[2].body.notification.body.should.contain('123,000');
args[2].body.android.data.title.should.contain('New payment received');
args[2].body.android.data.message.should.contain('123,000');
done();
}, 100);
});
@ -369,7 +317,7 @@ describe('Push notifications', function() {
txpId = txp.id;
async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44btc, function(server) {
helpers.getAuthServer(copayer.id44, function(server) {
server.rejectTx({
txProposalId: txp.id,
}, next);
@ -385,7 +333,9 @@ describe('Push notifications', function() {
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();
}, 100);
});
@ -414,7 +364,7 @@ describe('Push notifications', function() {
txp = t;
async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44btc, function(s) {
helpers.getAuthServer(copayer.id44, function(s) {
server = s;
var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H);
server.signTx({
@ -442,11 +392,11 @@ describe('Push notifications', function() {
return c.args[0];
});
args[0].body.notification.title.should.contain('Payment sent');
args[1].body.notification.title.should.contain('Payment sent');
args[0].body.android.data.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);
sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(server.copayerId)).should.not.equal(args[1].body.data.copayerId);
server.copayerId.should.not.equal((args[0].body.users[0]).split('$')[1]);
server.copayerId.should.not.equal((args[1].body.users[0]).split('$')[1]);
done();
}, 100);
});
@ -482,8 +432,7 @@ describe('Push notifications', function() {
defaultLanguage: 'en',
defaultUnit: 'btc',
subjectPrefix: '',
pushServerUrl: 'http://localhost:8000',
authorizationKey: 'secret',
pushServerUrl: 'http://localhost:8000/send',
},
}, function(err) {
should.not.exist(err);
@ -503,54 +452,44 @@ describe('Push notifications', function() {
customData: 'custom data ' + (i + 1),
});
server.joinWallet(copayerOpts, function(err, res) {
if (err) return next(err);
helpers.getAuthServer(res.copayerId, function(server) {
server.pushNotificationsSubscribe({
token: 'token:' + copayerOpts.name,
packageName: 'com.wallet',
platform: 'Android',
}, next);
});
});
server.joinWallet(copayerOpts, next);
}, function(err) {
should.not.exist(err);
setTimeout(function() {
var calls = requestStub.getCalls();
var args = _.filter(_.map(calls, function(call) {
return call.args[0];
}), function(arg) {
return arg.body.notification.title == 'New copayer';
var args = _.map(calls, function(c) {
return c.args[0];
});
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
copayer2 should notify to copayer1
copayer2 should NOT be notifyed
*/
var hashedCopayerIds = _.map(wallet.copayers, function(copayer) {
return sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(copayer.id));
});
hashedCopayerIds[0].should.equal((args[0].body.data.copayerId));
hashedCopayerIds[1].should.not.equal((args[0].body.data.copayerId));
w.copayers[0].id.should.contain((argu[0].body.users[0]).split('$')[1]);
w.copayers[1].id.should.not.contain((argu[0].body.users[0]).split('$')[1]);
/*
Second call - copayer3 joined
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
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
hashedCopayerIds[2].should.not.equal((args[1].body.data.copayerId));
hashedCopayerIds[2].should.not.equal((args[2].body.data.copayerId));
w.copayers[2].id.should.not.contain((argu[1].body.users[0]).split('$')[1]);
w.copayers[2].id.should.not.contain((argu[2].body.users[0]).split('$')[1]);
done();
});
}, 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() {
var x = Address.create({
address: '3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg',
coin: 'btc',
walletId: '123',
isChange: false,
path: 'm/0/1',
@ -24,7 +23,6 @@ describe('Address', function() {
it('should create testnet address', function() {
var x = Address.create({
address: 'mp5xaa4uBj16DJt1fuA3D9fejHuCzeb7hj',
coin: 'btc',
walletId: '123',
isChange: false,
path: 'm/0/1',
@ -41,7 +39,7 @@ describe('Address', function() {
}, {
xPubKey: 'xpub68tpbrfk747AvDUCdtEUgK2yDPmtGKf7YXzEcUUqnF3jmAMeZgcpoZqgXwwoi8CpwDkyzVX6wxUktTw2wh9EhhVjh5S71MLL3FkZDGF5GeY'
// PubKey(xPubKey/0/0) -> 03162179906dbe6a67979d4f8f46ee1db6ff81715f465e6615a4f5969478ad2171
}], 'm/0/0', 1, 'btc', 'livenet', false);
}], 'm/0/0', 1, 'livenet', false);
should.exist(address);
address.walletId.should.equal('wallet-id');
address.address.should.equal('3QN2CiSxcUsFuRxZJwXMNDQ2esnr5RXTvw');
@ -54,7 +52,7 @@ describe('Address', function() {
var address = Address.derive('wallet-id', 'P2SH', [{
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
// PubKey(xPubKey/0/0) -> 03fe466ea829aa4c9a1c289f9ba61ebc26a61816500860c8d23f94aad9af152ecd
}], 'm/0/0', 1, 'btc', 'livenet', false);
}], 'm/0/0', 1, 'livenet', false);
should.exist(address);
address.walletId.should.equal('wallet-id');
address.address.should.equal('3BY4K8dfsHryhWh2MJ6XHxxsRfcvPAyseH');
@ -67,7 +65,7 @@ describe('Address', function() {
var address = Address.derive('wallet-id', 'P2PKH', [{
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
// PubKey(xPubKey/1/2) -> 0232c09a6edd8e2189628132d530c038e0b15b414cf3984e532358cbcfb83a7bd7
}], 'm/1/2', 1, 'btc', 'livenet', true);
}], 'm/1/2', 1, 'livenet', true);
should.exist(address);
address.walletId.should.equal('wallet-id');
address.address.should.equal('1G4wgi9YzmSSwQaQVLXQ5HUVquQDgJf8oT');

View File

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

View File

@ -24,11 +24,6 @@ describe('TxProposal', function() {
should.exist(txp);
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() {
@ -113,10 +108,8 @@ var theXPub = 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2
var theSignatures = ['304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6'];
var theRawTx = '0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e08000000910047304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0380969800000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac002d3101000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70f62b040000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000';
var aTxpOpts = function() {
var aTxpOpts = function(type) {
var opts = {
coin: 'btc',
network: 'livenet',
message: 'some message'
};
opts.outputs = [{
@ -132,7 +125,7 @@ var aTxpOpts = function() {
return opts;
};
var aTXP = function() {
var aTXP = function(type) {
var txp = {
"version": 3,
"createdOn": 1423146231,

View File

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

View File

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