Merge
This commit is contained in:
commit
230b6e2228
83 changed files with 5830 additions and 2736 deletions
416
src/js/services/bitcoin-uri.service.js
Normal file
416
src/js/services/bitcoin-uri.service.js
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
'use strict';
|
||||
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0072.mediawiki
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('bitcoinUriService', bitcoinUriService);
|
||||
|
||||
function bitcoinUriService(bitcoinCashJsService, bwcService, $log) {
|
||||
var bch = bitcoinCashJsService.getBitcoinCashJs();
|
||||
var bitcore = bwcService.getBitcore();
|
||||
|
||||
var service = {
|
||||
parse: parse
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function bitpayAddrOnMainnet(address) {
|
||||
var Address = bch.Address;
|
||||
var BitpayFormat = Address.BitpayFormat;
|
||||
|
||||
var mainnet = bch.Networks.mainnet;
|
||||
|
||||
var result = null;
|
||||
if (address[0] == 'C') {
|
||||
try {
|
||||
result = Address.fromString(address, mainnet, 'pubkeyhash', BitpayFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'H') {
|
||||
try {
|
||||
result = Address.fromString(address, mainnet, 'scripthash', BitpayFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cashAddrOnMainnet(address) {
|
||||
var Address = bch.Address;
|
||||
var CashAddrFormat = Address.CashAddrFormat;
|
||||
|
||||
var mainnet = bch.Networks.mainnet;
|
||||
|
||||
var prefixed = 'bitcoincash:' + address;
|
||||
var result = null;
|
||||
if (address[0] == 'q') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, mainnet, 'pubkeyhash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'p') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, mainnet, 'scripthash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cashAddrOnTestnet(address) {
|
||||
var Address = bch.Address;
|
||||
var CashAddrFormat = Address.CashAddrFormat;
|
||||
|
||||
var testnet = bch.Networks.testnet;
|
||||
|
||||
var prefixed = 'bchtest:' + address;
|
||||
var result = null;
|
||||
if (address[0] == 'q') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, testnet, 'pubkeyhash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'p') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, testnet, 'scripthash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function infoFromWalletImportText(data) {
|
||||
var split = data.split('|');
|
||||
// Copay seems to use extra parameter for coin.
|
||||
if (split.length < 5 || split.length > 6) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var type = parseInt(split[0], 10);
|
||||
if (isNaN(type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = split[1];
|
||||
var network = split[2];
|
||||
if (!(network === 'livenet' || network === 'testnet')) {
|
||||
return null;
|
||||
}
|
||||
var isTestnet = network === 'testnet';
|
||||
|
||||
var derivationPath = split[3];
|
||||
if (!/^m\/\d+'\/\d+'\/\d+'$/.test(derivationPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var hasPassphraseText = split[4];
|
||||
if (!(hasPassphraseText === 'true' || hasPassphraseText === 'false')) {
|
||||
return null;
|
||||
}
|
||||
var hasPassphrase = hasPassphraseText === 'true';
|
||||
|
||||
var coin; // Intentionally undefined as may not be present
|
||||
if (split.length > 5) {
|
||||
var coinText = split[5];
|
||||
if (!(coinText === 'bch' || coinText === 'btc')) {
|
||||
return null;
|
||||
}
|
||||
coin = coinText;
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
data: data,
|
||||
isTestnet: isTestnet,
|
||||
derivationPath: derivationPath,
|
||||
hasPassphrase: hasPassphrase,
|
||||
coin: coin
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
For parsing:
|
||||
BIP21
|
||||
BIP72
|
||||
|
||||
returns:
|
||||
{
|
||||
amount: '',
|
||||
amountInSatoshis: 0,
|
||||
bareUrl: '',
|
||||
coin: '',
|
||||
copayInvitation: '',
|
||||
import: { // testnet info in root, coin info in root if available
|
||||
data: '',
|
||||
derivationPath: '',
|
||||
hasPassphrase: false,
|
||||
type: 1,
|
||||
},
|
||||
isValid: false,
|
||||
label: '',
|
||||
message: '',
|
||||
other: {
|
||||
somethingIDontUnderstand: 'Its value'
|
||||
},
|
||||
privateKey: {
|
||||
encrypted: '',
|
||||
wif: ''
|
||||
}'',
|
||||
publicAddress: {
|
||||
bitpay: '',
|
||||
cashAddr: '',
|
||||
legacy: '',
|
||||
},
|
||||
req: {
|
||||
"req-param0": '',
|
||||
"req-param1": ''
|
||||
},
|
||||
testnet: false,
|
||||
url: '' // For BIP70
|
||||
}
|
||||
|
||||
Only fields that are present in the data are defined in the returned object. Both privateKey and publicAddress only have 1 field defined, if they exist at all.
|
||||
The exception to this is the coin property, which is determined from other data, such as the prefix or address type.
|
||||
|
||||
*/
|
||||
|
||||
function parse(data) {
|
||||
var parsed = {
|
||||
isValid: false
|
||||
};
|
||||
|
||||
if (typeof data !== 'string') {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Identify prefix
|
||||
var trimmed = data.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].trim();
|
||||
console.log('Is btc');
|
||||
|
||||
} else if (/^(?:bitcoincash)|(?:bitcoin-cash)$/.test(preColonLower)) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.test = false;
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is bch');
|
||||
|
||||
} else if (/^(?:bchtest)$/.test(preColonLower)) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.isTestnet = true;
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is bch');
|
||||
|
||||
} else if (colonSplit[2] === '') {
|
||||
// No colon and no coin specifier.
|
||||
addressAndParams = colonSplit[1].trim();
|
||||
console.log('No prefix.');
|
||||
|
||||
} else if (/^https?$/.test(colonSplit[1])) { // Plain URL
|
||||
addressAndParams = trimmed;
|
||||
|
||||
} else if (colonSplit[2].indexOf('|') == 0) { // Import
|
||||
addressAndParams = trimmed
|
||||
} else {
|
||||
// Something we don't recognise
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Remove erroneous leading slashes
|
||||
//var leadingSlashes = /^\/*([^\/]+(?:.*))$/.exec(addressAndParams);
|
||||
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];
|
||||
|
||||
if (params.length > 0) {
|
||||
var paramsSplit = params.split('&');
|
||||
var others;
|
||||
var req;
|
||||
var paramCount = paramsSplit.length;
|
||||
for(var i = 0; i < paramCount; i++) {
|
||||
var param = paramsSplit[i];
|
||||
var valueSplit = param.split('=');
|
||||
if (valueSplit.length !== 2) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var key = valueSplit[0];
|
||||
var value = valueSplit[1];
|
||||
var decodedValue = decodeURIComponent(value);
|
||||
switch(key) {
|
||||
case 'amount':
|
||||
var amount = parseFloat(decodedValue);
|
||||
if (amount) { // Checking for NaN, or no numbers at all etc. & convert to satoshi
|
||||
parsed.amount = decodedValue; // Need to check if a currency is precised
|
||||
parsed.amountInSatoshis = amount * 100000000
|
||||
} else {
|
||||
return parsed;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'label':
|
||||
parsed.label = decodedValue;
|
||||
break;
|
||||
|
||||
case 'message':
|
||||
parsed.message = decodedValue;
|
||||
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 (decodedValue.startsWith('https://')) {
|
||||
parsed.url = decodedValue;
|
||||
} else {
|
||||
return parsed;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (key.startsWith('req-')) {
|
||||
req = req || {};
|
||||
req[key] = decodedValue;
|
||||
} else {
|
||||
others = others || {};
|
||||
others[key] = decodedValue;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
parsed.others = others;
|
||||
parsed.req = req;
|
||||
|
||||
|
||||
if (address) {
|
||||
var addressLowerCase = address.toLowerCase();
|
||||
var copayInvitationRe = /^[0-9A-HJ-NP-Za-km-z]{70,80}$/;
|
||||
//var legacyRe = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||
//var legacyTestnetRe = /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||
var privateKeyEncryptedRe = /^6P[1-9A-HJ-NP-Za-km-z]{56}$/;
|
||||
var privateKeyForUncompressedPublicKeyRe = /^5[1-9A-HJ-NP-Za-km-z]{50}$/;
|
||||
var privateKeyForUncompressedPublicKeyTestnetRe = /^9[1-9A-HJ-NP-Za-km-z]{50}$/;
|
||||
var privateKeyForCompressedPublicKeyRe = /^[KL][1-9A-HJ-NP-Za-km-z]{51}$/;
|
||||
var privateKeyForCompressedPublicKeyTestnetRe = /^[c][1-9A-HJ-NP-Za-km-z]{51}$/;
|
||||
var urlRe = /^https?:\/\/.+/;
|
||||
|
||||
var bitpayAddrMainnet = bitpayAddrOnMainnet(address);
|
||||
var cashAddrTestnet = cashAddrOnTestnet(addressLowerCase);
|
||||
var cashAddrMainnet = cashAddrOnMainnet(addressLowerCase);
|
||||
var importInfo = infoFromWalletImportText(address);
|
||||
var privateKey = '';
|
||||
|
||||
if (parsed.isTestnet && cashAddrTestnet) {
|
||||
parsed.address = addressLowerCase;
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
cashAddr: addressLowerCase
|
||||
};
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (cashAddrMainnet) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
cashAddr: addressLowerCase
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitcore.Address.isValid(address, 'livenet')) {
|
||||
parsed.publicAddress = {
|
||||
legacy: address
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitcore.Address.isValid(address, 'testnet')) {
|
||||
parsed.publicAddress = {
|
||||
legacy: address
|
||||
};
|
||||
parsed.isTestnet = true;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitpayAddrMainnet) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
bitpay: address
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (copayInvitationRe.test(address) ) {
|
||||
parsed.copayInvitation = address;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (privateKeyForUncompressedPublicKeyRe.test(address) || privateKeyForCompressedPublicKeyRe.test(address)) {
|
||||
privateKey = address;
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'livenet');
|
||||
parsed.privateKey = { wif: privateKey };
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
} catch (e) {}
|
||||
|
||||
} else if (privateKeyForUncompressedPublicKeyTestnetRe.test(address) || privateKeyForCompressedPublicKeyTestnetRe.test(address)) {
|
||||
privateKey = address;
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'testnet');
|
||||
parsed.privateKey = { wif: privateKey };
|
||||
parsed.isTestnet = true;
|
||||
parsed.isValid = true;
|
||||
} catch (e) {}
|
||||
|
||||
} else if (privateKeyEncryptedRe.test(address)) {
|
||||
parsed.privateKey = { encrypted: address };
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (urlRe.test(address)) {
|
||||
parsed.bareUrl = trimmed;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (importInfo) {
|
||||
parsed.import = {
|
||||
type: importInfo.type,
|
||||
data: importInfo.data,
|
||||
derivationPath: importInfo.derivationPath,
|
||||
hasPassphrase: importInfo.hasPassphrase
|
||||
};
|
||||
parsed.coin = importInfo.coin;
|
||||
parsed.isTestnet = importInfo.isTestnet;
|
||||
parsed.isValid = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
parsed.isValid = !!parsed.url; // BIP72
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
429
src/js/services/bitcoin-uri.service.spec.js
Normal file
429
src/js/services/bitcoin-uri.service.spec.js
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
describe('bitcoinUriService', function() {
|
||||
var bitcoinUriService;
|
||||
|
||||
beforeEach(function() {
|
||||
module('bitcoinCashJsModule');
|
||||
module('bitcoincom.services');
|
||||
module('bwcModule');
|
||||
|
||||
inject(function($injector){
|
||||
bitcoinUriService = $injector.get('bitcoinUriService');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Bitcoin BIP72', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:?r=https://bitpay.com/i/CwzbKP3k3JNgXJBfuoerDr');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.isTestnet).toBeUndefined();
|
||||
expect(parsed.publicAddress).toBeUndefined();
|
||||
expect(parsed.url).toBe('https://bitpay.com/i/CwzbKP3k3JNgXJBfuoerDr');
|
||||
});
|
||||
|
||||
it('Bitcoin Cash BIP72', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:?r=https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress).toBeUndefined();
|
||||
expect(parsed.isTestnet).toBeUndefined();
|
||||
expect(parsed.url).toBe('https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu');
|
||||
});
|
||||
|
||||
it('Bitcoin Cash prefix with legacy address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:1G9FA9fFnHfTYxvmXeAbBD9FwzPAVMbd3j');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.legacy).toBe('1G9FA9fFnHfTYxvmXeAbBD9FwzPAVMbd3j');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash prefix with legacy address on testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:mkDQrKfSFD441JxrD1iPBsJFExgkvrPGQn');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.legacy).toBe('mkDQrKfSFD441JxrD1iPBsJFExgkvrPGQn');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash uri with extended params', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qr8v2vqnzntykakht43rqmxq8cdjzjp795fc3vsjgc?unknown=something&mystery=Melton%20probang&req-one=ichi&req-beta=Ni%20san');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.others.mystery).toBe('Melton probang');
|
||||
expect(parsed.others.unknown).toBe('something');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qr8v2vqnzntykakht43rqmxq8cdjzjp795fc3vsjgc');
|
||||
expect(parsed.req['req-beta']).toBe('Ni san');
|
||||
expect(parsed.req['req-one']).toBe('ichi');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash uri with invalid amount', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qq0knhwj4d5zy3kdph24w6etq58vwzua6sm7lhcmuk?amount=three');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('Bitcoin testnet address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBeUndefined();
|
||||
expect(parsed.publicAddress.legacy).toBe('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('Bitcoin uri', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:15yCdKWVKRvfXMJpPYZBqMhiGKwjKzZdLN');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('15yCdKWVKRvfXMJpPYZBqMhiGKwjKzZdLN');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with encoded label', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:1MxudKDEBWZ1yjizUSf6htacenNtb3DWbT?label=Mr.%20Smith');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.label).toBe('Mr. Smith');
|
||||
expect(parsed.publicAddress.legacy).toBe('1MxudKDEBWZ1yjizUSf6htacenNtb3DWbT');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with params', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:12nCRhMDfxVnuF3uYMXv2fNxBohNmacfWu?amount=20.3&label=Luke-Jr&message=Donation%20for%20project%20xyz');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.amount).toBe('20.3');
|
||||
expect(parsed.amountInSatoshis).toBe(2030000000);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.label).toBe('Luke-Jr');
|
||||
expect(parsed.publicAddress.legacy).toBe('12nCRhMDfxVnuF3uYMXv2fNxBohNmacfWu');
|
||||
expect(parsed.message).toBe('Donation for project xyz');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with slash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:/1GhpYmbRaf73AZRxDwAGr6653iZBGzdgeA');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('1GhpYmbRaf73AZRxDwAGr6653iZBGzdgeA');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with slashes', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin://18PCPhgZJjLxe9g3Q1BXLpL5aVut1fW3aX');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('18PCPhgZJjLxe9g3Q1BXLpL5aVut1fW3aX');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with space', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin: 19cPoKU5ZazY8NkLEsxK7drBqJnpGkax3d');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('19cPoKU5ZazY8NkLEsxK7drBqJnpGkax3d');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitpay without prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('CJoRov8TirekvajiimQpb5Hk95evA7H2Yz');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.bitpay).toBe('CJoRov8TirekvajiimQpb5Hk95evA7H2Yz');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('legacy address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBeUndefined();
|
||||
expect(parsed.publicAddress.legacy).toBe('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr testnet with prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('cashAddr uppercase', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('BITCOINCASH:QZZG9NMC5VX8GAP6XFATX3TWNSDN2YRMCSSULSMY44');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qzzg9nmc5vx8gap6xfatx3twnsdn2yrmcssulsmy44');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with dash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin-cash:qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with slash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:/qzdectfmuw0xxztfx7mh045830dqcshj85hr44l35a');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qzdectfmuw0xxztfx7mh045830dqcshj85hr44l35a');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with slashes', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash://qpj966w8utue75lqqq3rlgh20zkz3rmydqpq8syv9c');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpj966w8utue75lqqq3rlgh20zkz3rmydqpq8syv9c');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with space', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash: qpar9ldle8z6alcwgclejdhc24ha2xrg0szs5802ce');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpar9ldle8z6alcwgclejdhc24ha2xrg0szs5802ce');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('cashAddr with space on testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bchtest: qqjxkmtaxk4nv6w9h5ht2fjcj9c7ruh0fu7cnxsx5j');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qqjxkmtaxk4nv6w9h5ht2fjcj9c7ruh0fu7cnxsx5j');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('cashAddr without prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('copay invitation', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('PD5B7rEEj72st9d5nFszyuKxJP6FAGS7idVC2SMqiMxUcWVd8JifZDJw1UgjUctxefUFE3Sz6qLbch');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.copayInvitation).toBe('PD5B7rEEj72st9d5nFszyuKxJP6FAGS7idVC2SMqiMxUcWVd8JifZDJw1UgjUctxefUFE3Sz6qLbch');
|
||||
});
|
||||
|
||||
|
||||
it ('import BCH wallet no password', function() {
|
||||
var parsed = bitcoinUriService.parse("1|suggest route obvious broccoli good position hidden tone history around final lobster|livenet|m/44'/0'/0'|false");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('suggest route obvious broccoli good position hidden tone history around final lobster');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/0'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(false);
|
||||
});
|
||||
|
||||
it ('import BCH wallet with passphrase', function() {
|
||||
var parsed = bitcoinUriService.parse("1|fringe hazard all hobby trap myth fire stand sock empty soon east|livenet|m/44'/0'/0'|true");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('fringe hazard all hobby trap myth fire stand sock empty soon east');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/0'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(true);
|
||||
});
|
||||
|
||||
it ('import BTC wallet testnet', function() {
|
||||
// From copay
|
||||
var parsed = bitcoinUriService.parse("1|cat wealth column firm wet sauce tornado era feature monster click eyebrow|testnet|m/44'/1'/0'|false|btc");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('cat wealth column firm wet sauce tornado era feature monster click eyebrow');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/1'/0'");
|
||||
expect(parsed.import.hasPassphrase).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);
|
||||
});
|
||||
|
||||
it('non-string', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse([1, 2, 3, 4]);
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key encrypted with BIP38', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('6PRN5nEDmX842gsBzJryPu8Tw5kcsaQq1GPLcjVQPcEStvbFAtz11JX9pX');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.encrypted).toBe('6PRN5nEDmX842gsBzJryPu8Tw5kcsaQq1GPLcjVQPcEStvbFAtz11JX9pX');
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey mainnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey mainnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTu');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey testnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMM');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey mainnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTMwx');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTMwx');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey mainnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTTwx');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey testnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcC');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('URL only, http', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('http://paperwallet.bitcoin.com');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.bareUrl).toBe('http://paperwallet.bitcoin.com');
|
||||
});
|
||||
|
||||
it('URL only, https with query', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('https://purse.io/?one=two&three=four');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.bareUrl).toBe('https://purse.io/?one=two&three=four');
|
||||
});
|
||||
|
||||
});
|
||||
79
src/js/services/incoming-data.service.js
Normal file
79
src/js/services/incoming-data.service.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* incomingDataService is an intermediate to redirect either to the sendFlow
|
||||
* or to import/join a wallet.
|
||||
*/
|
||||
angular.module('copayApp.services').factory('incomingDataService', function(bitcoinUriService, $log, $state, $rootScope, scannerService, sendFlowService, gettextCatalog) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.showMenu = function(data) {
|
||||
$rootScope.$broadcast('incomingDataMenu.showMenu', data);
|
||||
};
|
||||
|
||||
root.redir = function(data, cbError) {
|
||||
var parsed = bitcoinUriService.parse(data);
|
||||
|
||||
console.log(parsed);
|
||||
$log.debug(parsed);
|
||||
|
||||
|
||||
if (parsed.isValid) {
|
||||
if (parsed.isTestnet) {
|
||||
if (cbError) {
|
||||
var errorMessage = gettextCatalog.getString('Testnet is not supported.');
|
||||
cbError(new Error(errorMessage));
|
||||
}
|
||||
} else {
|
||||
scannerService.pausePreview();
|
||||
|
||||
/**
|
||||
* Strategy for the action
|
||||
*/
|
||||
if (parsed.copayInvitation) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
} else if (parsed.import) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.import', {
|
||||
code: data
|
||||
});
|
||||
});
|
||||
} else if (
|
||||
!parsed.isValid
|
||||
|| parsed.privateKey
|
||||
|| (sendFlowService.state.isEmpty() && !parsed.url && !parsed.amount)
|
||||
) {
|
||||
root.showMenu({
|
||||
original: data,
|
||||
parsed: parsed
|
||||
});
|
||||
} else {
|
||||
var state = sendFlowService.state.getClone();
|
||||
state.data = data;
|
||||
|
||||
sendFlowService.start(state, function onError(err) {
|
||||
/**
|
||||
* OnError, open the menu (link not validated)
|
||||
*/
|
||||
root.showMenu({
|
||||
original: data,
|
||||
parsed: parsed
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cbError) {
|
||||
var errorMessage = gettextCatalog.getString('Data not recognised.');
|
||||
cbError(new Error(errorMessage));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
@ -1,475 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, sendFlowService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.showMenu = function(data) {
|
||||
$rootScope.$broadcast('incomingDataMenu.showMenu', data);
|
||||
};
|
||||
|
||||
root.redir = function(data, serviceId, serviceData) {
|
||||
var originalAddress = null;
|
||||
var noPrefixInAddress = 0;
|
||||
|
||||
if (data.toLowerCase().indexOf('bitcoin') < 0) {
|
||||
noPrefixInAddress = 1;
|
||||
}
|
||||
|
||||
if (typeof(data) == 'string' && !(/^bitcoin(cash)?:\?r=[\w+]/).exec(data) && (data.toLowerCase().indexOf('bitcoincash:') >= 0 || data[0] == 'q' || data[0] == 'p' || data[0] == 'C' || data[0] == 'H')) {
|
||||
try {
|
||||
noPrefixInAddress = 0;
|
||||
|
||||
if (data[0] == 'p' || data[0] == 'q') {
|
||||
data = 'bitcoincash:' + data;
|
||||
}
|
||||
var paramString = '';
|
||||
if (data.indexOf('?') >= 0) {
|
||||
paramString = data.substring(data.indexOf('?'));
|
||||
data = data.substring(0, data.indexOf('?'));
|
||||
}
|
||||
|
||||
if (data.indexOf('BITCOINCASH:') >= 0) {
|
||||
data = data.toLowerCase();
|
||||
}
|
||||
originalAddress = data.replace('bitcoincash:', '');
|
||||
var legacyAddress = bitcoinCashJsService.readAddress(data).legacy;
|
||||
data = 'bitcoincash:' + legacyAddress + paramString;
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
$log.debug('Processing incoming data: ' + data);
|
||||
|
||||
function sanitizeUri(data) {
|
||||
// Fixes when a region uses comma to separate decimals
|
||||
var regex = /[\?\&]amount=(\d+([\,\.]\d+)?)/i;
|
||||
var match = regex.exec(data);
|
||||
if (!match || match.length === 0) {
|
||||
return data;
|
||||
}
|
||||
var value = match[0].replace(',', '.');
|
||||
var newUri = data.replace(regex, value);
|
||||
|
||||
// mobile devices, uris like copay://glidera
|
||||
newUri.replace('://', ':');
|
||||
|
||||
return newUri;
|
||||
}
|
||||
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) return;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
function checkPrivateKey(privateKey) {
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'livenet');
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function goSend(addr, amount, message, coin, serviceId, serviceData) {
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
});
|
||||
// Timeout is required to enable the "Back" button
|
||||
$timeout(function() {
|
||||
var params = sendFlowService.getStateClone();
|
||||
|
||||
if (amount) {
|
||||
params.amount = amount;
|
||||
}
|
||||
|
||||
if (addr) {
|
||||
params.toAddress = addr;
|
||||
params.displayAddress = originalAddress ? originalAddress : addr;
|
||||
}
|
||||
|
||||
if (coin) {
|
||||
params.coin = coin;
|
||||
}
|
||||
|
||||
if (noPrefixInAddress) {
|
||||
params.noPrefixInAddress = noPrefixInAddress;
|
||||
}
|
||||
|
||||
if (serviceId) {
|
||||
params.thirdParty = [];
|
||||
params.thirdParty.id = serviceId;
|
||||
params.thirdParty.data = serviceData;
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.amount');
|
||||
} else {
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
// data extensions for Payment Protocol with non-backwards-compatible request
|
||||
if ((/^bitcoin(cash)?:\?r=[\w+]/).exec(data)) {
|
||||
var coin = data.indexOf('bitcoincash') >= 0 ? 'bch' : 'btc';
|
||||
data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, ''));
|
||||
if (coin == 'bch') {
|
||||
payproService.getPayProDetailsViaHttp(data, function onGetPayProDetailsViaHttp(err, details) {
|
||||
if (err) {
|
||||
var message = err.toString();
|
||||
if (typeof err.data === 'string') {
|
||||
// i.e. 'This invoice is no longer accepting payments'
|
||||
message = gettextCatalog.getString(err.data);
|
||||
}
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), message)
|
||||
} else {
|
||||
handlePayPro(details, coin);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
payproService.getPayProDetails(data, coin, function onGetPayProDetails(err, details) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else {
|
||||
handlePayPro(details, coin);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
data = sanitizeUri(data);
|
||||
|
||||
// Bitcoin URL
|
||||
if (bitcore.URI.isValid(data)) {
|
||||
var coin = 'btc';
|
||||
var parsed = new bitcore.URI(data);
|
||||
|
||||
var addr = parsed.address ? parsed.address.toString() : '';
|
||||
var message = parsed.message;
|
||||
|
||||
var amount = parsed.amount ? parsed.amount : '';
|
||||
|
||||
if (parsed.r) {
|
||||
payproService.getPayProDetails(parsed.r, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount) goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
return true;
|
||||
// Cash URI
|
||||
} else if (bitcoreCash.URI.isValid(data)) {
|
||||
var coin = 'bch';
|
||||
var parsed = new bitcoreCash.URI(data);
|
||||
|
||||
var addr = parsed.address ? parsed.address.toString() : '';
|
||||
var message = parsed.message;
|
||||
|
||||
var amount = parsed.amount ? parsed.amount : '';
|
||||
|
||||
// paypro not yet supported on cash
|
||||
if (parsed.r) {
|
||||
payproService.getPayProDetails(parsed.r, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount)
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
}
|
||||
handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
return true;
|
||||
|
||||
// Cash URI with bitcoin (btc) address version number?
|
||||
} else if (bitcore.URI.isValid(data.replace(/^bitcoincash:/,'bitcoin:'))) {
|
||||
$log.debug('Handling bitcoincash URI with legacy address');
|
||||
var coin = 'bch';
|
||||
var parsed = new bitcore.URI(data.replace(/^bitcoincash:/,'bitcoin:'));
|
||||
|
||||
var oldAddr = parsed.address ? parsed.address.toString() : '';
|
||||
if (!oldAddr) return false;
|
||||
|
||||
var addr = '';
|
||||
|
||||
var a = bitcore.Address(oldAddr).toObject();
|
||||
addr = bitcoreCash.Address.fromObject(a).toString();
|
||||
|
||||
// Translate address
|
||||
$log.debug('address transalated to:' + addr);
|
||||
popupService.showConfirm(
|
||||
gettextCatalog.getString('Bitcoin cash Payment'),
|
||||
gettextCatalog.getString('Payment address was translated to new Bitcoin Cash address format: ' + addr),
|
||||
gettextCatalog.getString('OK'),
|
||||
gettextCatalog.getString('Cancel'),
|
||||
function(ret) {
|
||||
if (!ret) return false;
|
||||
|
||||
var message = parsed.message;
|
||||
var amount = parsed.amount ? parsed.amount : '';
|
||||
|
||||
// paypro not yet supported on cash
|
||||
if (parsed.r) {
|
||||
payproService.getPayProDetails(parsed.r, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount)
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
}
|
||||
handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
}
|
||||
);
|
||||
return true;
|
||||
// Plain URL
|
||||
} else if (/^https?:\/\//.test(data)) {
|
||||
payproService.getPayProDetails(data, coin, function(err, details) {
|
||||
if (err) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'url'
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
handlePayPro(details);
|
||||
return true;
|
||||
});
|
||||
// Plain Address
|
||||
} else if (bitcore.Address.isValid(data, 'livenet') || bitcore.Address.isValid(data, 'testnet')) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'bitcoinAddress'
|
||||
});
|
||||
} else {
|
||||
goToAmountPage(data);
|
||||
}
|
||||
} else if (bitcoreCash.Address.isValid(data, 'livenet')) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'bitcoinAddress',
|
||||
coin: 'bch',
|
||||
});
|
||||
} else {
|
||||
goToAmountPage(data, 'bch');
|
||||
}
|
||||
} else if (data && data.indexOf(appConfigService.name + '://glidera') === 0) {
|
||||
var code = getParameterByName('code', data);
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.transitionTo('tabs.buyandsell.glidera', {
|
||||
code: code
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
} else if (data && data.indexOf(appConfigService.name + '://coinbase') === 0) {
|
||||
var code = getParameterByName('code', data);
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.transitionTo('tabs.buyandsell.coinbase', {
|
||||
code: code
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
// BitPayCard Authentication
|
||||
} else if (data && data.indexOf(appConfigService.name + '://') === 0) {
|
||||
|
||||
// Disable BitPay Card
|
||||
if (!appConfigService._enabledExtensions.debitcard) return false;
|
||||
|
||||
var secret = getParameterByName('secret', data);
|
||||
var email = getParameterByName('email', data);
|
||||
var otp = getParameterByName('otp', data);
|
||||
var reason = getParameterByName('r', data);
|
||||
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
switch (reason) {
|
||||
default:
|
||||
case '0':
|
||||
/* For BitPay card binding */
|
||||
$state.transitionTo('tabs.bitpayCardIntro', {
|
||||
secret: secret,
|
||||
email: email,
|
||||
otp: otp
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
// Join
|
||||
} else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
// Old join
|
||||
} else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
} else if (data && (data.substring(0, 2) == '6P' || checkPrivateKey(data))) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'privateKey'
|
||||
});
|
||||
} else if (data && ((data.substring(0, 2) == '1|') || (data.substring(0, 2) == '2|') || (data.substring(0, 2) == '3|'))) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.import', {
|
||||
code: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
} else {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'text'
|
||||
});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function goToAmountPage(toAddress, coin) {
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
});
|
||||
$timeout(function() {
|
||||
var stateParams = {
|
||||
toAddress: toAddress,
|
||||
displayAddress: toAddress,
|
||||
coin: coin,
|
||||
noPrefix: 1
|
||||
};
|
||||
sendFlowService.pushState(stateParams);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function handlePayPro(payProData, coin) {
|
||||
|
||||
console.log(payProData);
|
||||
|
||||
var toAddr = payProData.toAddress;
|
||||
var amount = payProData.amount;
|
||||
var paymentUrl = payProData.url;
|
||||
var expires = payProData.expires;
|
||||
var time = payProData.time;
|
||||
|
||||
if (coin === 'bch') {
|
||||
var displayAddr = payProData.outputs[0].address;
|
||||
toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy;
|
||||
amount = payProData.outputs[0].amount;
|
||||
paymentUrl = payProData.paymentUrl;
|
||||
expires = Math.floor(new Date(expires).getTime() / 1000)
|
||||
time = Math.ceil(new Date(time).getTime() / 1000)
|
||||
}
|
||||
|
||||
var name = payProData.domain;
|
||||
|
||||
if (payProData.memo.indexOf('eGifter') > -1) {
|
||||
name = 'eGifter'
|
||||
} else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
|
||||
name = 'BitPay';
|
||||
}
|
||||
|
||||
var thirdPartyData = {
|
||||
id: 'bip70',
|
||||
amount: amount,
|
||||
caTrusted: true,
|
||||
name: name,
|
||||
domain: payProData.domain,
|
||||
expires: expires,
|
||||
memo: payProData.memo,
|
||||
network: 'livenet',
|
||||
requiredFeeRate: payProData.requiredFeeRate,
|
||||
selfSigned: 0,
|
||||
time: time,
|
||||
displayAddress: displayAddr,
|
||||
toAddress: toAddr,
|
||||
url: paymentUrl,
|
||||
verified: true
|
||||
};
|
||||
|
||||
var stateParams = {
|
||||
amount: thirdPartyData.amount,
|
||||
toAddress: thirdPartyData.toAddress,
|
||||
coin: coin,
|
||||
thirdParty: thirdPartyData
|
||||
};
|
||||
|
||||
// fee
|
||||
if (thirdPartyData.requiredFeeRate) {
|
||||
stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024;
|
||||
}
|
||||
|
||||
// This does not make sense, thirdPartyData gets added by stateParams below
|
||||
//sendFlowService.pushState(thirdPartyData);
|
||||
|
||||
scannerService.pausePreview();
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
}).then(function() {
|
||||
$timeout(function() {
|
||||
sendFlowService.pushState(stateParams); // Need to do more here
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return root;
|
||||
});
|
||||
180
src/js/services/latest-release.service.js
Normal file
180
src/js/services/latest-release.service.js
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
'use strict';
|
||||
|
||||
(function() {
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('latestReleaseService', latestReleaseService);
|
||||
|
||||
function latestReleaseService($log, $http, $ionicPopup, configService, externalLinkService, gettextCatalog, platformInfo) {
|
||||
|
||||
var service = {
|
||||
// Functions
|
||||
checkLatestRelease: checkLatestRelease,
|
||||
requestLatestRelease: requestLatestRelease,
|
||||
showUpdatePopup: showUpdatePopup
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function checkLatestRelease(cb) {
|
||||
var releaseURL = configService.getDefaults().release.url;
|
||||
|
||||
requestLatestRelease(releaseURL, function (err, releaseData) {
|
||||
if (err) return cb(err);
|
||||
var currentVersion = window.version;
|
||||
var latestVersion = releaseData.tag_name;
|
||||
|
||||
if (!verifyTagFormat(currentVersion))
|
||||
return cb('Cannot verify the format of version tag: ' + currentVersion);
|
||||
if (!verifyTagFormat(latestVersion))
|
||||
return cb('Cannot verify the format of latest release tag: ' + latestVersion);
|
||||
|
||||
var current = formatTagNumber(currentVersion);
|
||||
var latest = formatTagNumber(latestVersion);
|
||||
|
||||
if (latest.major < current.major || (latest.major === current.major && latest.minor <= current.minor)) {
|
||||
return cb(null, false);
|
||||
}
|
||||
|
||||
var releaseSearchTerm = "";
|
||||
if (platformInfo.isNW) { // XX SP: DESKTOP: Check if the latest release is already available for current OS
|
||||
var platform = process.platform;
|
||||
if (platform === "darwin") {
|
||||
releaseSearchTerm = "osx";
|
||||
} else if (platform === "win32") {
|
||||
releaseSearchTerm = "win";
|
||||
} else if (platform === "linux") {
|
||||
releaseSearchTerm = "linux";
|
||||
}
|
||||
var foundNewVersion = false;
|
||||
for (var i in releaseData.assets) {
|
||||
if (releaseData.assets[i].name.indexOf(releaseSearchTerm) !== -1) {
|
||||
foundNewVersion = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$log.debug('A new version is available: ' + latestVersion);
|
||||
|
||||
var releaseNotes = false;
|
||||
if (releaseData.body) {
|
||||
var releaseLines = releaseData.body.split('\n');
|
||||
for (var lineNum in releaseLines) {
|
||||
if (releaseLines[lineNum].substring(0, 2) === "# ") {
|
||||
releaseLines[lineNum] = "<strong>" + releaseLines[lineNum].substring(2) + "</strong>";
|
||||
} else if (releaseLines[lineNum].substring(0, 2) === "- ") {
|
||||
releaseLines[lineNum] = "• " + releaseLines[lineNum].substring(2);
|
||||
}
|
||||
}
|
||||
releaseNotes = releaseLines.join('\n');
|
||||
}
|
||||
|
||||
return cb(null, {latestVersion: latestVersion, releaseNotes: releaseNotes});
|
||||
});
|
||||
|
||||
function verifyTagFormat(tag) {
|
||||
var regex = /^v?\d+\.\d+(\.\d+)?(-rc\d)?$/i;
|
||||
return regex.exec(tag);
|
||||
}
|
||||
|
||||
function formatTagNumber(tag) {
|
||||
var label = false;
|
||||
if (tag.split("-")[1]) { // Move postfixes like "-rc2" to a variable
|
||||
label = tag.split("-")[1];
|
||||
tag = tag.split("-")[0];
|
||||
}
|
||||
|
||||
var formattedNumber = tag.replace(/^v/i, '').split('.');
|
||||
return {
|
||||
major: +(formattedNumber[0] ? +formattedNumber[0] : 0),
|
||||
minor: +(formattedNumber[1] ? +formattedNumber[1] : 0),
|
||||
patch: +(formattedNumber[2] ? +formattedNumber[2] : 0),
|
||||
label: label /* XX SP: Maybe we can use this in a later stage (with for example 1.0.0-rc2 the value will be "rc2" and false if there is no label) */
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function requestLatestRelease(releaseURL, cb) {
|
||||
$log.debug('Retrieving latest release information...');
|
||||
|
||||
var request = {
|
||||
url: releaseURL,
|
||||
method: 'GET',
|
||||
json: true
|
||||
};
|
||||
|
||||
$http(request).then(function (release) {
|
||||
$log.debug('Latest release: ' + release.data.name);
|
||||
return cb(null, release.data);
|
||||
}, function (err) {
|
||||
return cb('Cannot get the release information: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
function showUpdatePopup() {
|
||||
var buttons = [];
|
||||
|
||||
if (!platformInfo.isIOS) { // There is no GitHub-release for iPhone
|
||||
buttons.push({
|
||||
text: "GitHub",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (platformInfo.isAndroid) {
|
||||
buttons.unshift({
|
||||
text: "Google Play Store",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://play.google.com/store/apps/details?id=com.bitcoin.mwallet';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (platformInfo.isIOS) {
|
||||
buttons.unshift({
|
||||
text: "App Store",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://itunes.apple.com/app/id1252903728';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
} else if (platformInfo.isNW) {
|
||||
if (process.platform === 'darwin') {
|
||||
buttons.unshift({
|
||||
text: "Mac App Store",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://itunes.apple.com/app/bitcoin-com-wallet/id1383072453';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (buttons.length === 1) { // There is only one source to download (probably on desktop, so open GitHub release page..)
|
||||
buttons[0].onTap();
|
||||
} else {
|
||||
buttons.push({
|
||||
text: gettextCatalog.getString('Go Back'),
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
$ionicPopup.show({
|
||||
title: gettextCatalog.getString('Update Available'),
|
||||
subTitle: gettextCatalog.getString('An update to this app is available. For your security, please update to the latest version.'),
|
||||
cssClass: 'popup-update',
|
||||
buttons: buttons
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
'use strict';
|
||||
angular.module('copayApp.services')
|
||||
.factory('latestReleaseService', function latestReleaseServiceFactory($log, $http, configService) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.checkLatestRelease = function(cb) {
|
||||
var releaseURL = configService.getDefaults().release.url;
|
||||
|
||||
requestLatestRelease(releaseURL, function(err, release) {
|
||||
if (err) return cb(err);
|
||||
var currentVersion = window.version;
|
||||
var latestVersion = release.data.tag_name;
|
||||
|
||||
if (!verifyTagFormat(currentVersion))
|
||||
return cb('Cannot verify the format of version tag: ' + currentVersion);
|
||||
if (!verifyTagFormat(latestVersion))
|
||||
return cb('Cannot verify the format of latest release tag: ' + latestVersion);
|
||||
|
||||
var current = formatTagNumber(currentVersion);
|
||||
var latest = formatTagNumber(latestVersion);
|
||||
|
||||
if (latest.major < current.major || (latest.major == current.major && latest.minor <= current.minor))
|
||||
return cb(null, false);
|
||||
|
||||
$log.debug('A new version is available: ' + latestVersion);
|
||||
return cb(null, true);
|
||||
});
|
||||
|
||||
function verifyTagFormat(tag) {
|
||||
var regex = /^v?\d+\.\d+\.\d+$/i;
|
||||
return regex.exec(tag);
|
||||
};
|
||||
|
||||
function formatTagNumber(tag) {
|
||||
var formattedNumber = tag.replace(/^v/i, '').split('.');
|
||||
return {
|
||||
major: +formattedNumber[0],
|
||||
minor: +formattedNumber[1],
|
||||
patch: +formattedNumber[2]
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function requestLatestRelease(releaseURL, cb) {
|
||||
$log.debug('Retrieving latest relsease information...');
|
||||
|
||||
var request = {
|
||||
url: releaseURL,
|
||||
method: 'GET',
|
||||
json: true
|
||||
};
|
||||
|
||||
$http(request).then(function(release) {
|
||||
$log.debug('Latest release: ' + release.data.name);
|
||||
return cb(null, release);
|
||||
}, function(err) {
|
||||
return cb('Cannot get the release information: ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
@ -52,11 +52,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
|
|||
|
||||
root.clear = function() {
|
||||
ongoingProcess = {};
|
||||
if (isCordova && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.hide();
|
||||
} else {
|
||||
$ionicLoading.hide();
|
||||
}
|
||||
$ionicLoading.hide();
|
||||
};
|
||||
|
||||
root.get = function(processName) {
|
||||
|
|
@ -82,23 +78,14 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
|
|||
if (customHandler) {
|
||||
customHandler(processName, showName, isOn);
|
||||
} else if (root.onGoingProcessName) {
|
||||
if (isCordova && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.show(null, showName, root.clear);
|
||||
} else {
|
||||
|
||||
var tmpl;
|
||||
if (isWindowsPhoneApp) tmpl = '<div>' + showName + '</div>';
|
||||
else tmpl = '<div class="item-icon-left">' + showName + '<ion-spinner class="spinner-stable" icon="lines"></ion-spinner></div>';
|
||||
$ionicLoading.show({
|
||||
template: tmpl
|
||||
});
|
||||
}
|
||||
var tmpl;
|
||||
if (isWindowsPhoneApp) tmpl = '<div>' + showName + '</div>';
|
||||
else tmpl = '<div class="item-icon-left">' + showName + '<ion-spinner class="spinner-stable" icon="lines"></ion-spinner></div>';
|
||||
$ionicLoading.show({
|
||||
template: tmpl,
|
||||
});
|
||||
} else {
|
||||
if (isCordova && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.hide();
|
||||
} else {
|
||||
$ionicLoading.hide();
|
||||
}
|
||||
$ionicLoading.hide();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('openURLService', function($rootScope, $ionicHistory, $document, $log, $state, platformInfo, lodash, profileService, incomingData, appConfigService) {
|
||||
angular.module('copayApp.services').factory('openURLService', function($rootScope, $ionicHistory, $document, $log, $state, platformInfo, lodash, profileService, incomingDataService, appConfigService) {
|
||||
var root = {};
|
||||
|
||||
var handleOpenURL = function(args) {
|
||||
|
|
@ -23,9 +23,12 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
|
|||
|
||||
document.addEventListener('handleopenurl', handleOpenURL, false);
|
||||
|
||||
if (!incomingData.redir(url)) {
|
||||
$log.warn('Unknown URL! : ' + url);
|
||||
}
|
||||
incomingDataService.redir(url, function onError(err) {
|
||||
if (err) {
|
||||
$log.warn('Unknown URL! : ' + url);
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var handleResume = function() {
|
||||
|
|
|
|||
85
src/js/services/send-flow-router.service.js
Normal file
85
src/js/services/send-flow-router.service.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowRouterService', sendFlowRouterService);
|
||||
|
||||
function sendFlowRouterService(
|
||||
sendFlowStateService
|
||||
, $state, $ionicHistory, $timeout
|
||||
) {
|
||||
|
||||
var service = {
|
||||
// Functions
|
||||
start: start,
|
||||
goNext: goNext,
|
||||
goBack: goBack,
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Start new send flow
|
||||
*/
|
||||
function start() {
|
||||
var state = sendFlowStateService.state;
|
||||
|
||||
if (state.isRequestAmount) {
|
||||
$state.go('tabs.paymentRequest.amount');
|
||||
} else {
|
||||
if ($state.current.name != 'tabs.send') {
|
||||
$state.go('tabs.home').then(function () {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.send').then(function () {
|
||||
$timeout(function () {
|
||||
goNext();
|
||||
}, 60);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
goNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next page
|
||||
* Routing strategy : https://bitcoindotcom.atlassian.net/wiki/x/BQDWKQ
|
||||
*/
|
||||
function goNext() {
|
||||
var state = sendFlowStateService.state;
|
||||
|
||||
var needsDestination = !state.toWalletId && !state.toAddress;
|
||||
var needsOrigin = !state.fromWalletId;
|
||||
var needsAmount = !state.amount && !state.sendMax;
|
||||
|
||||
if (needsDestination) {
|
||||
if (!state.isWalletTransfer && !state.thirdParty) {
|
||||
$state.go('tabs.send');
|
||||
return;
|
||||
} else if (!needsOrigin) {
|
||||
$state.go('tabs.send.destination');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsOrigin) {
|
||||
$state.go('tabs.send.origin');
|
||||
} else if (needsAmount) {
|
||||
$state.go('tabs.send.amount');
|
||||
} else {
|
||||
$state.go('tabs.send.review');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the previous page
|
||||
*/
|
||||
function goBack() {
|
||||
$ionicHistory.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
142
src/js/services/send-flow-state.service.js
Normal file
142
src/js/services/send-flow-state.service.js
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowStateService', sendFlowStateService);
|
||||
|
||||
function sendFlowStateService($log) {
|
||||
|
||||
var service = {
|
||||
// Variables
|
||||
state: {
|
||||
amount: 0,
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: '',
|
||||
coin: '',
|
||||
isRequestAmount: false,
|
||||
isWalletTransfer: false
|
||||
},
|
||||
previousStates: [],
|
||||
|
||||
// Functions
|
||||
init: init,
|
||||
clear: clear,
|
||||
getClone: getClone,
|
||||
map: map,
|
||||
pop: pop,
|
||||
push: push,
|
||||
isEmpty: isEmpty
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Init state & stack
|
||||
* @param {Object} params
|
||||
*/
|
||||
function init(params) {
|
||||
$log.debug("send-flow-state init()");
|
||||
|
||||
clear();
|
||||
|
||||
if (params) {
|
||||
push(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a state & stack
|
||||
*/
|
||||
function clear() {
|
||||
$log.debug("send-flow-state clear()");
|
||||
|
||||
clearCurrent();
|
||||
service.previousStates = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear current state only
|
||||
*/
|
||||
function clearCurrent() {
|
||||
$log.debug("send-flow-state clearCurrent()");
|
||||
|
||||
service.state = {
|
||||
amount: 0,
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: '',
|
||||
coin: '',
|
||||
isRequestAmount: false,
|
||||
isWalletTransfer: false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a clone of the current state
|
||||
*/
|
||||
function getClone() {
|
||||
var currentState = {};
|
||||
Object.keys(service.state).forEach(function forCurrentParam(key) {
|
||||
if (typeof service.state[key] !== 'function' && key !== 'previousStates') {
|
||||
currentState[key] = service.state[key];
|
||||
}
|
||||
});
|
||||
return currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in the current state from the params
|
||||
* @param {Object} params
|
||||
*/
|
||||
function map(params) {
|
||||
Object.keys(params).forEach(function forNewParam(key) {
|
||||
service.state[key] = params[key];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop state
|
||||
*/
|
||||
function pop() {
|
||||
$log.debug('send-flow-state pop');
|
||||
|
||||
if (service.previousStates.length) {
|
||||
var params = service.previousStates.pop();
|
||||
clearCurrent();
|
||||
map(params);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push state
|
||||
* @param {Object} params
|
||||
*/
|
||||
function push(params) {
|
||||
$log.debug('send-flow-state push');
|
||||
|
||||
var currentParams = getClone();
|
||||
service.previousStates.push(currentParams);
|
||||
clearCurrent();
|
||||
map(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is empty stack
|
||||
*/
|
||||
function isEmpty() {
|
||||
return service.previousStates.length == 0;
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
148
src/js/services/send-flow.service.js
Normal file
148
src/js/services/send-flow.service.js
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowService', sendFlowService);
|
||||
|
||||
function sendFlowService(
|
||||
sendFlowStateService, sendFlowRouterService
|
||||
, bitcoinUriService, payproService, bitcoinCashJsService
|
||||
, popupService, gettextCatalog
|
||||
, $state, $log
|
||||
) {
|
||||
|
||||
var service = {
|
||||
// Variables
|
||||
state: sendFlowStateService,
|
||||
router: sendFlowRouterService,
|
||||
|
||||
// Functions
|
||||
start: start,
|
||||
goNext: goNext,
|
||||
goBack: goBack
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Start a new send flow
|
||||
* @param {Object} params
|
||||
* @param {Function} onError
|
||||
*/
|
||||
function start(params, onError) {
|
||||
$log.debug('send-flow start()');
|
||||
|
||||
if (params && params.data) {
|
||||
var res = bitcoinUriService.parse(params.data);
|
||||
|
||||
if (res.isValid) {
|
||||
|
||||
// If BIP70 (url)
|
||||
if (res.url) {
|
||||
var url = res.url;
|
||||
var coin = res.coin || '';
|
||||
payproService.getPayProDetails(url, coin, function onGetPayProDetails(err, payProData) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else {
|
||||
var name = payProData.domain;
|
||||
|
||||
// Detect some merchant that we know
|
||||
if (payProData.memo.indexOf('eGifter') > -1) {
|
||||
name = 'eGifter'
|
||||
} else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
|
||||
name = 'BitPay';
|
||||
}
|
||||
|
||||
// Init thirdParty
|
||||
var thirdPartyData = {
|
||||
id: 'bip70',
|
||||
caTrusted: true,
|
||||
name: name,
|
||||
domain: payProData.domain,
|
||||
expires: payProData.expires,
|
||||
memo: payProData.memo,
|
||||
network: 'livenet',
|
||||
requiredFeeRate: payProData.requiredFeeRate,
|
||||
selfSigned: 0,
|
||||
time: payProData.time,
|
||||
url: payProData.url,
|
||||
verified: true
|
||||
};
|
||||
|
||||
// Fill in params
|
||||
params.amount = payProData.amount,
|
||||
params.toAddress = payProData.toAddress,
|
||||
params.coin = coin,
|
||||
params.thirdParty = thirdPartyData
|
||||
}
|
||||
|
||||
// Resolve
|
||||
_next();
|
||||
});
|
||||
} else {
|
||||
if (res.coin) {
|
||||
params.coin = res.coin;
|
||||
}
|
||||
|
||||
if (res.amountInSatoshis) {
|
||||
params.amount = res.amountInSatoshis;
|
||||
}
|
||||
|
||||
if (res.publicAddress) {
|
||||
var prefix = res.isTestnet ? 'bchtest:' : 'bitcoincash:';
|
||||
params.displayAddress = res.publicAddress.cashAddr || res.publicAddress.legacy || res.publicAddress.bitpay;
|
||||
var formatAddress = res.publicAddress.cashAddr ? prefix + params.displayAddress : params.displayAddress;
|
||||
params.toAddress = bitcoinCashJsService.readAddress(formatAddress).legacy;
|
||||
}
|
||||
|
||||
_next();
|
||||
}
|
||||
} else {
|
||||
if (onError) {
|
||||
onError();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_next();
|
||||
}
|
||||
|
||||
|
||||
// Next used for sync the async task
|
||||
function _next() {
|
||||
sendFlowStateService.init(params);
|
||||
|
||||
// Routing strategy to -> send-flow-router.service
|
||||
sendFlowRouterService.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next step
|
||||
* @param {Object} state
|
||||
*/
|
||||
function goNext(state) {
|
||||
$log.debug('send-flow goNext()');
|
||||
|
||||
// Save the current route before leaving
|
||||
state.route = $state.current.name;
|
||||
|
||||
// Save the state and redirect the user
|
||||
sendFlowStateService.push(state);
|
||||
sendFlowRouterService.goNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the previous step
|
||||
*/
|
||||
function goBack() {
|
||||
$log.debug('send-flow goBack()');
|
||||
|
||||
// Remove the state on top and redirect the user
|
||||
sendFlowStateService.pop();
|
||||
sendFlowRouterService.goBack();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('copayApp.services')
|
||||
.factory('sendFlowService', sendFlowService);
|
||||
|
||||
function sendFlowService($log) {
|
||||
|
||||
var service = {
|
||||
// A separate state variable so we can ensure it is cleared of everything,
|
||||
// even other properties added that this service does not know about. (such as "coin")
|
||||
state: {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: ''
|
||||
},
|
||||
previousStates: [],
|
||||
|
||||
// Functions
|
||||
clear: clear,
|
||||
getStateClone: getStateClone,
|
||||
map: map,
|
||||
popState: popState,
|
||||
pushState: pushState,
|
||||
startSend: startSend
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function clear() {
|
||||
console.log("sendFlow clear()");
|
||||
clearCurrent();
|
||||
service.previousStates = [];
|
||||
}
|
||||
|
||||
function clearCurrent() {
|
||||
console.log("sendFlow clearCurrent()");
|
||||
service.state = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handy for debugging
|
||||
*/
|
||||
function getStateClone() {
|
||||
var currentState = {};
|
||||
Object.keys(service.state).forEach(function forCurrentParam(key) {
|
||||
if (typeof service.state[key] !== 'function' && key !== 'previousStates') {
|
||||
currentState[key] = service.state[key];
|
||||
}
|
||||
});
|
||||
return currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all previous state
|
||||
*/
|
||||
function startSend(params) {
|
||||
console.log('startSend()');
|
||||
clear();
|
||||
map(params);
|
||||
}
|
||||
|
||||
function map(params) {
|
||||
Object.keys(params).forEach(function forNewParam(key) {
|
||||
service.state[key] = params[key];
|
||||
});
|
||||
};
|
||||
|
||||
function popState() {
|
||||
console.log('sendFlow pop');
|
||||
if (service.previousStates.length) {
|
||||
var params = service.previousStates.pop();
|
||||
clearCurrent();
|
||||
map(params);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
};
|
||||
|
||||
function pushState(params) {
|
||||
console.log('sendFlow push');
|
||||
var currentParams = getStateClone();
|
||||
service.previousStates.push(currentParams);
|
||||
clearCurrent();
|
||||
map(params);
|
||||
};
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -328,18 +328,23 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q)
|
|||
$scope.amount, $scope.withdrawalAddress,
|
||||
$scope.coinIn, $scope.coinOut
|
||||
);
|
||||
console.log('shapeshiftApiService.FixedAmountTx()');
|
||||
console.log(fixedTx);
|
||||
SSA.FixedAmountTx(fixedTx, function (data) {
|
||||
console.log(data)
|
||||
return promise.resolve({ fixedTxData : data.success });
|
||||
console.log(data);
|
||||
promise.resolve(data);
|
||||
});
|
||||
return promise.promise;
|
||||
},
|
||||
NormalTx : function($scope){
|
||||
var promise = $q.defer();
|
||||
var normalTx = SSA.CreateNormalTx($scope.withdrawalAddress, $scope.coinIn, $scope.coinOut);
|
||||
|
||||
console.log('shapeshiftApiService.NormalTx()');
|
||||
console.log(normalTx);
|
||||
SSA.NormalTx(normalTx, function (data) {
|
||||
promise.resolve({ normalTxData : data });
|
||||
console.log(data);
|
||||
promise.resolve(data);
|
||||
});
|
||||
return promise.promise;
|
||||
},
|
||||
|
|
@ -360,11 +365,12 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q)
|
|||
return promise.promise;
|
||||
},
|
||||
ValidateAddress : function(address, coin) {
|
||||
var promise = $q.defer();
|
||||
SSA.ValidateAdddress(address, coin, function(data){
|
||||
promise.resolve(data);
|
||||
});
|
||||
return promise.promise;
|
||||
var promise = $q.defer();
|
||||
SSA.ValidateAdddress(address, coin, function onRequest(data){
|
||||
console.log(data);
|
||||
promise.resolve(data);
|
||||
});
|
||||
return promise.promise;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
112
src/js/services/shapeshift.service.js
Normal file
112
src/js/services/shapeshift.service.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('shapeshiftService', shapeshiftService);
|
||||
|
||||
function shapeshiftService(shapeshiftApiService, gettextCatalog) {
|
||||
|
||||
var service = {
|
||||
// Variables
|
||||
coinIn: '',
|
||||
coinOut: '',
|
||||
withdrawalAddress: '',
|
||||
returnAddress: '',
|
||||
amount: '',
|
||||
marketData: {},
|
||||
coins: {
|
||||
'BTC': {name: 'Bitcoin', symbol: 'BTC'},
|
||||
'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'}
|
||||
},
|
||||
|
||||
// Functions
|
||||
getMarketData: getMarketData,
|
||||
shiftIt: shiftIt
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function handleError(response, defaultMessage, cb) {
|
||||
if (response && typeof response.error === "string") {
|
||||
cb(new Error(response.error));
|
||||
} else if (response && response.error && response.error.message) {
|
||||
cb(new Error(response.error.message));
|
||||
} else {
|
||||
cb(new Error(defaultMessage));
|
||||
}
|
||||
}
|
||||
|
||||
function getMarketData(coinIn, coinOut, cb) {
|
||||
service.coinIn = coinIn;
|
||||
service.coinOut = coinOut;
|
||||
shapeshiftApiService
|
||||
.marketInfo(service.coinIn, service.coinOut)
|
||||
.then(function (response) {
|
||||
if (!response || response.error) {
|
||||
handleError(response, 'Invalid response from Shapeshift', cb);
|
||||
} else {
|
||||
service.marketData = response;
|
||||
service.rateString = service.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase();
|
||||
cb(null, response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) {
|
||||
// Test if the amount is correct depending on the min and max
|
||||
if (!amount || typeof amount !== 'number') {
|
||||
cb(new Error(gettextCatalog.getString('Amount is not defined')));
|
||||
} else if (amount < service.marketData.minimum) {
|
||||
cb(new Error(gettextCatalog.getString('Amount is below the minimun')));
|
||||
} else if (amount > service.marketData.maxLimit) {
|
||||
cb(new Error(gettextCatalog.getString('Amount is above the limit')));
|
||||
} else {
|
||||
// Init service data
|
||||
service.withdrawalAddress = withdrawalAddress;
|
||||
service.returnAddress = returnAddress;
|
||||
service.coinIn = coinIn;
|
||||
service.coinOut = coinOut;
|
||||
service.amount = amount;
|
||||
|
||||
// Check the address
|
||||
shapeshiftApiService
|
||||
.ValidateAddress(withdrawalAddress, coinOut)
|
||||
.then(function onSuccess(response) {
|
||||
if (response && response.isvalid) {
|
||||
// Prepare the transaction shapeshift side
|
||||
shapeshiftApiService.NormalTx(service).then(function onResponse(response) {
|
||||
// If error, return it
|
||||
if (!response || response.error) {
|
||||
handleError(response, gettextCatalog.getString('Invalid response from Shapeshift'), cb);
|
||||
} else {
|
||||
var txData = response;
|
||||
|
||||
// If the content is not that it was expected, get back an error
|
||||
if (!txData || !txData.orderId || !txData.deposit) {
|
||||
cb(new Error(gettextCatalog.getString('Invalid response from Shapeshift')));
|
||||
} else {
|
||||
// Get back the data
|
||||
service.depositInfo = txData;
|
||||
var shapeshiftData = {
|
||||
coinIn: coinIn,
|
||||
coinOut: coinOut,
|
||||
toWalletId: service.toWalletId,
|
||||
minAmount: service.marketData.minimum,
|
||||
maxAmount: service.marketData.maxLimit,
|
||||
orderId: txData.orderId,
|
||||
toAddress: txData.deposit
|
||||
};
|
||||
cb(null, shapeshiftData);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cb(new Error(gettextCatalog.getString('Invalid address')));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingData, platformInfo, servicesService) {
|
||||
var root = {};
|
||||
root.ShiftState = 'Shift';
|
||||
root.coinIn = '';
|
||||
root.coinOut = '';
|
||||
root.withdrawalAddress = '';
|
||||
root.returnAddress = '';
|
||||
root.amount = '';
|
||||
root.marketData = {};
|
||||
|
||||
root.getMarketDataIn = function (coin) {
|
||||
if (coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn);
|
||||
return root.getMarketData(coin, root.coinOut);
|
||||
};
|
||||
root.getMarketDataOut = function (coin) {
|
||||
if (coin === root.coinIn) return root.getMarketData(root.coinOut, root.coinIn);
|
||||
return root.getMarketData(root.coinIn, coin);
|
||||
};
|
||||
root.getMarketData = function (coinIn, coinOut, cb) {
|
||||
root.coinIn = coinIn;
|
||||
root.coinOut = coinOut;
|
||||
if (root.coinIn === undefined || root.coinOut === undefined) return;
|
||||
shapeshiftApiService
|
||||
.marketInfo(root.coinIn, root.coinOut)
|
||||
.then(function (marketData) {
|
||||
root.marketData = marketData;
|
||||
root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase();
|
||||
if (cb) {
|
||||
cb(marketData);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*shapeshiftApiService.coins().then(function(coins){
|
||||
root.coins = coins;
|
||||
root.coinIn = coins['BTC'].symbol;
|
||||
root.coinOut = coins['BCH'].symbol;
|
||||
root.getMarketData(root.coinIn, root.coinOut);
|
||||
});*/
|
||||
|
||||
root.coins = {
|
||||
'BTC': {name: 'Bitcoin', symbol: 'BTC'},
|
||||
'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'}
|
||||
};
|
||||
|
||||
function checkForError(data) {
|
||||
if (data.err) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) {
|
||||
ongoingProcess.set('connectingShapeshift', true);
|
||||
root.withdrawalAddress = withdrawalAddress;
|
||||
root.returnAddress = returnAddress;
|
||||
root.coinIn = coinIn;
|
||||
root.coinOut = coinOut;
|
||||
shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function (valid) {
|
||||
var tx = ShapeShift();
|
||||
var coin;
|
||||
console.log("Starting");
|
||||
tx.then(function (txData) {
|
||||
console.log("Got txData", txData);
|
||||
if (txData['fixedTxData']) {
|
||||
txData = txData.fixedTxData;
|
||||
if (checkForError(txData)) return cb(txData.err);
|
||||
//console.log(txData)
|
||||
var coinPair = txData.pair.split('_');
|
||||
txData.depositType = coinPair[0].toUpperCase();
|
||||
txData.withdrawalType = coinPair[1].toUpperCase();
|
||||
coin = root.coins[txData.depositType].name.toLowerCase();
|
||||
|
||||
txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount;
|
||||
|
||||
root.txFixedPending = true;
|
||||
|
||||
} else if (txData['normalTxData']) {
|
||||
txData = txData.normalTxData;
|
||||
if (checkForError(txData)) return cb(txData.err);
|
||||
coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase();
|
||||
txData.depositQR = coin + ":" + txData.deposit;
|
||||
} else if (txData['cancelTxData']) {
|
||||
txData = txData.cancelTxData;
|
||||
if (checkForError(txData)) return cb(txData.err);
|
||||
if (root.txFixedPending) {
|
||||
root.txFixedPending = false;
|
||||
}
|
||||
root.ShiftState = 'Shift';
|
||||
}
|
||||
root.depositInfo = txData;
|
||||
//console.log(root.marketData);
|
||||
//console.log(root.depositInfo);
|
||||
var sendAddress = txData.depositQR;
|
||||
if (sendAddress && sendAddress.indexOf('bitcoin cash') >= 0)
|
||||
sendAddress = sendAddress.replace('bitcoin cash', 'bitcoincash');
|
||||
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
|
||||
root.ShiftState = 'Cancel';
|
||||
//root.GetStatus();
|
||||
//root.txInterval=$interval(root.GetStatus, 8000);
|
||||
|
||||
var shapeshiftData = {
|
||||
coinIn: coinIn,
|
||||
coinOut: coinOut,
|
||||
toWalletId: root.toWalletId,
|
||||
minAmount: root.marketData.minimum,
|
||||
maxAmount: root.marketData.maxLimit,
|
||||
orderId: root.depositInfo.orderId,
|
||||
toAddress: txData.deposit
|
||||
};
|
||||
//
|
||||
// if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
// return;
|
||||
// }
|
||||
cb(null, shapeshiftData);
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
function ShapeShift() {
|
||||
if (parseFloat(root.amount) > 0) return shapeshiftApiService.FixedAmountTx(root);
|
||||
return shapeshiftApiService.NormalTx(root);
|
||||
}
|
||||
|
||||
root.GetStatus = function () {
|
||||
var address = root.depositInfo.deposit
|
||||
shapeshiftApiService.GetStatusOfDepositToAddress(address).then(function (data) {
|
||||
root.DepositStatus = data;
|
||||
if (root.DepositStatus.status === 'complete') {
|
||||
$interval.cancel(root.txInterval);
|
||||
root.depositInfo = null;
|
||||
root.ShiftState = 'Shift'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return root;
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue