Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lightning Address: handle addresses hosted on Tor #1349

Merged
merged 11 commits into from
Apr 12, 2023
2 changes: 2 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"general.admin": "Admin",
"general.pay": "Pay",
"general.open": "Open",
"general.loading": "Loading",
"general.conversionRate": "Conversion rate",
"general.bitcoin": "Bitcoin",
"general.fiat": "Fiat",
Expand Down Expand Up @@ -588,6 +589,7 @@
"utils.handleAnything.notValid": "Value provided was not a valid Bitcoin address or Lightning Invoice",
"utils.handleAnything.lnurlAuthNotSupported": "LnurlAuth not supported by your node implementation",
"utils.handleAnything.unsupportedLnurlType": "Unsupported lnurl type",
"utils.handleAnything.invalidLnurlParams": "Could not parse lnurl params",
"stores.InvoicesStore.errorCreatingInvoice": "Error creating invoice",
"stores.InvoicesStore.errorGeneratingAddress": "Error generating new address",
"stores.InvoicesStore.zeroAmountLndhub": "LNDHub instance might not support zero-amount invoices",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"https-browserify": "0.0.1",
"identicon.js": "2.3.3",
"inherits": "2.0.4",
"js-lnurl": "0.3.0",
"js-lnurl": "0.5.1",
"js-sha256": "0.9.0",
"jsc-android": "241213.1.0",
"lodash": "4.17.21",
Expand Down
5 changes: 5 additions & 0 deletions utils/AddressUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@ describe('AddressUtils', () => {
'evankaloudis@ln.twitter.com'
)
).toBeTruthy();
expect(
AddressUtils.isValidLightningAddress(
'oniontip@7tpv3ynajkv6cdocmzitcd4z3xrstp3ic6xtv5om3dc2ned3fffll5qd.onion'
)
).toBeTruthy();
});

it("rejects LNURLPay Lightning Addresses with ports - let's not mix this up with nodes", () => {
Expand Down
177 changes: 105 additions & 72 deletions utils/handleAnything.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Alert } from 'react-native';
import { getParams as getlnurlParams, findlnurl, decodelnurl } from 'js-lnurl';
import ReactNativeBlobUtil from 'react-native-blob-util';
import { doTorRequest, RequestMethod } from './../utils/TorUtils';

import stores from '../stores/Stores';
import AddressUtils from './../utils/AddressUtils';
import ConnectionFormatUtils from './../utils/ConnectionFormatUtils';
Expand Down Expand Up @@ -194,27 +196,44 @@ const handleAnything = async (
const error = localeString(
'utils.handleAnything.lightningAddressError'
);
return ReactNativeBlobUtil.fetch('get', url)
.then((response: any) => {
const status = response.info().status;
if (status == 200) {
const data = response.json();
// handle Tor LN addresses
if (settingsStore.enableTor) {
await doTorRequest(url, RequestMethod.GET)
.then((response: any) => {
return [
'LnurlPay',
{
lnurlParams: data,
lnurlParams: response,
amount: setAmount
}
];
} else {
})
.catch((error: any) => {
throw new Error(error);
}
})
.catch(() => {
throw new Error(error);
});
// BTCPay pairing QR
});
} else {
return ReactNativeBlobUtil.fetch('get', url)
.then((response: any) => {
const status = response.info().status;
if (status == 200) {
const data = response.json();
return [
'LnurlPay',
{
lnurlParams: data,
amount: setAmount
}
];
} else {
throw new Error(error);
}
})
.catch(() => {
throw new Error(error);
});
}
} else if (value.includes('config=') && value.includes('lnd.config')) {
// BTCPay pairing QR
if (isClipboardValue) return true;
return settingsStore
.fetchBTCPayConfig(value)
Expand Down Expand Up @@ -257,53 +276,81 @@ const handleAnything = async (
});
} else if (!!findlnurl(value) || !!lnurl) {
const raw: string = findlnurl(value) || lnurl || '';
return getlnurlParams(raw).then((params: any) => {
switch (params.tag) {
case 'withdrawRequest':
if (isClipboardValue) return true;
return [
'Receive',
{
lnurlParams: params
}
];
break;
case 'payRequest':
if (isClipboardValue) return true;
params.lnurlText = raw;
return [
'LnurlPay',
{
lnurlParams: params,
amount: setAmount
}
];
break;
case 'channelRequest':
if (isClipboardValue) return true;
return [
'LnurlChannel',
{
lnurlParams: params
}
];
break;
case 'login':
if (BackendUtils.supportsLnurlAuth()) {
return getlnurlParams(raw)
.then((params: any) => {
if (
params.status === 'ERROR' &&
params.domain.endsWith('.onion')
) {
// TODO handle fetching of params with internal Tor
throw new Error(`${params.domain} says: ${params.reason}`);
}

switch (params.tag) {
case 'withdrawRequest':
if (isClipboardValue) return true;
return [
'LnurlAuth',
'Receive',
{
lnurlParams: params
}
];
} else {
break;
case 'payRequest':
if (isClipboardValue) return true;
params.lnurlText = raw;
return [
'LnurlPay',
{
lnurlParams: params,
amount: setAmount
}
];
break;
case 'channelRequest':
if (isClipboardValue) return true;
return [
'LnurlChannel',
{
lnurlParams: params
}
];
break;
case 'login':
if (BackendUtils.supportsLnurlAuth()) {
if (isClipboardValue) return true;
return [
'LnurlAuth',
{
lnurlParams: params
}
];
} else {
if (isClipboardValue) return false;
Alert.alert(
localeString('general.error'),
localeString(
'utils.handleAnything.lnurlAuthNotSupported'
),
[
{
text: localeString('general.ok'),
onPress: () => void 0
}
],
{ cancelable: false }
);
}
break;
default:
if (isClipboardValue) return false;
Alert.alert(
localeString('general.error'),
localeString(
'utils.handleAnything.lnurlAuthNotSupported'
),
params.status === 'ERROR'
? `${params.domain} says: ${params.reason}`
: `${localeString(
'utils.handleAnything.unsupportedLnurlType'
)}: ${params.tag}`,
[
{
text: localeString('general.ok'),
Expand All @@ -312,27 +359,13 @@ const handleAnything = async (
],
{ cancelable: false }
);
}
break;
default:
if (isClipboardValue) return false;
Alert.alert(
localeString('general.error'),
params.status === 'ERROR'
? `${params.domain} says: ${params.reason}`
: `${localeString(
'utils.handleAnything.unsupportedLnurlType'
)}: ${params.tag}`,
[
{
text: localeString('general.ok'),
onPress: () => void 0
}
],
{ cancelable: false }
);
}
});
}
})
.catch(() => {
throw new Error(
localeString('utils.handleAnything.invalidLnurlParams')
);
});
} else {
if (isClipboardValue) return false;
throw new Error(localeString('utils.handleAnything.notValid'));
Expand Down
4 changes: 2 additions & 2 deletions views/NodeQRScanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ function NodeQRScanner(props: NodeQRProps) {
{ cancelable: false }
);

navigation.navigate('OpenChannel');
navigation.goBack();
}
};

return (
<QRCodeScanner
handleQRScanned={handleNodeScanned}
goBack={() => navigation.navigate('OpenChannel')}
goBack={() => navigation.goBack()}
navigation={navigation}
/>
);
Expand Down
47 changes: 43 additions & 4 deletions views/Send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import UTXOsStore from '../stores/UTXOsStore';
import Amount from '../components/Amount';
import AmountInput from '../components/AmountInput';
import Button from '../components/Button';
import LoadingIndicator from '../components/LoadingIndicator';
import { ErrorMessage } from '../components/SuccessErrorMessage';
import Header from '../components/Header';
import Screen from '../components/Screen';
Expand Down Expand Up @@ -77,6 +78,7 @@ interface SendState {
message: string;
enableAtomicMultiPathPayment: boolean;
clipboard: string;
loading: boolean;
}

@inject(
Expand Down Expand Up @@ -120,7 +122,8 @@ export default class Send extends React.Component<SendProps, SendState> {
feeLimitSat: '',
message: '',
enableAtomicMultiPathPayment: false,
clipboard: ''
clipboard: '',
loading: false
};
}

Expand Down Expand Up @@ -256,20 +259,27 @@ export default class Send extends React.Component<SendProps, SendState> {

validateAddress = (text: string) => {
const { navigation } = this.props;
this.setState({
loading: true
});
handleAnything(text, this.state.amount)
.then((response) => {
try {
const [route, props] = response;
navigation.navigate(route, props);
if (response) {
const [route, props] = response;
navigation.navigate(route, props);
}
} catch {
this.setState({
loading: false,
transactionType: null,
isValid: false
});
}
})
.catch((err) => {
this.setState({
loading: false,
transactionType: null,
isValid: false,
error_msg: err.message
Expand Down Expand Up @@ -376,7 +386,8 @@ export default class Send extends React.Component<SendProps, SendState> {
feeLimitSat,
message,
enableAtomicMultiPathPayment,
clipboard
clipboard,
loading
} = this.state;
const { confirmedBlockchainBalance } = BalanceStore;
const { implementation, settings } = SettingsStore;
Expand All @@ -394,6 +405,34 @@ export default class Send extends React.Component<SendProps, SendState> {
paymentOptions.push(localeString('views.Send.keysendAddress'));
}

if (loading) {
return (
<View
style={{
flex: 1,
backgroundColor: themeColor('background')
}}
>
<Header
centerComponent={{
text: localeString('general.loading'),
style: {
color: themeColor('text'),
fontFamily: 'Lato-Regular'
}
}}
backgroundColor={themeColor('background')}
containerStyle={{
borderBottomWidth: 0
}}
/>
<View style={{ top: 40 }}>
<LoadingIndicator />
</View>
</View>
);
}

return (
<Screen>
<Header
Expand Down
2 changes: 1 addition & 1 deletion views/SparkQRScanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default class SparkQRScanner extends React.Component<SparkQRProps, {}> {
{ cancelable: false }
);

navigation.navigate('Settings');
navigation.goBack();
}
};

Expand Down
Loading