Wallet/src/js/services/walletService.js

1141 lines
32 KiB
JavaScript
Raw Normal View History

'use strict';
2016-08-24 19:28:20 -03:00
angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txStatus, txFormatService, $ionicModal, $state, bwcService, bitcore) {
2016-08-15 10:25:43 -03:00
// `wallet` is a decorated version of client.
2016-08-12 11:10:26 -03:00
var root = {};
2016-08-17 18:02:47 -03:00
root.WALLET_STATUS_MAX_TRIES = 7;
root.WALLET_STATUS_DELAY_BETWEEN_TRIES = 1.4 * 1000;
2016-08-17 17:26:13 -03:00
root.SOFT_CONFIRMATION_LIMIT = 12;
2016-08-18 10:37:08 -03:00
root.SAFE_CONFIRMATIONS = 6;
2016-08-15 10:25:43 -03:00
2016-08-18 10:08:23 -03:00
// UI Related
root.openStatusModal = function(type, txp, cb) {
var scope = $rootScope.$new(true);
scope.type = type;
scope.tx = txFormatService.processTx(txp);
scope.color = txp.color;
scope.cb = cb;
$ionicModal.fromTemplateUrl('views/modals/tx-status.html', {
scope: scope,
animation: 'slide-in-up'
}).then(function(modal) {
scope.txStatusModal = modal;
scope.txStatusModal.show();
});
};
2016-08-18 10:37:08 -03:00
// // RECEIVE
// // Check address
// root.isUsed(wallet.walletId, balance.byAddress, function(err, used) {
// if (used) {
// $log.debug('Address used. Creating new');
// $rootScope.$emit('Local/AddressIsUsed');
// }
// });
//
2016-08-18 10:08:23 -03:00
2016-08-15 10:25:43 -03:00
var _signWithLedger = function(wallet, txp, cb) {
$log.info('Requesting Ledger Chrome app to sign the transaction');
2016-08-15 10:25:43 -03:00
ledger.signTx(txp, wallet.credentials.account, function(result) {
$log.debug('Ledger response', result);
if (!result.success)
return cb(result.message || result.error);
txp.signatures = lodash.map(result.signatures, function(s) {
return s.substring(0, s.length - 2);
});
2016-08-15 10:25:43 -03:00
return wallet.signTxProposal(txp, cb);
});
};
2016-08-15 10:25:43 -03:00
var _signWithTrezor = function(wallet, txp, cb) {
$log.info('Requesting Trezor to sign the transaction');
2016-08-15 10:25:43 -03:00
var xPubKeys = lodash.pluck(wallet.credentials.publicKeyRing, 'xPubKey');
trezor.signTx(xPubKeys, txp, wallet.credentials.account, function(err, result) {
if (err) return cb(err);
$log.debug('Trezor response', result);
txp.signatures = result.signatures;
2016-08-15 10:25:43 -03:00
return wallet.signTxProposal(txp, cb);
});
};
2016-08-15 16:07:30 -03:00
root.requiresBackup = function(wallet) {
2016-08-15 10:25:43 -03:00
if (wallet.isPrivKeyExternal()) return false;
if (!wallet.credentials.mnemonic) return false;
if (wallet.credentials.network == 'testnet') return false;
2016-06-06 18:26:45 -03:00
return true;
};
2016-08-15 16:07:30 -03:00
root.needsBackup = function(wallet, cb) {
2016-08-17 15:53:17 -03:00
if (!root.requiresBackup(wallet))
2016-08-15 16:07:30 -03:00
return cb(false);
storageService.getBackupFlag(wallet.credentials.walletId, function(err, val) {
if (err) $log.error(err);
if (val) return cb(false);
return cb(true);
});
};
2016-08-12 11:10:26 -03:00
// TODO
// This handles errors from BWS/index which normally
// trigger from async events (like updates).
// Debounce function avoids multiple popups
var _handleError = function(err) {
2016-08-15 10:25:43 -03:00
$log.warn('wallet ERROR: ', err);
2016-08-12 11:10:26 -03:00
$log.warn('TODO');
2016-08-15 10:25:43 -03:00
return; // TODO!!!
2016-08-12 11:10:26 -03:00
if (err instanceof errors.NOT_AUTHORIZED) {
2016-08-15 16:07:30 -03:00
2016-08-17 15:53:17 -03:00
console.log('[walletService.js.93] TODO NOT AUTH'); //TODO
// TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
2016-08-17 17:26:13 -03:00
wallet.notAuthorized = true;
2016-08-19 13:07:18 -03:00
$state.go('tabs.home');
2016-08-12 11:10:26 -03:00
} else if (err instanceof errors.NOT_FOUND) {
2016-08-18 10:08:23 -03:00
root.showErrorPopup(gettext('Could not access Wallet Service: Not found'));
2016-08-12 11:10:26 -03:00
} else {
var msg = ""
2016-08-18 10:08:23 -03:00
$rootScope.$emit('Local/ClientError', (err.error ? err.error : err));
2016-08-12 11:10:26 -03:00
var msg = bwcError.msg(err, gettext('Error at Wallet Service'));
2016-08-18 10:08:23 -03:00
root.showErrorPopup(msg);
2016-08-12 11:10:26 -03:00
}
};
root.handleError = lodash.debounce(_handleError, 1000);
2016-08-22 22:10:46 -03:00
root.invalidateCache = function(wallet) {
2016-08-23 10:26:47 -03:00
if (wallet.cachedStatus) {
wallet.cachedStatus.isValid = false;
2016-08-22 22:10:46 -03:00
}
2016-08-23 10:26:47 -03:00
if (wallet.completeHistory) {
wallet.completeHistory.isValid = false;
2016-08-22 22:10:46 -03:00
}
};
2016-08-17 18:02:47 -03:00
root.getStatus = function(wallet, opts, cb) {
opts = opts || {};
2016-08-15 10:25:43 -03:00
2016-08-23 16:53:26 -03:00
function processPendingTxps(status) {
var txps = status.pendingTxps;
var now = Math.floor(Date.now() / 1000);
/* To test multiple outputs...
var txp = {
message: 'test multi-output',
fee: 1000,
createdOn: new Date() / 1000,
outputs: []
};
function addOutput(n) {
txp.outputs.push({
amount: 600,
toAddress: '2N8bhEwbKtMvR2jqMRcTCQqzHP6zXGToXcK',
message: 'output #' + (Number(n) + 1)
});
};
lodash.times(150, addOutput);
txps.push(txp);
*/
lodash.each(txps, function(tx) {
tx = txFormatService.processTx(tx);
// no future transactions...
if (tx.createdOn > now)
tx.createdOn = now;
tx.wallet = wallet;
if (!tx.wallet) {
$log.error("no wallet at txp?");
return;
}
var action = lodash.find(tx.actions, {
copayerId: tx.wallet.copayerId
});
if (!action && tx.status == 'pending') {
tx.pendingForUs = true;
}
if (action && action.type == 'accept') {
tx.statusForUs = 'accepted';
} else if (action && action.type == 'reject') {
tx.statusForUs = 'rejected';
} else {
tx.statusForUs = 'pending';
}
if (!tx.deleteLockTime)
tx.canBeRemoved = true;
});
wallet.pendingTxps = txps;
};
2016-08-17 18:02:47 -03:00
function get(cb) {
wallet.getStatus({
twoStep: true
}, function(err, ret) {
if (err) {
return cb(bwcError.msg(err, gettext('Could not update Wallet')));
}
return cb(null, ret);
});
};
2016-08-15 10:25:43 -03:00
2016-08-17 18:02:47 -03:00
function cacheBalance(wallet, balance) {
if (!balance) return;
2016-08-15 10:25:43 -03:00
2016-08-17 18:02:47 -03:00
var config = configService.getSync().wallet.settings;
2016-08-15 10:25:43 -03:00
2016-08-17 18:48:30 -03:00
var cache = wallet.cachedStatus;
2016-08-17 18:02:47 -03:00
// Address with Balance
2016-08-17 18:48:30 -03:00
cache.balanceByAddress = balance.byAddress;
2016-08-15 10:25:43 -03:00
2016-08-17 18:02:47 -03:00
// Spend unconfirmed funds
2016-08-17 18:48:30 -03:00
if (cache.spendUnconfirmed) {
cache.totalBalanceSat = balance.totalAmount;
cache.lockedBalanceSat = balance.lockedAmount;
cache.availableBalanceSat = balance.availableAmount;
cache.totalBytesToSendMax = balance.totalBytesToSendMax;
cache.pendingAmount = null;
2016-08-17 18:02:47 -03:00
} else {
2016-08-17 18:48:30 -03:00
cache.totalBalanceSat = balance.totalConfirmedAmount;
cache.lockedBalanceSat = balance.lockedConfirmedAmount;
cache.availableBalanceSat = balance.availableConfirmedAmount;
cache.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax;
cache.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount;
2016-08-17 18:02:47 -03:00
}
2016-08-15 10:25:43 -03:00
2016-08-17 18:02:47 -03:00
// Selected unit
2016-08-17 18:48:30 -03:00
cache.unitToSatoshi = config.unitToSatoshi;
cache.satToUnit = 1 / cache.unitToSatoshi;
cache.unitName = config.unitName;
2016-08-15 10:25:43 -03:00
2016-08-17 18:02:47 -03:00
//STR
2016-08-18 10:37:08 -03:00
cache.totalBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName;
cache.lockedBalanceStr = txFormatService.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName;
cache.availableBalanceStr = txFormatService.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName;
2016-08-15 10:25:43 -03:00
2016-08-17 18:48:30 -03:00
if (cache.pendingAmount) {
2016-08-18 10:37:08 -03:00
cache.pendingAmountStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName;
2016-08-17 18:02:47 -03:00
} else {
2016-08-17 18:48:30 -03:00
cache.pendingAmountStr = null;
2016-08-17 18:02:47 -03:00
}
2016-08-15 10:25:43 -03:00
2016-08-17 18:48:30 -03:00
cache.alternativeName = config.alternativeName;
cache.alternativeIsoCode = config.alternativeIsoCode;
2016-08-15 10:25:43 -03:00
2016-08-17 18:02:47 -03:00
rateService.whenAvailable(function() {
2016-08-15 10:25:43 -03:00
2016-08-17 18:48:30 -03:00
var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode);
var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode);
var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode);
2016-08-15 10:25:43 -03:00
2016-08-17 18:48:30 -03:00
cache.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative);
cache.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative);
cache.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate);
2016-08-12 11:10:26 -03:00
2016-08-17 18:48:30 -03:00
cache.alternativeBalanceAvailable = true;
cache.isRateAvailable = true;
2016-08-17 17:26:13 -03:00
});
};
2016-08-12 11:10:26 -03:00
2016-08-17 18:48:30 -03:00
function isStatusCached() {
return wallet.cachedStatus && wallet.cachedStatus.isValid;
};
function cacheStatus(status) {
wallet.cachedStatus = status ||  {};
var cache = wallet.cachedStatus;
cache.statusUpdatedOn = Date.now();
cache.isValid = true;
cache.email = status.preferences ? status.preferences.email : null;
2016-08-17 18:02:47 -03:00
cacheBalance(wallet, status.balance);
2016-08-17 17:26:13 -03:00
};
2016-08-12 11:10:26 -03:00
2016-08-17 18:02:47 -03:00
function walletStatusHash(status) {
return status ? status.balance.totalAmount : wallet.totalBalanceSat;
2016-08-12 11:10:26 -03:00
};
2016-08-17 18:02:47 -03:00
function _getStatus(initStatusHash, tries, cb) {
2016-08-23 10:26:47 -03:00
if (isStatusCached() && !opts.force) {
$log.debug('Wallet status cache hit:' + wallet.id);
return cb(null, wallet.cachedStatus);
};
2016-08-17 17:26:13 -03:00
tries = tries || 0;
2016-08-17 18:02:47 -03:00
$log.debug('Updating Status:', wallet.credentials.walletName, tries);
get(function(err, status) {
if (err) return cb(err);
2016-08-12 11:10:26 -03:00
2016-08-17 18:02:47 -03:00
var currentStatusHash = walletStatusHash(status);
$log.debug('Status update. hash:' + currentStatusHash + ' Try:' + tries);
if (opts.untilItChanges &&
initStatusHash == currentStatusHash &&
tries < root.WALLET_STATUS_MAX_TRIES &&
walletId == wallet.credentials.walletId) {
return $timeout(function() {
$log.debug('Retrying update... ' + walletId + ' Try:' + tries)
return _getStatus(initStatusHash, ++tries, cb);
}, root.WALLET_STATUS_DELAY_BETWEEN_TRIES * tries);
}
2016-08-12 11:10:26 -03:00
2016-08-23 16:53:26 -03:00
processPendingTxps(status);
2016-08-17 18:02:47 -03:00
$log.debug('Got Wallet Status for:' + wallet.credentials.walletName);
2016-08-12 11:10:26 -03:00
2016-08-17 18:48:30 -03:00
cacheStatus(status);
2016-08-12 11:10:26 -03:00
2016-08-17 18:02:47 -03:00
return cb(null, status);
2016-08-12 11:10:26 -03:00
});
2016-08-17 17:26:13 -03:00
};
2016-08-18 10:37:08 -03:00
_getStatus(walletStatusHash(), 0, cb);
2016-08-12 11:10:26 -03:00
};
var getSavedTxs = function(walletId, cb) {
storageService.getTxHistory(walletId, function(err, txs) {
if (err) return cb(err);
var localTxs = [];
if (!txs) {
return cb(null, localTxs);
}
try {
localTxs = JSON.parse(txs);
} catch (ex) {
$log.warn(ex);
}
return cb(null, lodash.compact(localTxs));
});
};
2016-08-15 10:25:43 -03:00
var getTxsFromServer = function(wallet, skip, endingTxid, limit, cb) {
2016-08-12 11:10:26 -03:00
var res = [];
2016-08-15 10:25:43 -03:00
wallet.getTxHistory({
2016-08-12 11:10:26 -03:00
skip: skip,
limit: limit
}, function(err, txsFromServer) {
if (err) return cb(err);
if (!txsFromServer.length)
return cb();
var res = lodash.takeWhile(txsFromServer, function(tx) {
return tx.txid != endingTxid;
});
return cb(null, res, res.length == limit);
});
};
2016-08-17 17:26:13 -03:00
var removeAndMarkSoftConfirmedTx = function(txs) {
return lodash.filter(txs, function(tx) {
if (tx.confirmations >= root.SOFT_CONFIRMATION_LIMIT)
return tx;
tx.recent = true;
});
}
var processNewTxs = function(wallet, txs) {
var config = configService.getSync().wallet.settings;
var now = Math.floor(Date.now() / 1000);
var txHistoryUnique = {};
var ret = [];
wallet.hasUnsafeConfirmed = false;
lodash.each(txs, function(tx) {
tx = txFormatService.processTx(tx);
// no future transactions...
if (tx.time > now)
tx.time = now;
2016-08-18 10:37:08 -03:00
if (tx.confirmations >= root.SAFE_CONFIRMATIONS) {
tx.safeConfirmed = root.SAFE_CONFIRMATIONS + '+';
2016-08-17 17:26:13 -03:00
} else {
tx.safeConfirmed = false;
wallet.hasUnsafeConfirmed = true;
}
if (tx.note) {
delete tx.note.encryptedEditedByName;
delete tx.note.encryptedBody;
}
if (!txHistoryUnique[tx.txid]) {
ret.push(tx);
txHistoryUnique[tx.txid] = true;
} else {
$log.debug('Ignoring duplicate TX in history: ' + tx.txid)
}
});
return ret;
};
2016-08-12 11:10:26 -03:00
2016-08-18 10:37:08 -03:00
var updateLocalTxHistory = function(wallet, progressFn, cb) {
2016-08-12 11:10:26 -03:00
var FIRST_LIMIT = 5;
var LIMIT = 50;
var requestLimit = FIRST_LIMIT;
2016-08-15 10:25:43 -03:00
var walletId = wallet.credentials.walletId;
2016-08-12 11:10:26 -03:00
var config = configService.getSync().wallet.settings;
2016-08-18 10:37:08 -03:00
progressFn = progressFn || function() {};
2016-08-12 11:10:26 -03:00
var fixTxsUnit = function(txs) {
if (!txs || !txs[0] || !txs[0].amountStr) return;
var cacheUnit = txs[0].amountStr.split(' ')[1];
if (cacheUnit == config.unitName)
return;
var name = ' ' + config.unitName;
$log.debug('Fixing Tx Cache Unit to:' + name)
lodash.each(txs, function(tx) {
2016-08-17 18:02:47 -03:00
tx.amountStr = txFormatService.formatAmount(tx.amount) + name;
2016-08-18 11:45:30 -03:00
tx.feeStr = txFormatService.formatAmount(tx.fees) + name;
2016-08-12 11:10:26 -03:00
});
};
getSavedTxs(walletId, function(err, txsFromLocal) {
if (err) return cb(err);
fixTxsUnit(txsFromLocal);
2016-08-17 17:26:13 -03:00
var confirmedTxs = removeAndMarkSoftConfirmedTx(txsFromLocal);
2016-08-12 11:10:26 -03:00
var endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null;
var endingTs = confirmedTxs[0] ? confirmedTxs[0].time : null;
// First update
2016-08-18 10:37:08 -03:00
wallet.completeHistory = txsFromLocal;
2016-08-12 11:10:26 -03:00
2016-08-18 10:37:08 -03:00
function getNewTxs(newTxs, skip, cb) {
getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res, shouldContinue) {
if (err) return cb(err);
2016-08-12 11:10:26 -03:00
2016-08-18 10:37:08 -03:00
newTxs = newTxs.concat(processNewTxs(wallet, lodash.compact(res)));
2016-08-12 11:10:26 -03:00
2016-08-18 10:37:08 -03:00
progressFn(newTxs);
2016-08-12 11:10:26 -03:00
skip = skip + requestLimit;
$log.debug('Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue);
if (!shouldContinue) {
$log.debug('Finished Sync: New / soft confirmed Txs: ' + newTxs.length);
2016-08-18 10:37:08 -03:00
return cb(null, newTxs);
2016-08-12 11:10:26 -03:00
}
requestLimit = LIMIT;
2016-08-18 10:37:08 -03:00
getNewTxs(newTxs, skip, cb);
2016-08-12 11:10:26 -03:00
});
};
getNewTxs([], 0, function(err, txs) {
if (err) return cb(err);
var newHistory = lodash.uniq(lodash.compact(txs.concat(confirmedTxs)), function(x) {
return x.txid;
});
function updateNotes(cb2) {
if (!endingTs) return cb2();
$log.debug('Syncing notes from: ' + endingTs);
2016-08-15 10:25:43 -03:00
wallet.getTxNotes({
2016-08-12 11:10:26 -03:00
minTs: endingTs
}, function(err, notes) {
if (err) {
$log.warn(err);
return cb2();
};
lodash.each(notes, function(note) {
$log.debug('Note for ' + note.txid);
lodash.each(newHistory, function(tx) {
if (tx.txid == note.txid) {
$log.debug('...updating note for ' + note.txid);
tx.note = note;
}
});
});
return cb2();
});
}
updateNotes(function() {
var historyToSave = JSON.stringify(newHistory);
lodash.each(txs, function(tx) {
tx.recent = true;
})
$log.debug('Tx History synced. Total Txs: ' + newHistory.length);
// Final update
2016-08-17 17:26:13 -03:00
if (walletId == wallet.credentials.walletId) {
wallet.completeHistory = newHistory;
2016-08-12 11:10:26 -03:00
}
return storageService.setTxHistory(historyToSave, walletId, function() {
$log.debug('Tx History saved.');
return cb();
});
});
});
});
};
2016-08-18 10:37:08 -03:00
root.getTxHistory = function(wallet, opts, cb) {
2016-08-17 18:48:30 -03:00
opts = opts || {};
2016-08-15 10:25:43 -03:00
var walletId = wallet.credentials.walletId;
2016-08-12 11:10:26 -03:00
2016-08-17 18:02:47 -03:00
if (!wallet.isComplete()) return cb();
2016-08-12 11:10:26 -03:00
2016-08-22 22:10:46 -03:00
function isHistoryCached() {
return wallet.completeHistory && wallet.completeHistory.isValid;
};
if (isHistoryCached() && !opts.force) return cb(null, wallet.completeHistory);
2016-08-12 11:10:26 -03:00
$log.debug('Updating Transaction History');
2016-08-18 10:37:08 -03:00
updateLocalTxHistory(wallet, opts.progressFn, function(err) {
2016-08-17 18:48:30 -03:00
if (err) return cb(err);
2016-08-22 22:10:46 -03:00
wallet.completeHistory.isValid = true;
2016-08-18 10:37:08 -03:00
return cb(err, wallet.completeHistory);
2016-08-12 11:10:26 -03:00
});
};
2016-08-15 10:25:43 -03:00
root.isEncrypted = function(wallet) {
if (lodash.isEmpty(wallet)) return;
var isEncrypted = wallet.isPrivKeyEncrypted();
if (isEncrypted) $log.debug('Wallet is encrypted');
return isEncrypted;
};
2016-08-15 10:25:43 -03:00
root.createTx = function(wallet, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(wallet))
return cb('MISSING_PARAMETER');
if (txp.sendMax) {
2016-08-15 10:25:43 -03:00
wallet.createTxProposal(txp, function(err, createdTxp) {
if (err) return cb(err);
else return cb(null, createdTxp);
});
} else {
2016-08-15 10:25:43 -03:00
wallet.getFeeLevels(wallet.credentials.network, function(err, levels) {
if (err) return cb(err);
var feeLevelValue = lodash.find(levels, {
level: txp.feeLevel
});
2016-06-06 18:26:45 -03:00
if (!feeLevelValue || !feeLevelValue.feePerKB)
return cb({
message: 'Could not get dynamic fee for level: ' + feeLevel
});
$log.debug('Dynamic fee: ' + txp.feeLevel + ' ' + feeLevelValue.feePerKB + ' SAT');
txp.feePerKb = feeLevelValue.feePerKB;
2016-08-15 10:25:43 -03:00
wallet.createTxProposal(txp, function(err, createdTxp) {
if (err) return cb(err);
else {
$log.debug('Transaction created');
return cb(null, createdTxp);
}
});
});
}
};
2016-08-15 10:25:43 -03:00
root.publishTx = function(wallet, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(wallet))
return cb('MISSING_PARAMETER');
2016-08-15 10:25:43 -03:00
wallet.publishTxProposal({
2016-06-06 18:26:45 -03:00
txp: txp
}, function(err, publishedTx) {
if (err) return cb(err);
else {
$log.debug('Transaction published');
return cb(null, publishedTx);
}
});
};
2016-08-29 11:58:23 -03:00
root.signTx = function(wallet, txp, password, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(wallet) || lodash.isEmpty(cb))
return cb('MISSING_PARAMETER');
2016-06-06 18:26:45 -03:00
2016-08-15 10:25:43 -03:00
if (wallet.isPrivKeyExternal()) {
switch (wallet.getPrivKeyExternalSourceName()) {
case 'ledger':
2016-08-15 10:25:43 -03:00
return _signWithLedger(wallet, txp, cb);
case 'trezor':
2016-08-15 10:25:43 -03:00
return _signWithTrezor(wallet, txp, cb);
default:
2016-08-15 10:25:43 -03:00
var msg = 'Unsupported External Key:' + wallet.getPrivKeyExternalSourceName();
$log.error(msg);
return cb(msg);
}
} else {
try {
2016-08-29 11:58:23 -03:00
wallet.signTxProposal(txp, password, function(err, signedTxp) {
$log.debug('Transaction signed err:' + err);
return cb(err, signedTxp);
});
} catch (e) {
$log.warn('Error at signTxProposal:', e);
return cb(e);
}
}
};
2016-08-15 10:25:43 -03:00
root.broadcastTx = function(wallet, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(wallet))
return cb('MISSING_PARAMETER');
2016-06-06 18:26:45 -03:00
if (txp.status != 'accepted')
return cb('TX_NOT_ACCEPTED');
2016-08-15 10:25:43 -03:00
wallet.broadcastTxProposal(txp, function(err, broadcastedTxp, memo) {
2016-06-06 18:26:45 -03:00
if (err)
return cb(err);
$log.debug('Transaction broadcasted');
if (memo) $log.info(memo);
return cb(null, broadcastedTxp);
});
};
2016-08-15 10:25:43 -03:00
root.rejectTx = function(wallet, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(wallet))
return cb('MISSING_PARAMETER');
2016-06-06 18:26:45 -03:00
2016-08-15 10:25:43 -03:00
wallet.rejectTxProposal(txp, null, function(err, rejectedTxp) {
$log.debug('Transaction rejected');
return cb(err, rejectedTxp);
});
};
2016-08-15 10:25:43 -03:00
root.removeTx = function(wallet, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(wallet))
return cb('MISSING_PARAMETER');
2016-06-06 18:26:45 -03:00
2016-08-15 10:25:43 -03:00
wallet.removeTxProposal(txp, function(err) {
$log.debug('Transaction removed');
return cb(err);
});
};
2016-06-06 18:26:45 -03:00
root.updateRemotePreferences = function(clients, prefs, cb) {
prefs = prefs || {};
if (!lodash.isArray(clients))
clients = [clients];
function updateRemotePreferencesFor(clients, prefs, cb) {
2016-08-15 10:25:43 -03:00
var wallet = clients.shift();
if (!wallet) return cb();
$log.debug('Saving remote preferences', wallet.credentials.walletName, prefs);
2016-06-06 18:26:45 -03:00
2016-08-15 10:25:43 -03:00
wallet.savePreferences(prefs, function(err) {
2016-06-06 18:26:45 -03:00
// we ignore errors here
if (err) $log.warn(err);
updateRemotePreferencesFor(clients, prefs, cb);
});
};
// Update this JIC.
var config = configService.getSync().wallet.settings;
//prefs.email (may come from arguments)
2016-06-07 10:37:39 -03:00
prefs.language = uxLanguage.getCurrentLanguage();
2016-06-06 18:26:45 -03:00
prefs.unit = config.unitCode;
updateRemotePreferencesFor(clients, prefs, function(err) {
if (err) return cb(err);
lodash.each(clients, function(c) {
c.preferences = lodash.assign(prefs, c.preferences);
});
return cb();
});
};
2016-08-18 10:08:23 -03:00
root.showErrorPopup = function(msg, cb) {
2016-08-12 11:10:26 -03:00
$log.warn('Showing err popup:' + msg);
2016-08-18 10:08:23 -03:00
// An alert dialog
var alertPopup = $ionicPopup.alert({
title: title,
template: msg
});
2016-08-12 11:10:26 -03:00
2016-08-18 10:08:23 -03:00
if (!cb) cb = function() {};
2016-08-12 11:10:26 -03:00
2016-08-18 10:08:23 -03:00
alertPopup.then(cb);
2016-08-12 11:10:26 -03:00
};
2016-08-19 13:07:18 -03:00
// walletHome
2016-08-15 10:25:43 -03:00
root.recreate = function(wallet, cb) {
2016-08-12 11:10:26 -03:00
ongoingProcess.set('recreating', true);
2016-08-15 10:25:43 -03:00
wallet.recreateWallet(function(err) {
2016-08-17 17:26:13 -03:00
wallet.notAuthorized = false;
2016-08-12 11:10:26 -03:00
ongoingProcess.set('recreating', false);
if (err) {
2016-08-19 13:07:18 -03:00
handleError(err);
2016-08-12 11:10:26 -03:00
return;
}
2016-08-15 10:25:43 -03:00
profileService.bindWalletClient(wallet, {
2016-08-12 11:10:26 -03:00
force: true
});
2016-08-17 17:26:13 -03:00
wallet.startScan(wallet);
2016-08-12 11:10:26 -03:00
});
};
2016-08-15 10:25:43 -03:00
root.startScan = function(wallet) {
$log.debug('Scanning wallet ' + wallet.credentials.walletId);
if (!wallet.isComplete()) return;
2016-08-12 11:10:26 -03:00
2016-08-17 17:26:13 -03:00
// wallet.updating = true;
2016-08-12 11:10:26 -03:00
2016-08-15 10:25:43 -03:00
wallet.startScan({
2016-08-12 11:10:26 -03:00
includeCopayerBranches: true,
}, function(err) {
2016-08-24 17:54:01 -03:00
if (err && wallet.walletId == walletId) {
wallet.updating = false;
handleError(err);
}
2016-08-12 11:10:26 -03:00
});
};
2016-08-15 10:25:43 -03:00
root.expireAddress = function(wallet, cb) {
$log.debug('Cleaning Address ' + wallet.id);
storageService.clearLastAddress(wallet.id, function(err) {
return cb(err);
});
};
root.isUsed = function(wallet, byAddress, cb) {
storageService.getLastAddress(wallet.id, function(err, addr) {
var used = lodash.find(byAddress, {
address: addr
});
return cb(null, used);
});
};
2016-08-15 11:56:59 -03:00
var createAddress = function(wallet, cb) {
2016-08-15 10:25:43 -03:00
$log.debug('Creating address for wallet:', wallet.id);
wallet.createAddress({}, function(err, addr) {
if (err) {
var prefix = gettextCatalog.getString('Could not create address');
if (err.error && err.error.match(/locked/gi)) {
$log.debug(err.error);
return $timeout(function() {
2016-08-15 11:56:59 -03:00
createAddress(wallet, cb);
2016-08-15 10:25:43 -03:00
}, 5000);
} else if (err.message && err.message == 'MAIN_ADDRESS_GAP_REACHED') {
$log.warn(err.message);
prefix = null;
wallet.getMainAddresses({
reverse: true,
limit: 1
}, function(err, addr) {
if (err) return cb(err);
return cb(null, addr[0].address);
});
}
return bwcError.cb(err, prefix, cb);
}
return cb(null, addr.address);
});
};
root.getAddress = function(wallet, forceNew, cb) {
var firstStep;
if (forceNew) {
firstStep = storageService.clearLastAddress;
} else {
firstStep = function(walletId, cb) {
return cb();
};
}
firstStep(wallet.id, function(err) {
if (err) return cb(err);
storageService.getLastAddress(wallet.id, function(err, addr) {
if (err) return cb(err);
if (addr) return cb(null, addr);
2016-08-15 11:56:59 -03:00
createAddress(wallet, function(err, addr) {
2016-08-15 10:25:43 -03:00
if (err) return cb(err);
storageService.storeLastAddress(wallet.id, addr, function() {
if (err) return cb(err);
return cb(null, addr);
});
});
});
});
};
2016-08-12 11:10:26 -03:00
2016-08-15 16:07:30 -03:00
root.isReady = function(wallet, cb) {
if (!wallet.isComplete())
return cb('WALLET_NOT_COMPLETE');
root.needsBackup(wallet, function(needsBackup) {
if (needsBackup)
return cb('WALLET_NEEDS_BACKUP');
return cb();
});
};
2016-08-18 10:08:23 -03:00
// An alert dialog
var askPassword = function(name, cb) {
var scope = $rootScope.$new(true);
scope.data = [];
var pass = $ionicPopup.show({
template: '<input type="password" ng-model="data.pass">',
title: 'Enter Spending Password',
subTitle: name,
scope: scope,
buttons: [{
text: 'Cancel'
}, {
text: '<b>OK</b>',
type: 'button-positive',
onTap: function(e) {
if (!scope.data.pass) {
//don't allow the user to close unless he enters wifi password
e.preventDefault();
return;
2016-08-15 16:07:30 -03:00
2016-08-18 10:08:23 -03:00
}
return scope.data.pass;
}
}]
});
pass.then(function(res) {
return cb(res);
});
};
root.handleEncryptedWallet = function(wallet, cb) {
if (!root.isEncrypted(wallet)) return cb();
askPassword(wallet.name, function(password) {
if (!password) return cb('no password');
2016-08-29 11:58:23 -03:00
return cb(null, password);
2016-08-18 10:08:23 -03:00
});
};
2016-08-23 13:20:57 -03:00
root.reject = function(wallet, txp, cb) {
ongoingProcess.set('rejectTx', true);
root.rejectTx(wallet, txp, function(err, txpr) {
root.invalidateCache(wallet);
ongoingProcess.set('rejectTx', false);
if (err) return cb(err);
$rootScope.$emit('Local/TxAction', wallet.id);
return cb(null, txpr);
});
};
2016-08-18 10:08:23 -03:00
root.onlyPublish = function(wallet, txp, cb) {
ongoingProcess.set('sendingTx', true);
root.publishTx(wallet, txp, function(err, publishedTxp) {
2016-08-23 13:20:57 -03:00
root.invalidateCache(wallet);
2016-08-18 10:08:23 -03:00
ongoingProcess.set('sendingTx', false);
2016-08-17 17:31:45 -03:00
if (err) return cb(err);
2016-08-18 10:08:23 -03:00
var type = txStatus.notify(createdTxp);
root.openStatusModal(type, createdTxp, function() {
2016-08-23 13:20:57 -03:00
$rootScope.$emit('Local/TxAction', wallet.id);
return;
2016-08-18 10:08:23 -03:00
});
return cb(null, publishedTxp);
});
};
2016-08-29 11:58:23 -03:00
root.prepare = function(wallet, cb) {
fingerprintService.check(wallet, function(err) {
if (err) return cb(err);
root.handleEncryptedWallet(wallet, function(err, password) {
if (err) return cb(err);
return cb(null, password);
});
});
};
2016-08-18 10:08:23 -03:00
root.publishAndSign = function(wallet, txp, cb) {
var publishFn = root.publishTx;
// Already published?
if (txp.status == 'pending') {
publishFn = function(wallet, txp, cb) {
return cb(null, txp);
};
}
2016-08-29 11:58:23 -03:00
root.prepare(wallet, function(err, password) {
2016-08-18 10:08:23 -03:00
if (err) return cb(err);
2016-08-29 11:58:23 -03:00
ongoingProcess.set('sendingTx', true);
publishFn(wallet, txp, function(err, publishedTxp) {
ongoingProcess.set('sendingTx', false);
2016-08-18 10:08:23 -03:00
if (err) return cb(err);
2016-08-29 11:58:23 -03:00
ongoingProcess.set('signingTx', true);
root.signTx(wallet, publishedTxp, password, function(err, signedTxp) {
ongoingProcess.set('signingTx', false);
root.invalidateCache(wallet);
2016-08-18 10:08:23 -03:00
2016-08-29 11:58:23 -03:00
if (err) {
// TODO?
var msg = err.message ?
err.message :
gettext('The payment was created but could not be completed. Please try again from home screen');
$rootScope.$emit('Local/TxAction', wallet.id);
return cb(err);
}
2016-08-18 10:08:23 -03:00
2016-08-29 11:58:23 -03:00
if (signedTxp.status == 'accepted') {
ongoingProcess.set('broadcastingTx', true);
root.broadcastTx(wallet, signedTxp, function(err, broadcastedTxp) {
ongoingProcess.set('broadcastingTx', false);
if (err) return cb(err);
2016-08-18 10:08:23 -03:00
2016-08-29 11:58:23 -03:00
var type = txStatus.notify(broadcastedTxp);
root.openStatusModal(type, broadcastedTxp, function() {
2016-08-23 13:20:57 -03:00
$rootScope.$emit('Local/TxAction', wallet.id);
2016-08-18 10:08:23 -03:00
});
2016-08-29 11:58:23 -03:00
return cb(null, broadcastedTxp)
});
} else {
var type = txStatus.notify(signedTxp);
root.openStatusModal(type, signedTxp, function() {
root.invalidateCache(wallet);
$rootScope.$emit('Local/TxAction', wallet.id);
});
return cb(null, signedTxp);
}
2016-08-18 10:08:23 -03:00
});
});
2016-08-17 17:31:45 -03:00
});
};
2016-08-18 19:23:58 -03:00
2016-08-24 17:54:01 -03:00
root.getNotifications = function(wallet, opts, cb) {
wallet.getNotifications(opts, function(err, notifications) {
if (err) return cb(err);
notifications = lodash.filter(notifications, function(x) {
2016-08-29 11:58:23 -03:00
return x.type != 'NewBlock' && x.type != 'BalanceUpdated' && x.type != 'NewOutgoingTxByThirdParty';
2016-08-24 17:54:01 -03:00
});
var idToName = {};
if (wallet.cachedStatus) {
lodash.each(wallet.cachedStatus.wallet.copayers, function(c) {
idToName[c.id] = c.name;
});
}
lodash.each(notifications, function(x) {
x.wallet = wallet;
if (x.creatorId && wallet.cachedStatus) {
x.creatorName = idToName[x.creatorId];
2016-08-18 19:23:58 -03:00
};
2016-08-24 17:54:01 -03:00
});
2016-08-18 19:23:58 -03:00
2016-08-24 17:54:01 -03:00
return cb(null, notifications);
});
};
2016-08-18 19:23:58 -03:00
2016-08-24 17:54:01 -03:00
root.getEncodedWalletInfo = function(wallet, cb) {
var getCode = function() {
var derivationPath = wallet.credentials.getBaseAddressDerivationPath();
var encodingType = {
mnemonic: 1,
xpriv: 2,
xpub: 3
};
var info;
// not supported yet
if (wallet.credentials.derivationStrategy != 'BIP44' || !wallet.canSign())
return null;
if (wallet.credentials.mnemonic) {
info = {
type: encodingType.mnemonic,
data: wallet.credentials.mnemonic,
}
} else {
info = {
type: encodingType.xpriv,
data: wallet.credentials.xPrivKey
2016-08-18 19:23:58 -03:00
}
2016-08-24 17:54:01 -03:00
}
return info.type + '|' + info.data + '|' + wallet.credentials.network.toLowerCase() + '|' + derivationPath + '|' + (wallet.credentials.mnemonicHasPassphrase);
2016-08-18 19:23:58 -03:00
};
fingerprintService.check(wallet, function(err) {
if (err) return cb(err);
2016-08-17 17:31:45 -03:00
2016-08-18 19:23:58 -03:00
root.handleEncryptedWallet(wallet, function(err) {
if (err) return cb(err);
var code = getCode();
return cb(null, code);
});
});
};
2016-08-17 17:31:45 -03:00
2016-08-24 17:54:01 -03:00
root.processNotifications = function(notifications, limit) {
if (!notifications) return [];
var shown = lodash.sortBy(notifications, 'createdOn').reverse();
if (limit)
shown = shown.splice(0, limit);
lodash.each(shown, function(x) {
x.txpId = x.data ? x.data.txProposalId : null;
x.txid = x.data ? x.data.txid : null;
x.types = [x.type];
if (x.data && x.data.amount)
x.amountStr = txFormatService.formatAmountStr(x.data.amount);
x.action = function() {
// TODO?
$state.go('wallet.details', {
walletId: x.walletId,
txpId: x.txpId,
txid: x.txid,
});
};
});
// condense
var finale = [],
prev;
lodash.each(shown, function(x) {
2016-08-25 18:40:53 -03:00
if (prev && prev.walletId === x.walletId && prev.txpId && prev.txpId === x.txpId && prev.creatorId && prev.creatorId === x.creatorId) {
2016-08-24 17:54:01 -03:00
prev.types.push(x.type);
prev.data = lodash.assign(prev.data, x.data);
prev.txid = prev.txid || x.txid;
2016-08-25 18:40:53 -03:00
prev.amountStr = prev.amountStr || x.amountStr;
prev.creatorName = prev.creatorName || x.creatorName;
2016-08-24 17:54:01 -03:00
} else {
finale.push(x);
prev = x;
}
});
2016-08-29 11:58:23 -03:00
// messages...
2016-08-24 17:54:01 -03:00
var u = bwcService.getUtils();
lodash.each(finale, function(x) {
if (x.data && x.data.message && x.wallet && x.wallet.credentials.sharedEncryptingKey) {
// TODO TODO TODO => BWC
x.message = u.decryptMessage(x.data.message, x.wallet.credentials.sharedEncryptingKey);
}
});
return finale;
};
2016-08-24 19:28:20 -03:00
2016-08-29 11:58:23 -03:00
root.setTouchId = function(wallet, enabled, cb) {
fingerprintService.check(wallet, function(err) {
if (err) return cb(err); {
$log.debug(err);
return;
}
configService.set(opts, cb);
});
};
root.getKey = function(wallet, cb) {
root.prepare(wallet, function(err, password) {
if (err) return cb(err);
var keys;
try {
keys = wallet.getKeys(password);
} catch (e) {
return cb(err);
}
return cb(null, keys);
});
};
2016-08-24 19:28:20 -03:00
return root;
});