Merge pull request #1846 from matiu/bug/paypro-broadcast
Bug/paypro broadcast
This commit is contained in:
commit
b1b6261b2d
14 changed files with 248 additions and 175 deletions
|
|
@ -124,8 +124,7 @@ module.exports = function(grunt) {
|
||||||
'lib/socket.io-client/socket.io.js',
|
'lib/socket.io-client/socket.io.js',
|
||||||
'lib/sjcl.js',
|
'lib/sjcl.js',
|
||||||
'lib/ios-imagefile-megapixel/megapix-image.js',
|
'lib/ios-imagefile-megapixel/megapix-image.js',
|
||||||
'lib/qrcode-decoder-js/lib/qrcode-decoder.min.js',
|
'lib/qrcode-decoder-js/lib/qrcode-decoder.min.js'
|
||||||
'lib/zeroclipboard/ZeroClipboard.min.js'
|
|
||||||
],
|
],
|
||||||
dest: 'lib/vendors.js'
|
dest: 'lib/vendors.js'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@
|
||||||
"angular-moment": "~0.7.1",
|
"angular-moment": "~0.7.1",
|
||||||
"socket.io-client": ">=1.0.0",
|
"socket.io-client": ">=1.0.0",
|
||||||
"mousetrap": "1.4.6",
|
"mousetrap": "1.4.6",
|
||||||
"zeroclipboard": "~1.3.5",
|
|
||||||
"ng-idle": "*",
|
"ng-idle": "*",
|
||||||
"inherits": "~0.0.1",
|
"inherits": "~0.0.1",
|
||||||
"angular-load": "0.2.0",
|
"angular-load": "0.2.0",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ var bitcore = require('bitcore');
|
||||||
var preconditions = require('preconditions').singleton();
|
var preconditions = require('preconditions').singleton();
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('SendController',
|
angular.module('copayApp.controllers').controller('SendController',
|
||||||
function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification, controllerUtils, rateService) {
|
function($scope, $rootScope, $window, $timeout, $modal, isMobile, notification, controllerUtils, rateService) {
|
||||||
|
|
||||||
controllerUtils.redirIfNotComplete();
|
controllerUtils.redirIfNotComplete();
|
||||||
|
|
||||||
|
|
@ -13,6 +13,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
$rootScope.title = 'Send';
|
$rootScope.title = 'Send';
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
$scope.error = $scope.success = null;
|
||||||
var satToUnit = 1 / w.settings.unitToSatoshi;
|
var satToUnit = 1 / w.settings.unitToSatoshi;
|
||||||
$scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit;
|
$scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit;
|
||||||
$scope.unitToBtc = w.settings.unitToSatoshi / bitcore.util.COIN;
|
$scope.unitToBtc = w.settings.unitToSatoshi / bitcore.util.COIN;
|
||||||
|
|
@ -145,15 +146,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
if (w.requiresMultipleSignatures()) {
|
if (w.requiresMultipleSignatures()) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
var message = 'The transaction proposal has been created';
|
notification.success('Success', 'The transaction proposal created');
|
||||||
if (merchantData) {
|
|
||||||
if (merchantData.pr.ca) {
|
|
||||||
message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.';
|
|
||||||
}
|
|
||||||
message += ' Message from server: ' + merchantData.pr.pd.memo;
|
|
||||||
message += ' For merchant: ' + merchantData.pr.pd.payment_url;
|
|
||||||
}
|
|
||||||
notification.success('Success', message);
|
|
||||||
$scope.loadTxs();
|
$scope.loadTxs();
|
||||||
} else {
|
} else {
|
||||||
w.sendTx(ntxid, function(txid, merchantData) {
|
w.sendTx(ntxid, function(txid, merchantData) {
|
||||||
|
|
@ -163,10 +156,10 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
if (merchantData.pr.ca) {
|
if (merchantData.pr.ca) {
|
||||||
message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.';
|
message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.';
|
||||||
}
|
}
|
||||||
message += ' Message from server: ' + merchantData.pr.pd.memo;
|
message += merchantData.pr.pd.memo;
|
||||||
message += ' For merchant: ' + merchantData.pr.pd.payment_url;
|
message += ' Merchant: ' + merchantData.pr.pd.payment_url;
|
||||||
}
|
}
|
||||||
notification.success('Transaction broadcasted', message);
|
$scope.success = 'Transaction broadcasted' + message;
|
||||||
} else {
|
} else {
|
||||||
$scope.error = 'There was an error sending the transaction';
|
$scope.error = 'There was an error sending the transaction';
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +345,6 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
$scope.copyAddress = function(address) {
|
$scope.copyAddress = function(address) {
|
||||||
$scope.address = address;
|
$scope.address = address;
|
||||||
$anchorScroll();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.openAddressBookModal = function() {
|
$scope.openAddressBookModal = function() {
|
||||||
|
|
@ -363,7 +355,6 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
$scope.submitAddressBook = function(form) {
|
$scope.submitAddressBook = function(form) {
|
||||||
if (form.$invalid) {
|
if (form.$invalid) {
|
||||||
notification.error('Form Error', 'Please complete required fields');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var entry = {
|
var entry = {
|
||||||
|
|
@ -391,6 +382,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
errorMsg = e.message;
|
errorMsg = e.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO change this notifications
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
notification.error('Error', errorMsg);
|
notification.error('Error', errorMsg);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -409,14 +401,15 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
|
|
||||||
$scope.send = function(ntxid, cb) {
|
$scope.send = function(ntxid, cb) {
|
||||||
|
$scope.error = $scope.success = null;
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
$rootScope.txAlertCount = 0;
|
$rootScope.txAlertCount = 0;
|
||||||
w.sendTx(ntxid, function(txid, merchantData) {
|
w.sendTx(ntxid, function(txid, merchantData) {
|
||||||
if (!txid) {
|
if (!txid) {
|
||||||
$scope.error = 'There was an error sending the transaction';
|
notification.error('Error', 'There was an error sending the transaction');
|
||||||
} else {
|
} else {
|
||||||
if (!merchantData) {
|
if (!merchantData) {
|
||||||
notification.success('Transaction broadcasted', 'Transaction id: ' + txid);
|
notification.success('Success', 'Transaction broadcasted!');
|
||||||
} else {
|
} else {
|
||||||
var message = 'Transaction ID: ' + txid;
|
var message = 'Transaction ID: ' + txid;
|
||||||
if (merchantData.pr.ca) {
|
if (merchantData.pr.ca) {
|
||||||
|
|
@ -424,7 +417,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
}
|
}
|
||||||
message += ' Message from server: ' + merchantData.ack.memo;
|
message += ' Message from server: ' + merchantData.ack.memo;
|
||||||
message += ' For merchant: ' + merchantData.pr.pd.payment_url;
|
message += ' For merchant: ' + merchantData.pr.pd.payment_url;
|
||||||
notification.success('Transaction sent', message);
|
notification.success('Success', 'Transaction sent' + message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,15 +428,15 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
$scope.sign = function(ntxid) {
|
$scope.sign = function(ntxid) {
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
|
$scope.error = $scope.success = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
w.sign(ntxid);
|
w.sign(ntxid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$scope.error = 'There was an error signing the transaction';
|
notification.error('Error','There was an error signing the transaction');
|
||||||
$scope.loadTxs();
|
$scope.loadTxs();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$scope.error = undefined;
|
|
||||||
|
|
||||||
var p = w.txProposals.getTxProposal(ntxid);
|
var p = w.txProposals.getTxProposal(ntxid);
|
||||||
if (p.builder.isFullySigned()) {
|
if (p.builder.isFullySigned()) {
|
||||||
|
|
@ -465,16 +458,15 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.clearMerchant = function(callback) {
|
$scope.clearMerchant = function(callback) {
|
||||||
var scope = $scope;
|
|
||||||
// TODO: Find a better way of detecting
|
// TODO: Find a better way of detecting
|
||||||
// whether we're in the Send scope or not.
|
// whether we're in the Send scope or not.
|
||||||
if (!scope.sendForm || !scope.sendForm.address) {
|
if (!$scope.sendForm || !$scope.sendForm.address) {
|
||||||
delete $rootScope.merchant;
|
delete $rootScope.merchant;
|
||||||
$rootScope.merchantError = false;
|
$rootScope.merchantError = false;
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var val = scope.sendForm.address.$viewValue || '';
|
var val = $scope.sendForm.address.$viewValue || '';
|
||||||
var uri;
|
var uri;
|
||||||
// If we're setting the domain, ignore the change.
|
// If we're setting the domain, ignore the change.
|
||||||
if ($rootScope.merchant && $rootScope.merchant.domain && val === $rootScope.merchant.domain) {
|
if ($rootScope.merchant && $rootScope.merchant.domain && val === $rootScope.merchant.domain) {
|
||||||
|
|
@ -491,8 +483,8 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
}
|
}
|
||||||
if (!uri || !uri.merchant) {
|
if (!uri || !uri.merchant) {
|
||||||
delete $rootScope.merchant;
|
delete $rootScope.merchant;
|
||||||
scope.sendForm.amount.$setViewValue('');
|
$scope.sendForm.amount.$setViewValue('');
|
||||||
scope.sendForm.amount.$render();
|
$scope.sendForm.amount.$render();
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') {
|
if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') {
|
||||||
$rootScope.$apply();
|
$rootScope.$apply();
|
||||||
|
|
@ -501,10 +493,10 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.onChanged = function() {
|
$scope.onChanged = function() {
|
||||||
var scope = $scope;
|
var value = $scope.address || '';
|
||||||
var value = scope.address || '';
|
|
||||||
var uri;
|
var uri;
|
||||||
|
|
||||||
|
$scope.error = $scope.success = null;
|
||||||
// If we're setting the domain, ignore the change.
|
// If we're setting the domain, ignore the change.
|
||||||
if ($rootScope.merchant && $rootScope.merchant.domain && value === $rootScope.merchant.domain) {
|
if ($rootScope.merchant && $rootScope.merchant.domain && value === $rootScope.merchant.domain) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -528,28 +520,28 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
notification.info('Fetching Payment',
|
$scope.fetchingURL = uri.merchant;
|
||||||
'Retrieving Payment Request from ' + uri.merchant);
|
$scope.loading = true;
|
||||||
|
|
||||||
scope.loading = true;
|
|
||||||
apply();
|
apply();
|
||||||
|
|
||||||
var timeout = setTimeout(function() {
|
var timeout = setTimeout(function() {
|
||||||
timeout = null;
|
timeout = null;
|
||||||
scope.loading = false;
|
$scope.fetchingURL = null;
|
||||||
scope.sendForm.address.$setViewValue('');
|
$scope.loading = false;
|
||||||
scope.sendForm.address.$render();
|
$scope.sendForm.address.$setViewValue('');
|
||||||
scope.sendForm.address.$isValid = false;
|
$scope.sendForm.address.$render();
|
||||||
notification.error('Error', 'Payment server timed out.');
|
$scope.sendForm.address.$isValid = false;
|
||||||
|
$scope.error = 'Payment server timed out';
|
||||||
apply();
|
apply();
|
||||||
}, 10 * 1000);
|
}, 10 * 1000);
|
||||||
|
|
||||||
// Payment Protocol URI (BIP-72)
|
// Payment Protocol URI (BIP-72)
|
||||||
scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) {
|
$scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) {
|
||||||
if (!timeout) return;
|
if (!timeout) return;
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
scope.loading = false;
|
$scope.loading = false;
|
||||||
|
$scope.fetchingURL = null;
|
||||||
apply();
|
apply();
|
||||||
|
|
||||||
var balance = $rootScope.availableBalance;
|
var balance = $rootScope.availableBalance;
|
||||||
|
|
@ -561,28 +553,29 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.amount) {
|
if (err.amount) {
|
||||||
scope.sendForm.amount.$setViewValue(+err.amount / w.settings.unitToSatoshi);
|
$scope.sendForm.amount.$setViewValue(+err.amount / w.settings.unitToSatoshi);
|
||||||
scope.sendForm.amount.$render();
|
$scope.sendForm.amount.$render();
|
||||||
scope.sendForm.amount.$isValid = false;
|
$scope.sendForm.amount.$isValid = false;
|
||||||
scope.notEnoughAmount = true;
|
$scope.notEnoughAmount = true;
|
||||||
$rootScope.merchantError = true;
|
$rootScope.merchantError = true;
|
||||||
var lastAddr = scope.sendForm.address.$viewValue;
|
var lastAddr = $scope.sendForm.address.$viewValue;
|
||||||
var unregister = scope.$watch('address', function() {
|
var unregister = $scope.$watch('address', function() {
|
||||||
if (scope.sendForm.address.$viewValue !== lastAddr) {
|
if ($scope.sendForm.address.$viewValue !== lastAddr) {
|
||||||
delete $rootScope.merchantError;
|
delete $rootScope.merchantError;
|
||||||
scope.sendForm.amount.$setViewValue('');
|
$scope.sendForm.amount.$setViewValue('');
|
||||||
scope.sendForm.amount.$render();
|
$scope.sendForm.amount.$render();
|
||||||
unregister();
|
unregister();
|
||||||
apply();
|
apply();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
scope.sendForm.address.$setViewValue('');
|
$scope.sendForm.address.$setViewValue('');
|
||||||
scope.sendForm.address.$render();
|
$scope.sendForm.address.$render();
|
||||||
}
|
}
|
||||||
scope.sendForm.address.$isValid = false;
|
$scope.sendForm.address.$isValid = false;
|
||||||
|
copay.logger.error(err);
|
||||||
|
|
||||||
notification.error('Error', err.message || 'Bad payment server.');
|
$scope.error = 'Could not fetch payment request';
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
return;
|
return;
|
||||||
|
|
@ -593,18 +586,18 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
merchantData.unitTotal = (+merchantData.total / w.settings.unitToSatoshi) + '';
|
merchantData.unitTotal = (+merchantData.total / w.settings.unitToSatoshi) + '';
|
||||||
merchantData.expiration = new Date(
|
merchantData.expiration = new Date(
|
||||||
merchantData.pr.pd.expires * 1000).toISOString();
|
merchantData.pr.pd.expires * 1000);
|
||||||
merchantData.domain = domain;
|
merchantData.domain = domain;
|
||||||
|
|
||||||
$rootScope.merchant = merchantData;
|
$rootScope.merchant = merchantData;
|
||||||
|
|
||||||
scope.sendForm.address.$setViewValue(domain);
|
$scope.sendForm.address.$setViewValue(domain);
|
||||||
scope.sendForm.address.$render();
|
$scope.sendForm.address.$render();
|
||||||
scope.sendForm.address.$isValid = true;
|
$scope.sendForm.address.$isValid = true;
|
||||||
|
|
||||||
scope.sendForm.amount.$setViewValue(merchantData.unitTotal);
|
$scope.sendForm.amount.$setViewValue(merchantData.unitTotal);
|
||||||
scope.sendForm.amount.$render();
|
$scope.sendForm.amount.$render();
|
||||||
scope.sendForm.amount.$isValid = true;
|
$scope.sendForm.amount.$isValid = true;
|
||||||
|
|
||||||
// If the address changes to a non-payment-protocol one,
|
// If the address changes to a non-payment-protocol one,
|
||||||
// delete the `merchant` property from the scope.
|
// delete the `merchant` property from the scope.
|
||||||
|
|
@ -613,11 +606,6 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
});
|
});
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
|
|
||||||
notification.info('Payment Request',
|
|
||||||
'Server is requesting ' + merchantData.unitTotal +
|
|
||||||
' ' + w.settings.unitName +
|
|
||||||
'.' + ' Message: ' + merchantData.pr.pd.memo);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,24 @@ var Address = bitcore.Address;
|
||||||
var bignum = bitcore.Bignum;
|
var bignum = bitcore.Bignum;
|
||||||
var preconditions = require('preconditions').singleton();
|
var preconditions = require('preconditions').singleton();
|
||||||
|
|
||||||
|
|
||||||
|
function selectText(element) {
|
||||||
|
var doc = document;
|
||||||
|
if (doc.body.createTextRange) { // ms
|
||||||
|
var range = doc.body.createTextRange();
|
||||||
|
range.moveToElementText(element);
|
||||||
|
range.select();
|
||||||
|
} else if (window.getSelection) {
|
||||||
|
var selection = window.getSelection();
|
||||||
|
var range = doc.createRange();
|
||||||
|
range.selectNodeContents(element);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
angular.module('copayApp.directives')
|
angular.module('copayApp.directives')
|
||||||
|
|
||||||
.directive('validAddress', ['$rootScope',
|
.directive('validAddress', ['$rootScope',
|
||||||
|
|
@ -162,10 +180,14 @@ angular.module('copayApp.directives')
|
||||||
var contact = scope.wallet.addressBook[address];
|
var contact = scope.wallet.addressBook[address];
|
||||||
if (contact && !contact.hidden) {
|
if (contact && !contact.hidden) {
|
||||||
element.append(contact.label);
|
element.append(contact.label);
|
||||||
attrs['tooltip'] = attrs.address;
|
element.attr('tooltip',attrs.address);
|
||||||
} else {
|
} else {
|
||||||
element.append(address);
|
element.append(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
element.bind('click', function() {
|
||||||
|
selectText(element[0]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
@ -296,39 +318,19 @@ angular.module('copayApp.directives')
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.directive('clipCopy', function() {
|
.directive('clipCopy', function() {
|
||||||
ZeroClipboard.config({
|
|
||||||
moviePath: './lib/zeroclipboard/ZeroClipboard.swf',
|
|
||||||
trustedDomains: ['*'],
|
|
||||||
allowScriptAccess: 'always',
|
|
||||||
forceHandCursor: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
restric: 'A',
|
restric: 'A',
|
||||||
scope: {
|
scope: {
|
||||||
clipCopy: '=clipCopy'
|
clipCopy: '=clipCopy'
|
||||||
},
|
},
|
||||||
link: function(scope, elm) {
|
link: function(scope, elm) {
|
||||||
var client = new ZeroClipboard(elm);
|
// TODO this does not work (FIXME)
|
||||||
|
elm.attr('tooltip','Press Ctrl+C to Copy');
|
||||||
|
elm.attr('tooltip-placement','top');
|
||||||
|
|
||||||
client.on('load', function(client) {
|
elm.bind('click', function() {
|
||||||
|
selectText(elm[0]);
|
||||||
client.on('datarequested', function(client) {
|
|
||||||
client.setText(scope.clipCopy);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('complete', function(client, args) {
|
|
||||||
elm.removeClass('btn-copy').addClass('btn-copied').html('Copied!');
|
|
||||||
setTimeout(function() {
|
|
||||||
elm.addClass('btn-copy').removeClass('btn-copied').html('');
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
client.on('wrongflash noflash', function() {
|
|
||||||
elm.removeClass('btn-copy').html('');
|
|
||||||
ZeroClipboard.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -256,8 +256,8 @@ Insight.prototype.broadcast = function(rawtx, cb) {
|
||||||
this.requestPost('/api/tx/send', {
|
this.requestPost('/api/tx/send', {
|
||||||
rawtx: rawtx
|
rawtx: rawtx
|
||||||
}, function(err, res, body) {
|
}, function(err, res, body) {
|
||||||
if (err || res.statusCode != 200) cb(err || body);
|
if (err || res.statusCode != 200) return cb(err || body);
|
||||||
cb(null, body ? body.txid : null);
|
return cb(null, body ? body.txid : null);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ var buffertools = bitcore.buffertools;
|
||||||
var preconditions = require('preconditions').instance();
|
var preconditions = require('preconditions').instance();
|
||||||
|
|
||||||
var VERSION = 1;
|
var VERSION = 1;
|
||||||
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment'];
|
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment', 'paymentProtocolURL'];
|
||||||
|
|
||||||
|
|
||||||
function TxProposal(opts) {
|
function TxProposal(opts) {
|
||||||
|
|
@ -37,6 +37,7 @@ function TxProposal(opts) {
|
||||||
this.comment = opts.comment || null;
|
this.comment = opts.comment || null;
|
||||||
this.readonly = opts.readonly || null;
|
this.readonly = opts.readonly || null;
|
||||||
this.merchant = opts.merchant || null;
|
this.merchant = opts.merchant || null;
|
||||||
|
this.paymentProtocolURL = opts.paymentProtocolURL || null;
|
||||||
this._sync();
|
this._sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -426,14 +426,79 @@ Wallet.prototype._getKeyMap = function(txp) {
|
||||||
Wallet.prototype._checkSentTx = function(ntxid, cb) {
|
Wallet.prototype._checkSentTx = function(ntxid, cb) {
|
||||||
var txp = this.txProposals.get(ntxid);
|
var txp = this.txProposals.get(ntxid);
|
||||||
var tx = txp.builder.build();
|
var tx = txp.builder.build();
|
||||||
var txid = bitcore.util.formatHashFull(tx.getHash());
|
|
||||||
|
|
||||||
|
var txHex = tx.serialize().toString('hex');
|
||||||
|
|
||||||
|
//Use calcHash NOT getHash which could be cached.
|
||||||
|
var txid = bitcore.util.formatHashFull(tx.calcHash());
|
||||||
this.blockchain.getTransaction(txid, function(err, tx) {
|
this.blockchain.getTransaction(txid, function(err, tx) {
|
||||||
if (err) return cb(false);
|
if (err) return cb(false);
|
||||||
return cb(txid);
|
return cb(txid);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wallet.prototype._processTxProposalSeen = function(ntxid) {
|
||||||
|
var txp = this.txProposals.get(ntxid);
|
||||||
|
if (!txp.getSeen(this.getMyCopayerId())) {
|
||||||
|
txp.setSeen(this.getMyCopayerId());
|
||||||
|
this.sendSeen(ntxid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Wallet.prototype._processTxProposalSent = function(ntxid, cb) {
|
||||||
|
var self = this;
|
||||||
|
var txp = this.txProposals.get(ntxid);
|
||||||
|
|
||||||
|
this._checkSentTx(ntxid, function(txid) {
|
||||||
|
if (txid) {
|
||||||
|
if (!txp.getSent()) {
|
||||||
|
txp.setSent(txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.emitAndKeepAlive('txProposalsUpdated');
|
||||||
|
if (cb) return cb(null, txid);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
|
||||||
|
var self = this;
|
||||||
|
var txp = mergeInfo.txp;
|
||||||
|
var isNew = mergeInfo.new;
|
||||||
|
var ntxid = mergeInfo.ntxid;
|
||||||
|
|
||||||
|
if (!isNew || !txp.paymentProtocolURL)
|
||||||
|
return cb();
|
||||||
|
|
||||||
|
log.info('Received a Payment Protocol TX Proposal');
|
||||||
|
self.fetchPaymentTx(txp.paymentProtocolURL, function(err, merchantData) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
txp.merchant = merchantData;
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) {
|
||||||
|
if (!mergeInfo) return cb();
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self._processTxProposalPayPro(mergeInfo, function(err) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
self._processTxProposalSeen(mergeInfo.ntxid);
|
||||||
|
|
||||||
|
var tx = mergeInfo.txp.builder.build();
|
||||||
|
if (tx.isComplete())
|
||||||
|
self._processTxProposalSent(mergeInfo.ntxid);
|
||||||
|
else if (mergeInfo.hasChanged) {
|
||||||
|
self.sendTxProposal(mergeInfo.ntxid);
|
||||||
|
self.emitAndKeepAlive('txProposalsUpdated');
|
||||||
|
}
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc
|
* @desc
|
||||||
* Handles a 'TXPROPOSAL' network message
|
* Handles a 'TXPROPOSAL' network message
|
||||||
|
|
@ -454,34 +519,20 @@ Wallet.prototype._onTxProposal = function(senderId, data) {
|
||||||
m.newCopayer = m.txp.setCopayers(senderId, keyMap);
|
m.newCopayer = m.txp.setCopayers(senderId, keyMap);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('Corrupt TX proposal received from:', senderId, e.toString());
|
log.error('Corrupt TX proposal received from:', senderId, e.toString());
|
||||||
|
if (m && m.ntxid)
|
||||||
|
this.txProposals.deleteOne(m.ntxid);
|
||||||
m = null;
|
m = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m) {
|
self._processIncomingTxProposal(m, function(err) {
|
||||||
if (!m.txp.getSeen(this.getMyCopayerId())) {
|
if (err) {
|
||||||
m.txp.setSeen(this.getMyCopayerId());
|
log.error('Corrupt TX proposal received from:', senderId, err.toString());
|
||||||
this.sendSeen(m.ntxid);
|
if (m && m.ntxid)
|
||||||
}
|
self.txProposals.deleteOne(m.ntxid);
|
||||||
|
m = null;
|
||||||
var tx = m.txp.builder.build();
|
|
||||||
if (tx.isComplete()) {
|
|
||||||
this._checkSentTx(m.ntxid, function(txid) {
|
|
||||||
if (txid) {
|
|
||||||
if (!m.txp.getSent()) {
|
|
||||||
m.txp.setSent(txid);
|
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self._processProposalEvents(senderId, m);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
if (m.hasChanged) {
|
|
||||||
this.sendTxProposal(m.ntxid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emitAndKeepAlive('txProposalsUpdated');
|
|
||||||
}
|
|
||||||
this._processProposalEvents(senderId, m);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1420,16 +1471,18 @@ Wallet.prototype.sign = function(ntxid) {
|
||||||
Wallet.prototype.sendTx = function(ntxid, cb) {
|
Wallet.prototype.sendTx = function(ntxid, cb) {
|
||||||
var txp = this.txProposals.get(ntxid);
|
var txp = this.txProposals.get(ntxid);
|
||||||
|
|
||||||
if (txp.merchant) {
|
|
||||||
return this.sendPaymentTx(ntxid, cb);
|
|
||||||
}
|
|
||||||
var tx = txp.builder.build();
|
var tx = txp.builder.build();
|
||||||
if (!tx.isComplete())
|
if (!tx.isComplete())
|
||||||
throw new Error('Tx is not complete. Can not broadcast');
|
throw new Error('Tx is not complete. Can not broadcast');
|
||||||
|
|
||||||
log.debug('Wallet:' + this.id + ' Broadcasting Transaction');
|
log.debug('Wallet:' + this.id + ' Broadcasting Transaction');
|
||||||
|
|
||||||
|
if (txp.merchant) {
|
||||||
|
return this.sendPaymentTx(ntxid, cb);
|
||||||
|
}
|
||||||
|
|
||||||
var scriptSig = tx.ins[0].getScript();
|
var scriptSig = tx.ins[0].getScript();
|
||||||
var size = scriptSig.serialize().length;
|
var size = scriptSig.serialize().length;
|
||||||
|
|
||||||
var txHex = tx.serialize().toString('hex');
|
var txHex = tx.serialize().toString('hex');
|
||||||
log.debug('Wallet:' + this.id + ' Raw transaction: ', txHex);
|
log.debug('Wallet:' + this.id + ' Raw transaction: ', txHex);
|
||||||
|
|
||||||
|
|
@ -1446,10 +1499,7 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
|
||||||
return cb(txid);
|
return cb(txid);
|
||||||
} else {
|
} else {
|
||||||
log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already');
|
log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already');
|
||||||
self._checkSentTx(ntxid, function(txid) {
|
self._processTxProposalSent(ntxid, function(err, txid) {
|
||||||
if (txid)
|
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
|
||||||
|
|
||||||
return cb(txid);
|
return cb(txid);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1640,6 +1690,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) {
|
||||||
if (!unspent || !unspent.length) {
|
if (!unspent || !unspent.length) {
|
||||||
return cb(new Error('No unspent outputs available.'));
|
return cb(new Error('No unspent outputs available.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
self.createPaymentTxSync(options, merchantData, safeUnspent);
|
self.createPaymentTxSync(options, merchantData, safeUnspent);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -1765,7 +1816,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
|
||||||
view[i] = pay[i];
|
view[i] = pay[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Wallet.request({
|
var postInfo = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: txp.merchant.pr.pd.payment_url,
|
url: txp.merchant.pr.pd.payment_url,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -1780,7 +1831,9 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
|
||||||
// be the ArrayBuffer, now you send the View instead).
|
// be the ArrayBuffer, now you send the View instead).
|
||||||
data: view,
|
data: view,
|
||||||
responseType: 'arraybuffer'
|
responseType: 'arraybuffer'
|
||||||
})
|
};
|
||||||
|
|
||||||
|
return Wallet.request(postInfo)
|
||||||
.success(function(data, status, headers, config) {
|
.success(function(data, status, headers, config) {
|
||||||
data = PayPro.PaymentACK.decode(data);
|
data = PayPro.PaymentACK.decode(data);
|
||||||
var ack = new PayPro();
|
var ack = new PayPro();
|
||||||
|
|
@ -1790,9 +1843,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
|
||||||
.error(function(data, status, headers, config) {
|
.error(function(data, status, headers, config) {
|
||||||
log.debug('Sending to server was not met with a returned tx.');
|
log.debug('Sending to server was not met with a returned tx.');
|
||||||
log.debug('XHR status: ' + status);
|
log.debug('XHR status: ' + status);
|
||||||
return self._checkSentTx(ntxid, function(txid) {
|
self._processTxProposalSent(ntxid, function(err, txid) {
|
||||||
if (txid)
|
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
|
||||||
return cb(txid, txp.merchant);
|
return cb(txid, txp.merchant);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -1822,10 +1873,8 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) {
|
||||||
tx = payment.message.transactions[0];
|
tx = payment.message.transactions[0];
|
||||||
if (!tx) {
|
if (!tx) {
|
||||||
log.debug('Sending to server was not met with a returned tx.');
|
log.debug('Sending to server was not met with a returned tx.');
|
||||||
return this._checkSentTx(ntxid, function(txid) {
|
return this._processTxProposalSeen(ntxid, function(err, txid) {
|
||||||
log.debug('[Wallet.js.1613:txid:%s]', txid);
|
log.debug('[Wallet.js.1613:txid:%s]', txid);
|
||||||
if (txid)
|
|
||||||
self.emitAndKeepAlive('txProposalUpdated');
|
|
||||||
return cb(txid, txp.merchant);
|
return cb(txid, txp.merchant);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1841,7 +1890,7 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) {
|
||||||
log.debug('It is not returning a copy of the transaction.');
|
log.debug('It is not returning a copy of the transaction.');
|
||||||
}
|
}
|
||||||
|
|
||||||
var txid = tx.getHash().toString('hex');
|
var txid = tx.calcHash().toString('hex');
|
||||||
var txHex = tx.serialize().toString('hex');
|
var txHex = tx.serialize().toString('hex');
|
||||||
log.debug('Raw transaction: ', txHex);
|
log.debug('Raw transaction: ', txHex);
|
||||||
|
|
||||||
|
|
@ -1855,11 +1904,8 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) {
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
self.emitAndKeepAlive('txProposalsUpdated');
|
||||||
return cb(txid, txp.merchant);
|
return cb(txid, txp.merchant);
|
||||||
} else {
|
} else {
|
||||||
log.debug('Sent failed. Checking if the TX was sent already');
|
log.debug('PayPro Sent failed. Checking if the TX was sent already');
|
||||||
self._checkSentTx(ntxid, function(txid) {
|
self._processTxProposalSent(ntxid, function(err, txid) {
|
||||||
if (txid)
|
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
|
||||||
|
|
||||||
return cb(txid, txp.merchant);
|
return cb(txid, txp.merchant);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1951,6 +1997,10 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent)
|
||||||
});
|
});
|
||||||
|
|
||||||
var selectedUtxos = b.getSelectedUnspent();
|
var selectedUtxos = b.getSelectedUnspent();
|
||||||
|
if (selectedUtxos.length > TX_MAX_INS)
|
||||||
|
throw new Error('BIG: Resulting TX is too big:' + selectedUtxos.length + ' inputs. Aborting');
|
||||||
|
|
||||||
|
|
||||||
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
||||||
return pkr.pathForAddress(utxo.address);
|
return pkr.pathForAddress(utxo.address);
|
||||||
});
|
});
|
||||||
|
|
@ -1971,6 +2021,12 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent)
|
||||||
if (!tx.countInputSignatures(0))
|
if (!tx.countInputSignatures(0))
|
||||||
throw new Error('Could not sign generated tx');
|
throw new Error('Could not sign generated tx');
|
||||||
|
|
||||||
|
var txSize = tx.getSize();
|
||||||
|
if (txSize / 1024 > TX_MAX_SIZE_KB)
|
||||||
|
throw new Error('BIG: Resulting TX is too big ' + txSize + ' bytes. Aborting');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var me = {};
|
var me = {};
|
||||||
me[myId] = now;
|
me[myId] = now;
|
||||||
var meSeen = {};
|
var meSeen = {};
|
||||||
|
|
@ -1984,8 +2040,10 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent)
|
||||||
createdTs: now,
|
createdTs: now,
|
||||||
builder: b,
|
builder: b,
|
||||||
comment: options.memo,
|
comment: options.memo,
|
||||||
merchant: merchantData
|
merchant: merchantData,
|
||||||
|
paymentProtocolURL: options.uri,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return ntxid;
|
return ntxid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2187,7 +2245,7 @@ Wallet.prototype.subscribeToAddresses = function() {
|
||||||
|
|
||||||
var addrInfo = this.publicKeyRing.getAddressesInfo();
|
var addrInfo = this.publicKeyRing.getAddressesInfo();
|
||||||
this.blockchain.subscribe(_.pluck(addrInfo, 'addressStr'));
|
this.blockchain.subscribe(_.pluck(addrInfo, 'addressStr'));
|
||||||
log.debug('Subscribed to ' + addrInfo.length + ' addresses'); //TODO
|
log.debug('Subscribed to ' + addrInfo.length + ' addresses');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -2386,7 +2444,7 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb)
|
||||||
var ntxid;
|
var ntxid;
|
||||||
try {
|
try {
|
||||||
ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts);
|
ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts);
|
||||||
log.debug('TX Created: ntxid', ntxid); //TODO
|
log.debug('TX Created: ntxid', ntxid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -362,6 +362,11 @@ angular.module('copayApp.services')
|
||||||
var res = w.getPendingTxProposals();
|
var res = w.getPendingTxProposals();
|
||||||
_.each(res.txs, function(tx) {
|
_.each(res.txs, function(tx) {
|
||||||
root.computeAlternativeAmount(w, tx);
|
root.computeAlternativeAmount(w, tx);
|
||||||
|
if (tx.merchant) {
|
||||||
|
var url = tx.merchant.request_url;
|
||||||
|
var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1];
|
||||||
|
tx.merchant.domain = domain;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
$rootScope.txps = res.txs;
|
$rootScope.txps = res.txs;
|
||||||
if ($rootScope.pendingTxCount < res.pendingForUs) {
|
if ($rootScope.pendingTxCount < res.pendingForUs) {
|
||||||
|
|
|
||||||
|
|
@ -1533,6 +1533,8 @@ describe('Wallet model', function() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
w.txProposals.get = sinon.stub().returns(txp);
|
||||||
|
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
w.txProposals.merge = sinon.stub().returns({
|
||||||
ntxid: 1,
|
ntxid: 1,
|
||||||
txp: txp,
|
txp: txp,
|
||||||
|
|
@ -1574,6 +1576,7 @@ describe('Wallet model', function() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
w.txProposals.get = sinon.stub().returns(txp);
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
w.txProposals.merge = sinon.stub().returns({
|
||||||
ntxid: 1,
|
ntxid: 1,
|
||||||
txp: txp,
|
txp: txp,
|
||||||
|
|
@ -1616,6 +1619,7 @@ describe('Wallet model', function() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
w.txProposals.get = sinon.stub().returns(txp);
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
w.txProposals.merge = sinon.stub().returns({
|
||||||
ntxid: 1,
|
ntxid: 1,
|
||||||
txp: txp,
|
txp: txp,
|
||||||
|
|
@ -1649,6 +1653,7 @@ describe('Wallet model', function() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
w.txProposals.get = sinon.stub().returns(txp);
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
w.txProposals.merge = sinon.stub().returns({
|
||||||
ntxid: 1,
|
ntxid: 1,
|
||||||
txp: txp,
|
txp: txp,
|
||||||
|
|
@ -1682,6 +1687,7 @@ describe('Wallet model', function() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
w.txProposals.get = sinon.stub().returns(txp);
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
w.txProposals.merge = sinon.stub().returns({
|
||||||
ntxid: 1,
|
ntxid: 1,
|
||||||
txp: txp,
|
txp: txp,
|
||||||
|
|
@ -1712,6 +1718,7 @@ describe('Wallet model', function() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
w.txProposals.get = sinon.stub().returns(txp);
|
||||||
w.txProposals.merge = sinon.stub().returns({
|
w.txProposals.merge = sinon.stub().returns({
|
||||||
ntxid: 1,
|
ntxid: 1,
|
||||||
txp: txp,
|
txp: txp,
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@
|
||||||
You can import your current wallets after
|
You can import your current wallets after
|
||||||
<a class="text-white" href="#!/createProfile">creating your profile</a>
|
<a class="text-white" href="#!/createProfile">creating your profile</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="box-setup">
|
<div class="box-setup">
|
||||||
<h1><span translate>Sign in to</span> <b>Copay</b></h1>
|
<h1><span translate>Sign in to</span> <b>Copay</b></h1>
|
||||||
<form name="loginForm" ng-submit="openProfile(loginForm)" novalidate>
|
<form name="loginForm" ng-submit="openProfile(loginForm)" novalidate>
|
||||||
|
|
|
||||||
|
|
@ -40,15 +40,10 @@
|
||||||
<qrcode size="220" data="bitcoin:{{$root.addrInfos[0].addressStr}}"></qrcode>
|
<qrcode size="220" data="bitcoin:{{$root.addrInfos[0].addressStr}}"></qrcode>
|
||||||
|
|
||||||
<div class="m10t">
|
<div class="m10t">
|
||||||
<h4 class="size-12">{{$root.addrInfos[0].addressStr}} <span class="btn-copy" clip-copy="$root.addrInfos[0].addressStr"></span></h4>
|
<h4 class="size-12" clip-copy>{{$root.addrInfos[0].addressStr}} </h4>
|
||||||
<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>
|
||||||
</span>
|
</span>
|
||||||
<div class="small-10 columns small-centered">
|
|
||||||
<button class="m15t button secondary hide-for-large-up" ng-show="isMobile" ng-click="mobileCopy($root.addrInfos[0].addressStr)">
|
|
||||||
<i class="fi-link"> </i> <span translate>Copy to clipboard</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,20 @@
|
||||||
<contact address="{{out.address}}" tooltip-popup-delay="500" tooltip tooltip-placement="right" />
|
<contact address="{{out.address}}" tooltip-popup-delay="500" tooltip tooltip-placement="right" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="line-t" ng-show="!!tx.merchant">
|
||||||
|
<div class="send-note">
|
||||||
|
<p>
|
||||||
|
<b>{{tx.merchant.pr.pd.memo}}</b>
|
||||||
|
<p>
|
||||||
|
Expires {{tx.merchant.pr.pd.expires * 1000 | amTimeAgo }}
|
||||||
|
<span ng-show="tx.merchant.domain">[{{tx.merchant.domain}}]</span>
|
||||||
|
<span ng-show="!!tx.merchant.pr.ca"><i class="fi-lock"></i> {{tx.merchant.pr.ca}}</span>
|
||||||
|
<span ng-show="!tx.merchant.pr.ca" style="color:red;weight:bold;"><i class="fi-unlock"></i> Untrusted</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="last-transactions-content">
|
<table class="last-transactions-content">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="c in tx.actionList">
|
<tr ng-repeat="c in tx.actionList">
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,8 @@
|
||||||
<div class="large-7 medium-9 columns">
|
<div class="large-7 medium-9 columns">
|
||||||
<div class="list-addr">
|
<div class="list-addr">
|
||||||
<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>
|
||||||
<span class="btn-copy" clip-copy="addr.address"> </span>
|
|
||||||
<small translate class="label" ng-if="addr.isChange">change</small>
|
<small translate class="label" ng-if="addr.isChange">change</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
<h1 class="hide-for-large-up">{{$root.title}}</h1>
|
<h1 class="hide-for-large-up">{{$root.title}}</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="large-6 columns">
|
<div class="large-12 columns">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
|
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
|
||||||
<p class="text-warning size-16"
|
<p class="text-warning size-16"
|
||||||
|
|
@ -21,6 +21,13 @@
|
||||||
<i class="fi-alert"></i>
|
<i class="fi-alert"></i>
|
||||||
{{error|translate}}
|
{{error|translate}}
|
||||||
</p>
|
</p>
|
||||||
|
<p class="text-success size-16"
|
||||||
|
ng-show="success">
|
||||||
|
<i class="fi-check"></i>
|
||||||
|
{{success|translate}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row collapse">
|
<div class="row collapse">
|
||||||
<label for="address" class="left">
|
<label for="address" class="left">
|
||||||
|
|
@ -120,38 +127,36 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="large-12 columns" ng-show="fetchingURL">
|
||||||
|
<h3>
|
||||||
|
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
|
||||||
|
Fetching payment
|
||||||
|
</h3>
|
||||||
|
<p> From {{fetchingURL}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="large-12 columns" ng-show="!!$root.merchant">
|
<div class="large-12 columns" ng-show="!!$root.merchant">
|
||||||
<h3>This is a payment protocol transaction</h3>
|
<h3>This is a payment protocol transaction</h3>
|
||||||
<div class="send-note">
|
<div class="send-note">
|
||||||
<p>
|
<p>
|
||||||
<b translate>Send to</b>:
|
<b>{{$root.merchant.pr.pd.memo}}</b>
|
||||||
{{$root.merchant.domain}}
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b translate>Total amount for this transaction</b>:
|
|
||||||
<i>{{amount + defaultFee |noFractionNumber}} {{$root.wallet.settings.unitName}}</i>
|
<i>{{amount + defaultFee |noFractionNumber}} {{$root.wallet.settings.unitName}}</i>
|
||||||
<small ng-if="isRateAvailable">
|
<span class="text-gray" ng-if="isRateAvailable">
|
||||||
{{ rateService.toFiat((amount + defaultFee) * unitToSatoshi, alternativeIsoCode) | noFractionNumber: 2 }} {{ alternativeIsoCode }}
|
{{ rateService.toFiat((amount + defaultFee) * unitToSatoshi, alternativeIsoCode) | noFractionNumber: 2 }} {{ alternativeIsoCode }}
|
||||||
</small>
|
</span>
|
||||||
<small>
|
<span class="text-gray" >
|
||||||
(<span translate>Including fee of</span>
|
(<span translate>Including fee of</span>
|
||||||
{{defaultFee|noFractionNumber}}
|
{{defaultFee|noFractionNumber}}
|
||||||
{{$root.wallet.settings.unitName}})
|
{{$root.wallet.settings.unitName}})
|
||||||
</small>
|
</span>
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
<b translate>Server Says</b>:
|
Expires {{$root.merchant.expiration | amTimeAgo }}
|
||||||
{{$root.merchant.pr.pd.memo}}
|
[{{$root.merchant.domain}}]
|
||||||
</p>
|
<span ng-show="!!$root.merchant.pr.ca"><i class="fi-lock"></i> {{$root.merchant.pr.ca}}</span>
|
||||||
<p>
|
<span ng-show="!$root.merchant.pr.ca" style="color:red;weight:bold;"><i class="fi-unlock"></i> Untrusted</span>
|
||||||
<b translate>Certificate</b>:
|
|
||||||
<span ng-show="!!$root.merchant.pr.ca">{{$root.merchant.pr.ca}}</span>
|
|
||||||
<span ng-show="!$root.merchant.pr.ca" style="color:red;weight:bold;">Untrusted</span>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b translate>Payment Expiration</b>:
|
|
||||||
{{$root.merchant.expiration}}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -179,7 +184,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div><!-- end of row -->
|
</div><!-- end of row -->
|
||||||
<div class="row m20b" ng-show="$root.alternativeConversionRate > 0">
|
<div class="row m20b" ng-show="$root.alternativeConversionRate > 0">
|
||||||
<div class="large-6 columns size-12">
|
<div class="large-12 columns size-12">
|
||||||
<i class="fi-bitcoin-circle"></i>
|
<i class="fi-bitcoin-circle"></i>
|
||||||
1 BTC = {{alternativeConversionRate|noFractionNumber:2}} {{alternativeIsoCode}}
|
1 BTC = {{alternativeConversionRate|noFractionNumber:2}} {{alternativeIsoCode}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue