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

Added support for Lnurl-Pay through copy/paste and QR code scan #323

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 21 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@react-navigation/native-stack": "^6.9.17",
"@sentry/react-native": "5.10.0",
"@shopify/flash-list": "1.4.3",
"bech32": "^2.0.0",
"crypto-js": "4.2.0",
"expo": "^49.0.21",
"expo-application": "~5.3.0",
Expand Down
4 changes: 2 additions & 2 deletions src/screens/Payment/Processing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { addLnPaymentToHistory } from '@store/HistoryStore'
import { addToHistory, updateLatestHistory } from '@store/latestHistoryEntries'
import { getDefaultMint } from '@store/mintStore'
import { globals } from '@styles'
import { decodeLnInvoice, getInvoiceFromLnurl, isErr, isLnurl, isNum, uniqByIContacts } from '@util'
import { decodeLnInvoice, getInvoiceFromLnurl, isErr, isLnurlOrAddress, isNum, uniqByIContacts } from '@util'
import { autoMintSwap, checkFees, fullAutoMintSwap, getHighestBalMint, payLnInvoice, requestMint, sendToken } from '@wallet'
import { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
Expand Down Expand Up @@ -99,7 +99,7 @@ export default function ProcessingScreen({ navigation, route }: TProcessingPageP
const handleMelting = async () => {
let invoice = ''
// recipient can be a LNURL (address) or a LN invoice
if (recipient?.length && isLnurl(recipient)) {
if (recipient?.length && isLnurlOrAddress(recipient)) {
try {
invoice = await getInvoiceFromLnurl(recipient, +amount)
if (!invoice?.length) {
Expand Down
4 changes: 2 additions & 2 deletions src/screens/Payment/Send/CoinSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useInitialURL } from '@src/context/Linking'
import { useThemeContext } from '@src/context/Theme'
import { NS } from '@src/i18n'
import { globals } from '@styles'
import { formatInt, formatMintUrl, formatSatStr, getSelectedAmount, isLnurl, isNum } from '@util'
import { formatInt, formatMintUrl, formatSatStr, getSelectedAmount, isLnurlOrAddress, isNum } from '@util'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ScrollView, View } from 'react-native'
Expand Down Expand Up @@ -60,7 +60,7 @@ export default function CoinSelectionScreen({ navigation, route }: TCoinSelectio

const getRecipient = () => {
if (recipient) {
return !isLnurl(recipient) ? truncateStr(recipient, 16) : recipient
return !isLnurlOrAddress(recipient) ? truncateStr(recipient, 16) : recipient
}
const npub = npubEncode(nostr?.contact?.hex ?? '')
const receiverName = getNostrUsername(nostr?.contact)
Expand Down
8 changes: 5 additions & 3 deletions src/screens/Payment/Send/Inputfield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { usePromptContext } from '@src/context/Prompt'
import { useThemeContext } from '@src/context/Theme'
import { NS } from '@src/i18n'
import { globals } from '@styles'
import { decodeLnInvoice, getStrFromClipboard, isErr, isLnInvoice, isLnurl, openUrl } from '@util'
import { decodeLnInvoice, getStrFromClipboard, isErr, isLnurlOrAddress, openUrl } from '@util'
import { checkFees } from '@wallet'
import { createRef, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand Down Expand Up @@ -43,7 +43,7 @@ export default function InputfieldScreen({ navigation, route }: TMeltInputfieldP
if (!clipboard) { return }
setInput(clipboard)
// pasted LNURL address which does not need decoding
if (isLnurl(clipboard)) { return }
if (isLnurlOrAddress(clipboard)) { return }
// pasted LN invoice
await handleInvoicePaste(clipboard)
}
Expand Down Expand Up @@ -73,7 +73,7 @@ export default function InputfieldScreen({ navigation, route }: TMeltInputfieldP
openPromptAutoClose({ msg: isErr(e) ? e.message : t('deepLinkErr') }))
}
// user pasted a LNURL, we need to get the amount by the user
if (isLnurl(input)) {
if (isLnurlOrAddress(input)) {
return navigation.navigate('selectAmount', { mint, balance, isMelt: true, lnurl: input })
}
// not enough funds
Expand Down Expand Up @@ -165,9 +165,11 @@ export default function InputfieldScreen({ navigation, route }: TMeltInputfieldP
value={input}
onChangeText={text => {
setInput(text)
/* Handle when the continue button is pressed
if (isLnInvoice(text)) {
void handleInvoicePaste(text)
}
*/
}}
onSubmitEditing={() => void handleBtnPress()}
autoFocus
Expand Down
30 changes: 26 additions & 4 deletions src/screens/QRScan/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import useLoading from '@comps/hooks/Loading'
import useCashuToken from '@comps/hooks/Token'
import { CloseIcon, FlashlightOffIcon } from '@comps/Icons'
import { isIOS, QRType } from '@consts'
import { addMint, getMintsUrls } from '@db'
import { addMint, getMintsBalances, getMintsUrls } from '@db'
import TrustMintModal from '@modal/TrustMint'
import type { TQRScanPageProps } from '@model/nav'
import { isNProfile, isNpubQR } from '@nostr/util'
import { useIsFocused } from '@react-navigation/core'
import { usePromptContext } from '@src/context/Prompt'
import { useThemeContext } from '@src/context/Theme'
import { NS } from '@src/i18n'
import { getDefaultMint } from '@store/mintStore'
import { getCustomMintNames, getDefaultMint } from '@store/mintStore'
import { globals, mainColors } from '@styles'
import { decodeLnInvoice, extractStrFromURL, hasTrustedMint, isCashuToken, isNull, isStr, isUrl } from '@util'
import { decodeLnInvoice, extractStrFromURL, hasTrustedMint, isCashuToken, isLnurlOrAddress, isNull, isStr, isUrl } from '@util'
import { getTokenInfo } from '@wallet/proofs'
import { BarCodeScanner, PermissionStatus } from 'expo-barcode-scanner'
import { Camera, FlashMode } from 'expo-camera'
Expand Down Expand Up @@ -82,7 +82,7 @@ export default function QRScanPage({ navigation, route }: TQRScanPageProps) {
navigation.navigate('qr processing', { tokenInfo, token })
}

const handleBarCodeScanned = ({ type, data }: { type: string, data: string }) => {
const handleBarCodeScanned = async ({ type, data }: { type: string, data: string }) => {
setScanned(true)
const bcType = isIOS ? 'org.iso.QRCode' : +QRType
// early return if barcode is not a QR
Expand Down Expand Up @@ -113,6 +113,28 @@ export default function QRScanPage({ navigation, route }: TQRScanPageProps) {
if (isUrl(data) && new URL(data).protocol === 'https:') {
return navigation.navigate('mint confirm', { mintUrl: data })
}
// handle LNURL

if (isLnurlOrAddress(data) ) {

if (mint === undefined || balance === undefined) {

// user has not selected the mint yet (Pressed scan QR and scanned a Lightning invoice)
const mintsWithBal = await getMintsBalances()
const mints = await getCustomMintNames(mintsWithBal.map(m => ({ mintUrl: m.mintUrl })))
const nonEmptyMint = mintsWithBal.filter(m => m.amount > 0)
const mintUsing = mints.find(m => m.mintUrl === nonEmptyMint[0].mintUrl) || { mintUrl: 'N/A', customName: 'N/A' }

return navigation.navigate('selectAmount', { mint:mintUsing, balance:nonEmptyMint[0].amount, isMelt: true, lnurl: data })


}

return navigation.navigate('selectAmount', { mint, balance, isMelt: true, lnurl: data })


}

// handle LN invoice
try {
const invoice = extractStrFromURL(data) || data
Expand Down