diff --git a/locales/en.json b/locales/en.json
index cfc29899f..37cef0919 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -4,6 +4,7 @@
"general.request": "Request",
"general.scan": "Scan",
"general.enableNfc": "Enable NFC",
+ "general.receiveNfc": "Receive via NFC",
"general.confirm": "Confirm",
"general.cancel": "Cancel",
"general.warning": "Warning",
diff --git a/views/Receive.tsx b/views/Receive.tsx
index ee903f685..082835fea 100644
--- a/views/Receive.tsx
+++ b/views/Receive.tsx
@@ -7,7 +7,8 @@ import {
StyleSheet,
Text,
TouchableOpacity,
- View
+ View,
+ Platform
} from 'react-native';
import BigNumber from 'bignumber.js';
import { LNURLWithdrawParams } from 'js-lnurl';
@@ -15,6 +16,14 @@ import { ButtonGroup, Header, Icon } from 'react-native-elements';
import { inject, observer } from 'mobx-react';
import _map from 'lodash/map';
+import NfcManager, {
+ NfcEvents,
+ TagEvent,
+ Ndef
+} from 'react-native-nfc-manager';
+
+import handleAnything from './../utils/handleAnything';
+
import Success from '../assets/images/GIF/Success.gif';
import Amount from './../components/Amount';
@@ -37,6 +46,7 @@ import UnitsStore, { SATS_PER_BTC } from './../stores/UnitsStore';
import { localeString } from './../utils/LocaleUtils';
import BackendUtils from './../utils/BackendUtils';
+import NFCUtils from './../utils/NFCUtils';
import { themeColor } from './../utils/ThemeUtils';
interface ReceiveProps {
@@ -75,7 +85,7 @@ export default class Receive extends React.Component<
routeHints: false
};
- componentDidMount() {
+ async componentDidMount() {
const { navigation, InvoicesStore } = this.props;
const { reset } = InvoicesStore;
@@ -100,12 +110,32 @@ export default class Receive extends React.Component<
}
if (autoGenerate) this.autoGenerateInvoice(this.getSatAmount(amount));
+
+ if (Platform.OS === 'android') {
+ await this.enableNfc();
+ }
}
componentWillUnmount() {
if (this.listener && this.listener.stop) this.listener.stop();
}
+ UNSAFE_componentWillReceiveProps(nextProps: any) {
+ const { navigation, InvoicesStore } = nextProps;
+ const { reset } = InvoicesStore;
+
+ reset();
+ const lnurl: LNURLWithdrawParams | undefined =
+ navigation.getParam('lnurlParams');
+
+ if (lnurl) {
+ this.setState({
+ memo: lnurl.defaultDescription,
+ value: (lnurl.maxWithdrawable / 1000).toString()
+ });
+ }
+ }
+
autoGenerateInvoice = (amount?: string) => {
const { InvoicesStore } = this.props;
const { createUnifiedInvoice } = InvoicesStore;
@@ -123,6 +153,88 @@ export default class Receive extends React.Component<
).then((rHash: string) => this.subscribeInvoice(rHash));
};
+ disableNfc = () => {
+ NfcManager.setEventListener(NfcEvents.DiscoverTag, null);
+ NfcManager.setEventListener(NfcEvents.SessionClosed, null);
+ };
+
+ enableNfc = async () => {
+ this.disableNfc();
+ await NfcManager.start().catch((e) => console.warn(e.message));
+
+ return new Promise((resolve: any) => {
+ let tagFound: TagEvent | null = null;
+
+ NfcManager.setEventListener(
+ NfcEvents.DiscoverTag,
+ (tag: TagEvent) => {
+ tagFound = tag;
+ const bytes = new Uint8Array(
+ tagFound.ndefMessage[0].payload
+ );
+
+ let str;
+ const decoded = Ndef.text.decodePayload(bytes);
+ if (decoded.match(/^(https?|lnurl)/)) {
+ str = decoded;
+ } else {
+ str = NFCUtils.nfcUtf8ArrayToStr(bytes) || '';
+ }
+ resolve(this.validateAddress(str));
+ NfcManager.unregisterTagEvent().catch(() => 0);
+ }
+ );
+
+ NfcManager.setEventListener(NfcEvents.SessionClosed, () => {
+ if (!tagFound) {
+ resolve();
+ }
+ });
+
+ NfcManager.registerTagEvent();
+ });
+ };
+
+ validateAddress = (text: string) => {
+ const { navigation, InvoicesStore } = this.props;
+ const { createUnifiedInvoice } = InvoicesStore;
+ const amount = this.getSatAmount(navigation.getParam('amount'));
+
+ handleAnything(text, amount.toString())
+ .then((response) => {
+ try {
+ const [route, props] = response;
+ const { lnurlParams } = props;
+ const { memo } = lnurlParams.defaultDescription;
+
+ // if an amount was entered on the keypad screen before scanning
+ // we will automatically create an invoice and attempt to withdraw
+ // otherwise we present the user with the create invoice screen
+ if (Number(amount) > 0) {
+ createUnifiedInvoice(
+ memo,
+ amount.toString(),
+ '3600',
+ lnurlParams
+ )
+ .then((rHash: string) => {
+ navigation.setParam;
+ this.subscribeInvoice(rHash);
+ })
+ .catch(() => {
+ navigation.navigate(route, {
+ amount,
+ ...props
+ });
+ });
+ } else {
+ navigation.navigate(route, props);
+ }
+ } catch (e) {}
+ })
+ .catch();
+ };
+
subscribeInvoice = (rHash: string) => {
const { InvoicesStore, SettingsStore } = this.props;
const { implementation } = SettingsStore;
@@ -552,6 +664,26 @@ export default class Receive extends React.Component<
textBottom
/>
)}
+ {Platform.OS === 'ios' && (
+
+
+ )}
{!belowDustLimit && haveUnifiedInvoice && (