Merge pull request #309 from Bitcoin-com/wallet/task/500
Get the interface for the sendFlowService
This commit is contained in:
commit
1d1fde21ea
5 changed files with 370 additions and 11 deletions
|
|
@ -19,7 +19,8 @@ var modules = [
|
||||||
'copayApp.controllers',
|
'copayApp.controllers',
|
||||||
'copayApp.directives',
|
'copayApp.directives',
|
||||||
'copayApp.addons',
|
'copayApp.addons',
|
||||||
'bitcoincom.directives'
|
'bitcoincom.directives',
|
||||||
|
'bitcoincom.services'
|
||||||
];
|
];
|
||||||
|
|
||||||
var copayApp = window.copayApp = angular.module('copayApp', modules);
|
var copayApp = window.copayApp = angular.module('copayApp', modules);
|
||||||
|
|
@ -30,3 +31,4 @@ angular.module('copayApp.controllers', []);
|
||||||
angular.module('copayApp.directives', []);
|
angular.module('copayApp.directives', []);
|
||||||
angular.module('copayApp.addons', []);
|
angular.module('copayApp.addons', []);
|
||||||
angular.module('bitcoincom.directives', []);
|
angular.module('bitcoincom.directives', []);
|
||||||
|
angular.module('bitcoincom.services', []);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ describe('amountController', function(){
|
||||||
platformInfo,
|
platformInfo,
|
||||||
profileService,
|
profileService,
|
||||||
rateService,
|
rateService,
|
||||||
|
sendFlowService,
|
||||||
|
shapeshiftService,
|
||||||
$stateParams;
|
$stateParams;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -39,9 +41,11 @@ describe('amountController', function(){
|
||||||
isIos: true
|
isIos: true
|
||||||
};
|
};
|
||||||
|
|
||||||
profileService = jasmine.createSpyObj(['getWallets']);
|
profileService = jasmine.createSpyObj(['getWallet', 'getWallets']);
|
||||||
|
|
||||||
rateService = jasmine.createSpyObj(['fromFiat', 'whenAvailable']);
|
rateService = jasmine.createSpyObj(['fromFiat', 'whenAvailable']);
|
||||||
|
sendFlowService = jasmine.createSpyObj(['getStateClone']);
|
||||||
|
shapeshiftService = jasmine.createSpyObj(['shiftIt']);
|
||||||
|
|
||||||
$stateParams = {};
|
$stateParams = {};
|
||||||
|
|
||||||
|
|
@ -61,6 +65,11 @@ describe('amountController', function(){
|
||||||
stateName: 'ignoreme'
|
stateName: 'ignoreme'
|
||||||
};
|
};
|
||||||
$ionicHistory.backView.and.returnValue(backView);
|
$ionicHistory.backView.and.returnValue(backView);
|
||||||
|
|
||||||
|
var wallet = {
|
||||||
|
|
||||||
|
};
|
||||||
|
profileService.getWallet.and.returnValue(wallet);
|
||||||
profileService.getWallets.and.returnValue([{}]);
|
profileService.getWallets.and.returnValue([{}]);
|
||||||
rateService.fromFiat.and.returnValue(12); // satoshis or coins?
|
rateService.fromFiat.and.returnValue(12); // satoshis or coins?
|
||||||
|
|
||||||
|
|
@ -80,22 +89,25 @@ describe('amountController', function(){
|
||||||
popupService: {},
|
popupService: {},
|
||||||
rateService: rateService,
|
rateService: rateService,
|
||||||
$scope: $scope,
|
$scope: $scope,
|
||||||
|
sendFlowService: sendFlowService,
|
||||||
|
shapeshiftService: shapeshiftService,
|
||||||
$state: {},
|
$state: {},
|
||||||
$stateParams: $stateParams,
|
$stateParams: $stateParams,
|
||||||
txFormatService: {},
|
txFormatService: {},
|
||||||
walletService: {}
|
walletService: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
var data = {
|
var sendFlowState = {
|
||||||
stateParams: {
|
fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b',
|
||||||
fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b',
|
toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'
|
||||||
toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
$scope.$emit('$ionicView.beforeEnter', data);
|
|
||||||
|
|
||||||
expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b');
|
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||||
expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s');
|
|
||||||
|
$scope.$emit('$ionicView.beforeEnter', {});
|
||||||
|
|
||||||
|
//expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b');
|
||||||
|
//expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
250
src/js/services/bitcoin-uri.service.js
Normal file
250
src/js/services/bitcoin-uri.service.js
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('bitcoincom.services')
|
||||||
|
.factory('bitcoinUriService', bitcoinUriService);
|
||||||
|
|
||||||
|
function bitcoinUriService(bitcoinCashJsService, bwcService, $log) {
|
||||||
|
var bch = bitcoinCashJsService.getBitcoinCashJs();
|
||||||
|
var bitcore = bwcService.getBitcore();
|
||||||
|
var cashAddrRe = /^((?:q|p)[a-z0-9]{41})|((?:Q|P)[A-Z0-9]{41})$/;
|
||||||
|
|
||||||
|
var service = {
|
||||||
|
parse: parse
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function isValidCashAddr(address, network) {
|
||||||
|
var privateKey = new bch.PrivateKey('testnet');
|
||||||
|
var address1 = privateKey.toAddress();
|
||||||
|
console.log('legacy pub:', address1.toString());
|
||||||
|
//var addrss = bitcoinCashJsService.readAddress(address1);
|
||||||
|
//console.log('generated:', addrss.cashaddr);
|
||||||
|
//bch.Address.fromString(address1, 'testnet');
|
||||||
|
console.log('generated:', address1.toString('cashaddr'));
|
||||||
|
|
||||||
|
var isValid = false;
|
||||||
|
|
||||||
|
var prefix = network === 'testnet' ? 'bchtest:' : 'bitcoincash:';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (cashAddrRe.test(address)) {
|
||||||
|
// bitcoinCashJs.Address.isValid() assumes legacy address for string data, so does not work with cashaddr.
|
||||||
|
var bchAddresses = bitcoinCashJsService.readAddress(address.toLowerCase());
|
||||||
|
if (bchAddresses) {
|
||||||
|
var legacyAddress = bchAddresses.legacy;
|
||||||
|
if (bch.Address.isValid(legacyAddress, network)) {
|
||||||
|
isValid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Nop - Must not be a valid cashAddr.
|
||||||
|
$log.error('Error validating address.', e);
|
||||||
|
}
|
||||||
|
console.log(address,'isValidCashAddr:', isValid);
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
For parsing:
|
||||||
|
BIP21
|
||||||
|
BIP72
|
||||||
|
|
||||||
|
returns:
|
||||||
|
{
|
||||||
|
address: '',
|
||||||
|
amount: '',
|
||||||
|
coin: '',
|
||||||
|
isValid: false,
|
||||||
|
label: '',
|
||||||
|
legacyAddress: '',
|
||||||
|
message: '',
|
||||||
|
other: {
|
||||||
|
somethingIDontUnderstand: 'Its value'
|
||||||
|
},
|
||||||
|
req: {
|
||||||
|
"req-param0": "",
|
||||||
|
"req-param1": ""
|
||||||
|
},
|
||||||
|
testnet: false,
|
||||||
|
url: ''
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to do testnet, and copay too
|
||||||
|
|
||||||
|
*/
|
||||||
|
// bitcoincash:?r=https://bitpay.com/i/GLRoZMZxaWBqLqpoXexzoD
|
||||||
|
function parse(uri) {
|
||||||
|
var parsed = {
|
||||||
|
isValid: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Identify prefix
|
||||||
|
var trimmed = uri.trim();
|
||||||
|
var colonSplit = /^([\w-]*):?(.*)$/.exec(trimmed);
|
||||||
|
if (!colonSplit) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var addressAndParams = '';
|
||||||
|
var preColonLower = colonSplit[1].toLowerCase();
|
||||||
|
if (preColonLower === 'bitcoin') {
|
||||||
|
parsed.coin = 'btc';
|
||||||
|
addressAndParams = colonSplit[2];
|
||||||
|
console.log('Is btc');
|
||||||
|
|
||||||
|
} else if (/^(?:bitcoincash)|(?:bitcoin-cash)$/.test(preColonLower)) {
|
||||||
|
parsed.coin = 'bch';
|
||||||
|
parsed.test = false;
|
||||||
|
addressAndParams = colonSplit[2];
|
||||||
|
console.log('Is bch');
|
||||||
|
|
||||||
|
} else if (/^(?:bchtest)$/.test(preColonLower)) {
|
||||||
|
parsed.coin = 'bch';
|
||||||
|
parsed.testnet = true;
|
||||||
|
addressAndParams = colonSplit[2];
|
||||||
|
console.log('Is bch');
|
||||||
|
|
||||||
|
} else if (colonSplit[2] === '') {
|
||||||
|
// No colon and no coin specifier.
|
||||||
|
addressAndParams = colonSplit[1];
|
||||||
|
console.log('No prefix.');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Something with a colon in the middle that we don't recognise
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove erroneous leading slashes
|
||||||
|
var leadingSlashes = /^\/*([^\/]+(?:.*))$/.exec(addressAndParams);
|
||||||
|
if (!leadingSlashes) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
addressAndParams = leadingSlashes[1];
|
||||||
|
|
||||||
|
var questionMarkSplit = /^([^\?]*)\??([^\?]*)$/.exec(addressAndParams);
|
||||||
|
if (!questionMarkSplit) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = questionMarkSplit[1];
|
||||||
|
var params = questionMarkSplit[2];
|
||||||
|
|
||||||
|
var paramsSplit = params.split('&');
|
||||||
|
var others;
|
||||||
|
var req;
|
||||||
|
paramsSplit.forEach(function onParam(param){
|
||||||
|
var valueSplit = param.split('=');
|
||||||
|
if (valueSplit.length !== 2) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = valueSplit[0];
|
||||||
|
var value = valueSplit[1];
|
||||||
|
switch(key) {
|
||||||
|
case 'amount':
|
||||||
|
if (parseFloat(value)) {
|
||||||
|
parsed.amount = value;
|
||||||
|
} else {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'label':
|
||||||
|
parsed.label = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'message':
|
||||||
|
parsed.message = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
// Could use a more comprehesive regex to test URL validity, but then how would we know
|
||||||
|
// which part of the validation it failed?
|
||||||
|
if (value.startsWith('https://')) {
|
||||||
|
parsed.url = value;
|
||||||
|
} else {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (key.startsWith('req-')) {
|
||||||
|
req = req || {};
|
||||||
|
req[key] = value;
|
||||||
|
} else {
|
||||||
|
others = others || {};
|
||||||
|
others[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
parsed.others = others;
|
||||||
|
parsed.req = req;
|
||||||
|
|
||||||
|
|
||||||
|
// Need to do bitpay format as well? Probably
|
||||||
|
if (address) {
|
||||||
|
var addressLowerCase = address.toLowerCase();
|
||||||
|
var bch = bitcoinCashJsService.getBitcoinCashJs();
|
||||||
|
// Just a rough validation to exclude half-pasted addresses, or things obviously not bitcoin addresses
|
||||||
|
var cashAddrRe = /^((?:q|p)[a-z0-9]{41})|((?:Q|P)[A-Z0-9]{41})$/;
|
||||||
|
|
||||||
|
//var legacyRe = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||||
|
//var legacyTestnetRe = /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||||
|
|
||||||
|
if (bitcore.Address.isValid(address, 'livenet')) {
|
||||||
|
parsed.address = address;
|
||||||
|
parsed.legacyAddress = address;
|
||||||
|
parsed.testnet = false;
|
||||||
|
|
||||||
|
} else if (bitcore.Address.isValid(address, 'testnet')) {
|
||||||
|
parsed.address = address;
|
||||||
|
parsed.legacyAddress = address;
|
||||||
|
parsed.testnet = true;
|
||||||
|
|
||||||
|
// bitcoinCaashJs.Address.isValid() assumes legacy address for string data, so does not work with cashaddr.
|
||||||
|
// } else if (isValidCashAddr(addressLowerCase, 'livenet')) {
|
||||||
|
} else if (cashAddrRe.test(address) && parsed.testnet) {
|
||||||
|
var cashAddr = 'bchtest:' + addressLowerCase;
|
||||||
|
parsed.address = cashAddr;
|
||||||
|
parsed.coin = 'bch';
|
||||||
|
// TODO: Get legacy address
|
||||||
|
|
||||||
|
} else if (cashAddrRe.test(address)) {
|
||||||
|
var cashAddr = 'bitcoincash:' + addressLowerCase;
|
||||||
|
parsed.address = cashAddr;
|
||||||
|
parsed.coin = 'bch';
|
||||||
|
|
||||||
|
var bchAddresses = bitcoinCashJsService.readAddress(cashAddr);
|
||||||
|
parsed.legacyAddress = bchAddresses['legacy'];
|
||||||
|
|
||||||
|
parsed.testnet = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Check for a private key here too
|
||||||
|
|
||||||
|
|
||||||
|
// If has no address, must have Url.
|
||||||
|
parsed.isValid = !!(parsed.address || parsed.url);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
95
src/js/services/bitcoin-uri.service.spec.js
Normal file
95
src/js/services/bitcoin-uri.service.spec.js
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
fdescribe('bitcoinUriService', function() {
|
||||||
|
var bitcoinUriService;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
module('bitcoinCashJsModule');
|
||||||
|
module('bitcoincom.services');
|
||||||
|
module('bwcModule');
|
||||||
|
|
||||||
|
inject(function($injector){
|
||||||
|
bitcoinUriService = $injector.get('bitcoinUriService');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('Bitcoin testnet address', function() {
|
||||||
|
|
||||||
|
var parsed = bitcoinUriService.parse('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||||
|
|
||||||
|
expect(parsed.isValid).toBe(true);
|
||||||
|
expect(parsed.address).toBe('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||||
|
expect(parsed.coin).toBeUndefined();
|
||||||
|
expect(parsed.legacyAddress).toBe('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||||
|
expect(parsed.testnet).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('legacy address', function() {
|
||||||
|
|
||||||
|
var parsed = bitcoinUriService.parse('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||||
|
|
||||||
|
expect(parsed.isValid).toBe(true);
|
||||||
|
expect(parsed.address).toBe('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||||
|
expect(parsed.coin).toBeUndefined();
|
||||||
|
expect(parsed.legacyAddress).toBe('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||||
|
expect(parsed.testnet).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cashAddr testnet with prefix', function() {
|
||||||
|
|
||||||
|
var parsed = bitcoinUriService.parse('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||||
|
|
||||||
|
expect(parsed.isValid).toBe(true);
|
||||||
|
expect(parsed.address).toBe('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||||
|
expect(parsed.coin).toBe('bch');
|
||||||
|
expect(parsed.legacyAddress).toBe('mqk5vE278ytt6LUZqd97wi8c3FHsSYREX4');
|
||||||
|
expect(parsed.testnet).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cashAddr with prefix', function() {
|
||||||
|
|
||||||
|
var parsed = bitcoinUriService.parse('bitcoincash:qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||||
|
|
||||||
|
expect(parsed.isValid).toBe(true);
|
||||||
|
expect(parsed.address).toBe('bitcoincash:qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||||
|
expect(parsed.coin).toBe('bch');
|
||||||
|
expect(parsed.legacyAddress).toBe('1JXsK3HSFqoMnwh4Mevf5bTgqPcgNWX7ic');
|
||||||
|
expect(parsed.testnet).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cashAddr without prefix', function() {
|
||||||
|
|
||||||
|
var parsed = bitcoinUriService.parse('qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||||
|
|
||||||
|
expect(parsed.isValid).toBe(true);
|
||||||
|
expect(parsed.address).toBe('bitcoincash:qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||||
|
expect(parsed.coin).toBe('bch');
|
||||||
|
expect(parsed.legacyAddress).toBe('15fm3EwqgBYcxkndALBfforueps5yWKReJ');
|
||||||
|
expect(parsed.testnet).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Invalid addresses from https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md
|
||||||
|
it('invalid cashAddr style 1', function() {
|
||||||
|
var parsed = bitcoinUriService.parse('prefix:x64nx6hz');
|
||||||
|
expect(parsed.isValid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid cashAddr style 2', function() {
|
||||||
|
var parsed = bitcoinUriService.parse('p:gpf8m4h7');
|
||||||
|
expect(parsed.isValid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid cashAddr style 3', function() {
|
||||||
|
var parsed = bitcoinUriService.parse('bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn');
|
||||||
|
expect(parsed.isValid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid cashAddr style 4', function() {
|
||||||
|
var parsed = bitcoinUriService.parse('bchtest:testnetaddress4d6njnut');
|
||||||
|
expect(parsed.isValid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid cashAddr style 5', function() {
|
||||||
|
var parsed = bitcoinUriService.parse('bchreg:555555555555555555555555555555555555555555555udxmlmrz');
|
||||||
|
expect(parsed.isValid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -17,7 +17,7 @@ module.exports = function(config) {
|
||||||
files: [
|
files: [
|
||||||
'node_modules/angular/angular.js',
|
'node_modules/angular/angular.js',
|
||||||
|
|
||||||
'bitanalytics/bitanalytics-0.1.0.js',
|
'bitanalytics/bitanalytics.js',
|
||||||
|
|
||||||
// From Gruntfile.js
|
// From Gruntfile.js
|
||||||
'bower_components/qrcode-generator/js/qrcode.js',
|
'bower_components/qrcode-generator/js/qrcode.js',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue