Fix Conflicts:
views/addresses.html views/send.html views/transactions.html
This commit is contained in:
commit
6a855b7ac1
18 changed files with 393 additions and 89 deletions
|
|
@ -368,6 +368,7 @@ a:hover {
|
||||||
.pr {position: relative;}
|
.pr {position: relative;}
|
||||||
.pa {position: absolute;}
|
.pa {position: absolute;}
|
||||||
.m0 {margin: 0;}
|
.m0 {margin: 0;}
|
||||||
|
.p0 {padding: 0 !important;}
|
||||||
.db {display: block;}
|
.db {display: block;}
|
||||||
.size-12 { font-size: 12px; }
|
.size-12 { font-size: 12px; }
|
||||||
.size-14 { font-size: 14px; }
|
.size-14 { font-size: 14px; }
|
||||||
|
|
@ -923,8 +924,8 @@ button, .button, p {
|
||||||
|
|
||||||
.addresses .list-addr i {
|
.addresses .list-addr i {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -25px;
|
top: -15px;
|
||||||
left: 6px;
|
left: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1194,5 +1195,10 @@ a.text-warning:hover {color: #FD7262;}
|
||||||
padding: 50px;
|
padding: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collapse {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------*/
|
/*-----------------------------------------------------------------*/
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,14 @@
|
||||||
border-right: 1px solid #425568;
|
border-right: 1px solid #425568;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right-small {
|
||||||
|
border-left: 1px solid #425568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-small a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.panel .secret {
|
.panel .secret {
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
@ -123,6 +131,10 @@
|
||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addresses .panel {
|
||||||
|
padding: 1rem 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-copy {
|
.btn-copy {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@
|
||||||
<section class="left-small">
|
<section class="left-small">
|
||||||
<a class="left-off-canvas-toggle menu-icon" ><span></span></a>
|
<a class="left-off-canvas-toggle menu-icon" ><span></span></a>
|
||||||
</section>
|
</section>
|
||||||
|
<section ng-controller="SidebarController" class="right-small text-center">
|
||||||
|
<a href="#" ng-click="signout()"><i class="size-24 fi-power"></i></a>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="middle tab-bar-section">
|
<section class="middle tab-bar-section">
|
||||||
<h1 class="right">
|
<h1 class="right">
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ if (localConfig) {
|
||||||
if (key === 'networkName' && config['forceNetwork']) {
|
if (key === 'networkName' && config['forceNetwork']) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
config[name] = value;
|
config[key] = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
this._alternative = newValue;
|
this._alternative = newValue;
|
||||||
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
|
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
|
||||||
this._amount = Number.parseFloat(
|
this._amount = parseFloat(
|
||||||
(rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit
|
(rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit
|
||||||
).toFixed(config.unitDecimals), 10);
|
).toFixed(config.unitDecimals), 10);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -52,7 +52,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
this._amount = newValue;
|
this._amount = newValue;
|
||||||
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
|
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
|
||||||
this._alternative = Number.parseFloat(
|
this._alternative = parseFloat(
|
||||||
(rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode)
|
(rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode)
|
||||||
).toFixed(2), 10);
|
).toFixed(2), 10);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,45 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// 62.9% typed (by google's closure-compiler account)
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var HK = bitcore.HierarchicalKey;
|
var HK = bitcore.HierarchicalKey;
|
||||||
var WalletKey = bitcore.WalletKey;
|
var WalletKey = bitcore.WalletKey;
|
||||||
var networks = bitcore.networks;
|
var networks = bitcore.networks;
|
||||||
var util = bitcore.util;
|
var util = bitcore.util;
|
||||||
|
var _ = require('underscore');
|
||||||
|
var preconditions = require('preconditions').instance();
|
||||||
var HDPath = require('./HDPath');
|
var HDPath = require('./HDPath');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc
|
||||||
|
* Wrapper for bitcore.HierarchicalKey to be used inside of Copay.
|
||||||
|
*
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {string} opts.networkName if set to 'testnet', use the test3 bitcoin
|
||||||
|
* network constants (livenet otherwise)
|
||||||
|
* @param {string} opts.extendedPrivateKeyString if set, use this private key
|
||||||
|
* string, othewise create a new
|
||||||
|
* private key
|
||||||
|
*/
|
||||||
function PrivateKey(opts) {
|
function PrivateKey(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
this.network = opts.networkName === 'testnet' ?
|
this.network = opts.networkName === 'testnet' ? networks.testnet : networks.livenet;
|
||||||
networks.testnet : networks.livenet;
|
|
||||||
var init = opts.extendedPrivateKeyString || this.network.name;
|
var init = opts.extendedPrivateKeyString || this.network.name;
|
||||||
this.bip = new HK(init);
|
this.bip = new HK(init);
|
||||||
this.privateKeyCache = {};
|
this.privateKeyCache = {};
|
||||||
this.publicHex = this.deriveBIP45Branch().eckey.public.toString('hex');
|
this.publicHex = this.deriveBIP45Branch().eckey.public.toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Retrieve this derivated private key's public key in hexa format
|
||||||
|
*
|
||||||
|
* The value returned is calculated using the path from PrivateKey's
|
||||||
|
* <tt>HDParams.IdFullBranch</tt>. This key is used to identify the copayer
|
||||||
|
* (signing messages mostly).
|
||||||
|
*
|
||||||
|
* @returns {string} the public key in a hexadecimal string
|
||||||
|
*/
|
||||||
PrivateKey.prototype.getId = function() {
|
PrivateKey.prototype.getId = function() {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
this.cacheId();
|
this.cacheId();
|
||||||
|
|
@ -25,6 +47,15 @@ PrivateKey.prototype.getId = function() {
|
||||||
return this.id;
|
return this.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Retrieve this private key's private key in hex format
|
||||||
|
*
|
||||||
|
* The value returned is calculated using the path from PrivateKey's
|
||||||
|
* <tt>HDParams.IdFullBranch</tt>. This key is used to identify the copayer
|
||||||
|
* (signing messages mostly).
|
||||||
|
*
|
||||||
|
* @returns {string} the private key in a hexadecimal string
|
||||||
|
*/
|
||||||
PrivateKey.prototype.getIdPriv = function() {
|
PrivateKey.prototype.getIdPriv = function() {
|
||||||
if (!this.idpriv) {
|
if (!this.idpriv) {
|
||||||
this.cacheId();
|
this.cacheId();
|
||||||
|
|
@ -32,6 +63,15 @@ PrivateKey.prototype.getIdPriv = function() {
|
||||||
return this.idpriv;
|
return this.idpriv;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Retrieve this private key's private key
|
||||||
|
*
|
||||||
|
* The value returned is calculated using the path from PrivateKey's
|
||||||
|
* <tt>HDParams.IdFullBranch</tt>. This key is used to identify the copayer
|
||||||
|
* (signing messages mostly).
|
||||||
|
*
|
||||||
|
* @returns {bitcore.PrivateKey} the private key
|
||||||
|
*/
|
||||||
PrivateKey.prototype.getIdKey = function() {
|
PrivateKey.prototype.getIdKey = function() {
|
||||||
if (!this.idkey) {
|
if (!this.idkey) {
|
||||||
this.cacheId();
|
this.cacheId();
|
||||||
|
|
@ -39,6 +79,11 @@ PrivateKey.prototype.getIdKey = function() {
|
||||||
return this.idkey;
|
return this.idkey;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Caches the result of deriving IdFullBranch
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
PrivateKey.prototype.cacheId = function() {
|
PrivateKey.prototype.cacheId = function() {
|
||||||
var path = HDPath.IdFullBranch;
|
var path = HDPath.IdFullBranch;
|
||||||
var idhk = this.bip.derive(path);
|
var idhk = this.bip.derive(path);
|
||||||
|
|
@ -47,54 +92,116 @@ PrivateKey.prototype.cacheId = function() {
|
||||||
this.idpriv = idhk.eckey.private.toString('hex');
|
this.idpriv = idhk.eckey.private.toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Derive the master branch for Copay.
|
||||||
|
*/
|
||||||
PrivateKey.prototype.deriveBIP45Branch = function() {
|
PrivateKey.prototype.deriveBIP45Branch = function() {
|
||||||
if (!this.bip45Branch) {
|
if (!this.bip45Branch) {
|
||||||
this.bip45Branch = this.bip.derive(HDPath.BIP45_PUBLIC_PREFIX);
|
this.bip45Branch = this.bip.derive(HDPath.BIP45_PUBLIC_PREFIX);
|
||||||
}
|
}
|
||||||
return this.bip45Branch;
|
return this.bip45Branch;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PrivateKey.trim = function(data) {
|
|
||||||
var opts = {};
|
|
||||||
['networkName', 'extendedPrivateKeyString'].forEach(function(k){
|
|
||||||
opts[k] = data[k];
|
|
||||||
});
|
|
||||||
|
|
||||||
return opts;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Returns an object with information needed to rebuild a PrivateKey
|
||||||
|
* (as most of its properties are derived from the extended private key).
|
||||||
|
*
|
||||||
|
* @TODO: Figure out if this is the correct pattern
|
||||||
|
* This is a static method and is probably used for serialization.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {*} data.networkName - a name for a bitcoin network
|
||||||
|
* @param {*} data.extendedPrivateKeyString - a bip32 extended private key
|
||||||
|
* @returns {Object} an object with two properties: networkName and
|
||||||
|
* extendedPrivateKeyString, taken from the <tt>data</tt>
|
||||||
|
* parameter.
|
||||||
|
*/
|
||||||
|
PrivateKey.trim = function(data) {
|
||||||
|
var opts = {};
|
||||||
|
['networkName', 'extendedPrivateKeyString'].forEach(function(k){
|
||||||
|
opts[k] = data[k];
|
||||||
|
});
|
||||||
|
return opts
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Generate a private Key from a serialized object
|
||||||
|
*
|
||||||
|
* @TODO: This method uses PrivateKey.trim but it's actually not needed...
|
||||||
|
*
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {*} data.networkName - a name for a bitcoin network
|
||||||
|
* @param {*} data.extendedPrivateKeyString - a bip32 extended private key
|
||||||
|
* @returns {PrivateKey}
|
||||||
|
*/
|
||||||
PrivateKey.fromObj = function(obj) {
|
PrivateKey.fromObj = function(obj) {
|
||||||
return new PrivateKey(PrivateKey.trim(obj));
|
return new PrivateKey(PrivateKey.trim(obj));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Serialize a private key, keeping only the data necessary to rebuild it
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
PrivateKey.prototype.toObj = function() {
|
PrivateKey.prototype.toObj = function() {
|
||||||
return {
|
return {
|
||||||
extendedPrivateKeyString: this.getExtendedPrivateKeyString(),
|
extendedPrivateKeyString: this.getExtendedPrivateKeyString(),
|
||||||
networkName: this.network.name,
|
networkName: this.network.name
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Retrieve a BIP32 extended public key as generated by bitcore
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
PrivateKey.prototype.getExtendedPublicKeyString = function() {
|
PrivateKey.prototype.getExtendedPublicKeyString = function() {
|
||||||
return this.bip.extendedPublicKeyString();
|
return this.bip.extendedPublicKeyString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Retrieve a BIP32 extended private key as generated by bitcore
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
PrivateKey.prototype.getExtendedPrivateKeyString = function() {
|
PrivateKey.prototype.getExtendedPrivateKeyString = function() {
|
||||||
return this.bip.extendedPrivateKeyString();
|
return this.bip.extendedPrivateKeyString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc
|
||||||
|
* Retrieve a HierarchicalKey derived from the given path as generated by
|
||||||
|
* bitcore
|
||||||
|
* @param {string} path - a string for derivation (something like "m/234'/1/2")
|
||||||
|
* @returns {bitcore.HierarchicalKey}
|
||||||
|
*/
|
||||||
PrivateKey.prototype._getHK = function(path) {
|
PrivateKey.prototype._getHK = function(path) {
|
||||||
if (typeof path === 'undefined') {
|
if (_.isUndefined(path)) {
|
||||||
return this.bip;
|
return this.bip;
|
||||||
}
|
}
|
||||||
var ret = this.bip.derive(path);
|
var ret = this.bip.derive(path);
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc
|
||||||
|
* Retrieve an array of WalletKey derived from given paths. {@see PrivateKey#getForPath}
|
||||||
|
*
|
||||||
|
* @param {string[]} paths - the paths to derive
|
||||||
|
* @returns {bitcore.WalletKey[]} - the derived keys
|
||||||
|
*/
|
||||||
PrivateKey.prototype.getForPaths = function(paths) {
|
PrivateKey.prototype.getForPaths = function(paths) {
|
||||||
return paths.map(this.getForPath.bind(this));
|
return paths.map(this.getForPath.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc
|
||||||
|
* Retrieve a WalletKey derived from a path.
|
||||||
|
*
|
||||||
|
* @param {string} paths - the path to derive
|
||||||
|
* @returns {bitcore.WalletKey} - the derived key
|
||||||
|
*/
|
||||||
PrivateKey.prototype.getForPath = function(path) {
|
PrivateKey.prototype.getForPath = function(path) {
|
||||||
var pk = this.privateKeyCache[path];
|
var pk = this.privateKeyCache[path];
|
||||||
if (!pk) {
|
if (!pk) {
|
||||||
|
|
@ -110,14 +217,38 @@ PrivateKey.prototype.getForPath = function(path) {
|
||||||
return wk;
|
return wk;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc
|
||||||
|
* Retrieve a Branch for Copay using the given path
|
||||||
|
*
|
||||||
|
* @TODO: Investigate when is this called and if this is really needed
|
||||||
|
*
|
||||||
|
* @param {number} index - the index of the key to generate
|
||||||
|
* @param {boolean} isChange - whether this is a change adderess or a receive
|
||||||
|
* @param {number} cosigner - the cosigner index
|
||||||
|
* @return {bitcore.HierarchicalKey}
|
||||||
|
*/
|
||||||
PrivateKey.prototype.get = function(index, isChange, cosigner) {
|
PrivateKey.prototype.get = function(index, isChange, cosigner) {
|
||||||
|
|
||||||
|
// TODO: Add parameter validation?
|
||||||
|
|
||||||
var path = HDPath.FullBranch(index, isChange, cosigner);
|
var path = HDPath.FullBranch(index, isChange, cosigner);
|
||||||
return this.getForPath(path);
|
return this.getForPath(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc
|
||||||
|
* Retrieve multiple branches for Copay up to the received indexes
|
||||||
|
*
|
||||||
|
* @TODO: Investigate when is this called and if this is really needed
|
||||||
|
*
|
||||||
|
* @param {number} receiveIndex - the number of receive addresses to generate
|
||||||
|
* @param {number} changeIndex - the number of change addresses to generate
|
||||||
|
* @param {number} cosigner - the cosigner index
|
||||||
|
* @return {bitcore.HierarchicalKey}
|
||||||
|
*/
|
||||||
PrivateKey.prototype.getAll = function(receiveIndex, changeIndex, cosigner) {
|
PrivateKey.prototype.getAll = function(receiveIndex, changeIndex, cosigner) {
|
||||||
if (typeof receiveIndex === 'undefined' || typeof changeIndex === 'undefined')
|
preconditions.checkArgument(!_.isUndefined(receiveIndex) && !_.isUndefined(changeIndex));
|
||||||
throw new Error('Invalid parameters');
|
|
||||||
|
|
||||||
var ret = [];
|
var ret = [];
|
||||||
for (var i = 0; i < receiveIndex; i++) {
|
for (var i = 0; i < receiveIndex; i++) {
|
||||||
|
|
@ -129,6 +260,4 @@ PrivateKey.prototype.getAll = function(receiveIndex, changeIndex, cosigner) {
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = PrivateKey;
|
module.exports = PrivateKey;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@ TxProposals.prototype.getNtxids = function() {
|
||||||
return Object.keys(this.txps);
|
return Object.keys(this.txps);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TxProposals.prototype.deleteOne = function(ntxid) {
|
||||||
|
preconditions.checkState(this.txps[ntxid], 'Unknown TXP: ' + ntxid);
|
||||||
|
delete this.txps[ntxid];
|
||||||
|
};
|
||||||
|
|
||||||
TxProposals.prototype.deleteAll = function() {
|
TxProposals.prototype.deleteAll = function() {
|
||||||
this.txps = {};
|
this.txps = {};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1534,6 +1534,57 @@ Wallet.prototype.getUnspent = function(cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wallet.prototype.removeTxWithSpentInputs = function(cb) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
cb = cb || function () {};
|
||||||
|
|
||||||
|
var txps = [];
|
||||||
|
var maxRejectCount = this.maxRejectCount();
|
||||||
|
for (var ntxid in this.txProposals.txps) {
|
||||||
|
var txp = this.txProposals.txps[ntxid];
|
||||||
|
txp.ntxid = ntxid;
|
||||||
|
if (txp.isPending(maxRejectCount)) {
|
||||||
|
txps.push(txp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputs = [];
|
||||||
|
txps.forEach(function (txp) {
|
||||||
|
txp.builder.utxos.forEach(function (utxo) {
|
||||||
|
inputs.push({ ntxid: txp.ntxid, txid: utxo.txid, vout: utxo.vout });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (inputs.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
var proposalsChanged = false;
|
||||||
|
this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
unspentList.forEach(function (unspent) {
|
||||||
|
inputs.forEach(function (input) {
|
||||||
|
input.unspent = input.unspent || (input.txid === unspent.txid && input.vout === unspent.vout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.forEach(function (input) {
|
||||||
|
if (!input.unspent) {
|
||||||
|
proposalsChanged = true;
|
||||||
|
self.txProposals.deleteOne(input.ntxid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (proposalsChanged) {
|
||||||
|
self.emit('txProposalsUpdated');
|
||||||
|
self.store();
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) {
|
Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,7 @@ Network.prototype.getCopayerIds = function() {
|
||||||
|
|
||||||
|
|
||||||
Network.prototype.send = function(dest, payload, cb) {
|
Network.prototype.send = function(dest, payload, cb) {
|
||||||
|
preconditions.checkState(this.socket);
|
||||||
preconditions.checkArgument(payload);
|
preconditions.checkArgument(payload);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
@ -357,7 +358,8 @@ Network.prototype.send = function(dest, payload, cb) {
|
||||||
var to = dest[ii];
|
var to = dest[ii];
|
||||||
if (to == this.copayerId)
|
if (to == this.copayerId)
|
||||||
continue;
|
continue;
|
||||||
log.debug('SEND to: ' + to, this.copayerId, payload);
|
|
||||||
|
log.debug('SEND to: ' + to, this.copayerId, JSON.stringify(payload));
|
||||||
|
|
||||||
var message = this.encode(to, payload);
|
var message = this.encode(to, payload);
|
||||||
this.socket.emit('message', message);
|
this.socket.emit('message', message);
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,8 @@ angular.module('copayApp.services')
|
||||||
if (!w) return root.onErrorDigest();
|
if (!w) return root.onErrorDigest();
|
||||||
if (!w.isReady()) return;
|
if (!w.isReady()) return;
|
||||||
|
|
||||||
|
w.removeTxWithSpentInputs();
|
||||||
|
|
||||||
$rootScope.balanceByAddr = {};
|
$rootScope.balanceByAddr = {};
|
||||||
$rootScope.updatingBalance = true;
|
$rootScope.updatingBalance = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,9 @@ FakeWallet.prototype.getBalance = function(cb) {
|
||||||
return cb(null, this.balance, this.balanceByAddr, this.safeBalance);
|
return cb(null, this.balance, this.balanceByAddr, this.safeBalance);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FakeWallet.prototype.removeTxWithSpentInputs = function (cb) {
|
||||||
|
};
|
||||||
|
|
||||||
FakeWallet.prototype.setEnc = function(enc) {
|
FakeWallet.prototype.setEnc = function(enc) {
|
||||||
this.enc = enc;
|
this.enc = enc;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,21 @@ describe('TxProposals', function() {
|
||||||
txps.getNtxids().should.deep.equal(['a','b']);
|
txps.getNtxids().should.deep.equal(['a','b']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#deleteOne', function() {
|
||||||
|
it('should delete specified ntxid', function() {
|
||||||
|
var txps = new TxProposals();
|
||||||
|
txps.txps = {a:1, b:2};
|
||||||
|
txps.deleteOne('a');
|
||||||
|
txps.getNtxids().should.deep.equal(['b']);
|
||||||
|
});
|
||||||
|
it('should fail on non-existent ntxid', function() {
|
||||||
|
var txps = new TxProposals();
|
||||||
|
txps.txps = {a:1, b:2};
|
||||||
|
(function () {
|
||||||
|
txps.deleteOne('c');
|
||||||
|
}).should.throw('Unknown TXP: c');
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('#toObj', function() {
|
describe('#toObj', function() {
|
||||||
it('should an object', function() {
|
it('should an object', function() {
|
||||||
var txps = TxProposals.fromObj({
|
var txps = TxProposals.fromObj({
|
||||||
|
|
|
||||||
|
|
@ -809,6 +809,79 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('removeTxWithSpentInputs', function () {
|
||||||
|
it('should remove pending TxProposal with spent inputs', function(done) {
|
||||||
|
var w = cachedCreateW2();
|
||||||
|
var utxo = createUTXO(w);
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(0);
|
||||||
|
w.blockchain.fixUnspent(utxo);
|
||||||
|
w.createTx(toAddress, amountSatStr, null, function(ntxid) {
|
||||||
|
w.sendTxProposal(ntxid);
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(1);
|
||||||
|
|
||||||
|
// Inputs are still available, txp still valid
|
||||||
|
w.removeTxWithSpentInputs();
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(1);
|
||||||
|
|
||||||
|
// Simulate input spent. txp should be removed from txps list
|
||||||
|
w.blockchain.fixUnspent([]);
|
||||||
|
w.removeTxWithSpentInputs();
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(0);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove pending TxProposal with at least 1 spent input', function(done) {
|
||||||
|
var w = cachedCreateW2();
|
||||||
|
var utxo = [createUTXO(w)[0], createUTXO(w)[0]];
|
||||||
|
utxo[0].amount = 80000;
|
||||||
|
utxo[1].amount = 80000;
|
||||||
|
utxo[1].vout = 1;
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(0);
|
||||||
|
w.blockchain.fixUnspent(utxo);
|
||||||
|
w.createTx(toAddress, '100000', null, function(ntxid) {
|
||||||
|
w.sendTxProposal(ntxid);
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(1);
|
||||||
|
|
||||||
|
// Inputs are still available, txp still valid
|
||||||
|
w.removeTxWithSpentInputs();
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(1);
|
||||||
|
|
||||||
|
// Simulate 1 input spent. txp should be removed from txps list
|
||||||
|
w.blockchain.fixUnspent([utxo[0]]);
|
||||||
|
w.removeTxWithSpentInputs();
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(0);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not remove complete TxProposal', function(done) {
|
||||||
|
var w = cachedCreateW2();
|
||||||
|
var utxo = createUTXO(w);
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(0);
|
||||||
|
w.blockchain.fixUnspent(utxo);
|
||||||
|
w.createTx(toAddress, amountSatStr, null, function(ntxid) {
|
||||||
|
w.sendTxProposal(ntxid);
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(1);
|
||||||
|
|
||||||
|
// Inputs are still available, txp still valid
|
||||||
|
w.removeTxWithSpentInputs();
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(1);
|
||||||
|
|
||||||
|
// Simulate input spent. txp should be removed from txps list
|
||||||
|
w.blockchain.fixUnspent([]);
|
||||||
|
var txp = w.txProposals.get(ntxid);
|
||||||
|
sinon.stub(txp, 'isPending', function () { return false; })
|
||||||
|
w.removeTxWithSpentInputs();
|
||||||
|
chai.expect(w.getTxProposals().length).to.equal(1);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#send', function() {
|
describe('#send', function() {
|
||||||
it('should call this.network.send', function() {
|
it('should call this.network.send', function() {
|
||||||
var w = cachedCreateW2();
|
var w = cachedCreateW2();
|
||||||
|
|
|
||||||
|
|
@ -7,30 +7,29 @@
|
||||||
|
|
||||||
<div class="large-12 medium-12" ng-if="!!(addresses|removeEmpty).length">
|
<div class="large-12 medium-12" ng-if="!!(addresses|removeEmpty).length">
|
||||||
<div class="large-12 medium-12" ng-init="showAll=0">
|
<div class="large-12 medium-12" ng-init="showAll=0">
|
||||||
<ul>
|
<div class="panel radius oh" ng-repeat="addr in addresses|removeEmpty|limitAddress:showAll" ng-click="openAddressModal(addr)">
|
||||||
<li class="panel radius oh" ng-repeat="addr in addresses|removeEmpty|limitAddress:showAll" ng-click="openAddressModal(addr)">
|
<div class="row collapse">
|
||||||
|
<div class="large-10 medium-9 small-8 column" >
|
||||||
<div class="large-10 medium-9 small-8 column" >
|
<div class="ellipsis list-addr">
|
||||||
<div class="ellipsis list-addr">
|
<i class="fi-thumbnails size-48 show-for-large-up"> </i>
|
||||||
<i class="fi-thumbnails size-48 show-for-large-up"> </i>
|
<span>
|
||||||
<span>
|
<contact address="{{addr.address}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/>
|
||||||
<contact address="{{addr.address}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/>
|
</span>
|
||||||
|
<span class="btn-copy" clip-copy="addr.address"> </span>
|
||||||
|
<small translate class="label" ng-if="addr.isChange">change</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="large-2 medium-3 small-4 column text-right">
|
||||||
|
<span ng-if="$root.updatingBalance">
|
||||||
|
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
|
||||||
|
</span>
|
||||||
|
<span class="size-12" ng-if="!$root.updatingBalance">
|
||||||
|
{{addr.balance || 0|noFractionNumber}} {{$root.unitName}}
|
||||||
</span>
|
</span>
|
||||||
<span class="btn-copy" clip-copy="addr.address"> </span>
|
|
||||||
<small translate class="label" ng-if="addr.isChange">change</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="large-2 medium-3 small-4 column text-right">
|
|
||||||
<span ng-if="$root.updatingBalance">
|
|
||||||
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
|
|
||||||
</span>
|
|
||||||
<span class="size-12" ng-if="!$root.updatingBalance">
|
|
||||||
{{addr.balance || 0|noFractionNumber}} {{$root.unitName}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|removeEmpty).length != (addresses|removeEmpty|limitAddress).length">
|
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|removeEmpty).length != (addresses|removeEmpty|limitAddress).length">
|
||||||
<span translate ng-if="!showAll">Show all</span>
|
<span translate ng-if="!showAll">Show all</span>
|
||||||
|
|
|
||||||
|
|
@ -6,36 +6,33 @@
|
||||||
</a>
|
</a>
|
||||||
<div ng-include="'views/includes/version.html'"></div>
|
<div ng-include="'views/includes/version.html'"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="line-sidebar-b"></div>
|
|
||||||
<div class="founds size-12 text-center box-founds p10t">
|
|
||||||
<div class="m10b">
|
|
||||||
Balance
|
|
||||||
<span ng-if="$root.updatingBalance">
|
|
||||||
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
|
|
||||||
</span>
|
|
||||||
<span ng-if="!$root.updatingBalance"
|
|
||||||
data-options="disable_for_touch:true"
|
|
||||||
tooltip="{{totalBalanceBTC |noFractionNumber:8}} BTC"
|
|
||||||
tooltip-trigger="mouseenter"
|
|
||||||
tooltip-placement="bottom">{{totalBalance || 0
|
|
||||||
|noFractionNumber}} {{$root.unitName}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Locked
|
|
||||||
<span ng-if="$root.updatingBalance">
|
|
||||||
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
|
|
||||||
</span>
|
|
||||||
<span ng-show="!$root.updatingBalance"
|
|
||||||
data-options="disable_for_touch:true"
|
|
||||||
tooltip="{{lockedBalanceBTC |noFractionNumber:8}} BTC"
|
|
||||||
tooltip-trigger="mouseenter"
|
|
||||||
tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}}
|
|
||||||
</span> <i class="fi-info medium" tooltip="Balance locked in pending transaction proposals" tooltip-placement="bottom"></i>
|
|
||||||
</div>
|
|
||||||
<div class="line-sidebar-b"></div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
<div class="line-sidebar-b"></div>
|
||||||
|
<div class="founds size-12 box-founds p15" ng-disabled="$root.loading" ng-click="refresh()">
|
||||||
|
<p class="text-gray">
|
||||||
|
<span>{{$root.wallet.getName()}}</span>
|
||||||
|
<span class="size-12 right">{{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}}</span>
|
||||||
|
</p>
|
||||||
|
<div class="line-sidebar-t">
|
||||||
|
Balance
|
||||||
|
<span class="gray small side-bar right" title="Manual Refresh"><i class="size-16 fi-refresh"></i></span>
|
||||||
|
<span ng-if="$root.updatingBalance">
|
||||||
|
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
|
||||||
|
</span>
|
||||||
|
<span ng-if="!$root.updatingBalance">{{totalBalance || 0
|
||||||
|
|noFractionNumber}} {{$root.unitName}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="m10t" ng-show="lockedBalance">
|
||||||
|
Locked
|
||||||
|
<span ng-if="$root.updatingBalance">
|
||||||
|
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
|
||||||
|
</span>
|
||||||
|
<span ng-show="!$root.updatingBalance">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}}
|
||||||
|
</span> <i class="fi-info medium" tooltip="Balance locked in pending transaction proposals" tooltip-placement="bottom"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="line-sidebar-t p0"></div>
|
||||||
<ul class="off-canvas-list">
|
<ul class="off-canvas-list">
|
||||||
<li data-ng-repeat="item in menu" ui-route="{{item.link}}" class="nav-item" data-ng-class="{active: isActive(item)}">
|
<li data-ng-repeat="item in menu" ui-route="{{item.link}}" class="nav-item" data-ng-class="{active: isActive(item)}">
|
||||||
<a href="#!/{{item.link}}" ng-click="toggleCollapse()" class="db p20h">
|
<a href="#!/{{item.link}}" ng-click="toggleCollapse()" class="db p20h">
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
tooltip-trigger="mouseenter"
|
tooltip-trigger="mouseenter"
|
||||||
tooltip-placement="bottom">{{totalBalance || 0 |noFractionNumber}} {{$root.unitName}}
|
tooltip-placement="bottom">{{totalBalance || 0 |noFractionNumber}} {{$root.unitName}}
|
||||||
</span>
|
</span>
|
||||||
<br>
|
<div class="m10t" ng-show="lockedBalance">
|
||||||
Locked
|
Locked
|
||||||
<span ng-if="$root.updatingBalance">
|
<span ng-if="$root.updatingBalance">
|
||||||
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
|
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
tooltip-trigger="mouseenter"
|
tooltip-trigger="mouseenter"
|
||||||
tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}}
|
tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}}
|
||||||
</span> <i class="fi-info medium" tooltip="Balance locked in pending transaction proposals" tooltip-placement="bottom"></i>
|
</span> <i class="fi-info medium" tooltip="Balance locked in pending transaction proposals" tooltip-placement="bottom"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="line-sidebar"></div>
|
<div class="line-sidebar"></div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@
|
||||||
<div ng-show="txs.length != 0" class="large-12 line-dashed" style="padding: 0;"></div>
|
<div ng-show="txs.length != 0" class="large-12 line-dashed" style="padding: 0;"></div>
|
||||||
|
|
||||||
<h1>{{title|translate}}</h1>
|
<h1>{{title|translate}}</h1>
|
||||||
|
<div class="row collapse m0">
|
||||||
<div class="large-6 columns">
|
<div class="large-6 columns">
|
||||||
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
|
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
|
||||||
<div class="row">
|
<div class="row collapse">
|
||||||
<div class="large-12 columns">
|
<div class="large-12 columns">
|
||||||
<div class="row collapse">
|
<div class="row collapse">
|
||||||
<label for="address"><span translate>To address</span>
|
<label for="address"><span translate>To address</span>
|
||||||
|
|
@ -52,8 +53,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row collapse">
|
||||||
<div class="large-6 medium-6 columns">
|
<div class="large-5 medium-5 columns">
|
||||||
<div class="row collapse">
|
<div class="row collapse">
|
||||||
<label for="amount"><span translate>Amount</span>
|
<label for="amount"><span translate>Amount</span>
|
||||||
<small translate ng-hide="!sendForm.amount.$pristine">required</small>
|
<small translate ng-hide="!sendForm.amount.$pristine">required</small>
|
||||||
|
|
@ -101,7 +102,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" ng-show="wallet.isShared()">
|
<div class="row collapse" ng-show="wallet.isShared()">
|
||||||
<div class="large-12 columns">
|
<div class="large-12 columns">
|
||||||
<div class="row collapse">
|
<div class="row collapse">
|
||||||
<label for="comment"><span translate>Notes</span>
|
<label for="comment"><span translate>Notes</span>
|
||||||
|
|
@ -116,8 +117,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row collapse">
|
||||||
<div class="large-5 medium-3 small-4 columns">
|
<div class="large-5 medium-6 small-12 columns">
|
||||||
<button translate type="submit" class="button primary expand text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending">
|
<button translate type="submit" class="button primary expand text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending">
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -125,6 +126,7 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div><!-- end of row -->
|
||||||
|
|
||||||
<div class="large-6 columns show-for-large-up" ng-show="!!$root.merchant">
|
<div class="large-6 columns show-for-large-up" ng-show="!!$root.merchant">
|
||||||
<div class="send-note">
|
<div class="send-note">
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,14 @@
|
||||||
No transactions yet.
|
No transactions yet.
|
||||||
</div>
|
</div>
|
||||||
<div class="last-transactions" ng-repeat="btx in blockchain_txs | orderBy: 'time':true">
|
<div class="last-transactions" ng-repeat="btx in blockchain_txs | orderBy: 'time':true">
|
||||||
<div class="last-transactions-header">
|
<div class="last-transactions-header size-14">
|
||||||
<div class="large-8 medium-7 small-3 columns ellipsis">
|
<div class="large-8 medium-7 small-4 columns ellipsis">
|
||||||
<a href="http://{{getShortNetworkName()}}.insight.is/tx/{{btx.txid}}" target="_blank">
|
<a href="http://{{getShortNetworkName()}}.insight.is/tx/{{btx.txid}}" target="_blank">
|
||||||
{{btx.txid}}
|
{{btx.txid}}
|
||||||
</a>
|
</a>
|
||||||
<span class="btn-copy" clip-copy="btx.txid"></span>
|
<span class="btn-copy" clip-copy="btx.txid"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="large-4 medium-5 small-9 columns text-right">
|
<div class="large-4 medium-5 small-8 columns text-right">
|
||||||
<div data-ng-show="btx.firstSeenTs">
|
<div data-ng-show="btx.firstSeenTs">
|
||||||
<span translate>first seen at</span>
|
<span translate>first seen at</span>
|
||||||
<time>{{btx.firstSeenTs * 1000 | amCalendar}}</time>
|
<time>{{btx.firstSeenTs * 1000 | amCalendar}}</time>
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="last-transactions-content">
|
<div class="last-transactions-content">
|
||||||
<div class="large-5 medium-5 small-5 columns">
|
<div class="large-5 medium-5 small-12 columns">
|
||||||
<div ng-repeat="vin in btx.vinSimple">
|
<div ng-repeat="vin in btx.vinSimple">
|
||||||
<small class="right m5t">{{vin.value| noFractionNumber}} {{$root.unitName}}</small>
|
<small class="right m5t">{{vin.value| noFractionNumber}} {{$root.unitName}}</small>
|
||||||
<p class="ellipsis text-gray size-12">
|
<p class="ellipsis text-gray size-12">
|
||||||
|
|
@ -58,10 +58,13 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="large-1 medium-1 small-1 columns text-center">
|
<div class="large-1 medium-1 hide-for-small-only columns text-center">
|
||||||
<i class="fi-arrow-right"></i>
|
<i class="fi-arrow-right"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="large-6 medium-6 small-6 columns">
|
<div class="show-for-small-only small-12 columns text-center">
|
||||||
|
<i class="fi-arrow-down"></i>
|
||||||
|
</div>
|
||||||
|
<div class="large-6 medium-6 small-12 columns">
|
||||||
<div ng-repeat="vout in btx.voutSimple">
|
<div ng-repeat="vout in btx.voutSimple">
|
||||||
<small class="right m5t">{{vout.value| noFractionNumber}} {{$root.unitName}}</small>
|
<small class="right m5t">{{vout.value| noFractionNumber}} {{$root.unitName}}</small>
|
||||||
<p class="ellipsis text-gray size-12">
|
<p class="ellipsis text-gray size-12">
|
||||||
|
|
@ -71,12 +74,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="last-transactions-footer">
|
<div class="last-transactions-footer">
|
||||||
<div class="large-4 medium-4 small-4 columns">
|
<div class="large-6 medium-6 small-6 columns">
|
||||||
<span translate>Fee</span>: {{btx.fees | noFractionNumber}} {{$root.unitName}}</div>
|
<p class="size-12"><span translate>Fee</span>: {{btx.fees | noFractionNumber}} {{$root.unitName}}</p>
|
||||||
<div class="large-4 medium-4 small-4 columns text-center">
|
<p class="size-12"><span translate>Confirmations</span>: {{btx.confirmations || 0}}</p>
|
||||||
<span translate>Confirmations</span>: {{btx.confirmations || 0}}</div>
|
</div>
|
||||||
<div class="large-4 medium-4 small-4 columns text-right">
|
<div class="large-6 medium-6 small-6 columns text-right">
|
||||||
<span translate>Total</span>: {{btx.valueOut| noFractionNumber}} {{$root.unitName}}</div>
|
<p class="label size-14"><span translate>Total</span>: {{btx.valueOut| noFractionNumber}} {{$root.unitName}}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue