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

Update UI to handle encoded LNURL #324

Merged
merged 9 commits into from
Mar 27, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
node-version: [18.x, 20.x, 21.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
Expand Down
2 changes: 1 addition & 1 deletion assets/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@
"invoiceHint": "Das kann einige Sekunden dauern...",
"lowBal": "Kein Guthaben verfügbar",
"meltAddressbookHint": "Wählen Sie Ihre eigene LNURL oder einen anderen Kontakt als Zahlungsempfänger aus.",
"meltInputHint": "Erstellen Sie eine Lightning-Rechnung oder geben Sie eine LNURL ein.",
"meltInputHint": "Fügen Sie eine Lightning-Rechnung, LNURL oder Lightning Adresse ein.",
"meltScanQRHint": "Erstellen Sie eine Lightning-Rechnung mit einem anderen Gerät und scannen Sie sie einfach.",
"meltSwapHint": "Wählen Sie eine andere Mint aus Ihrer vertrauenswürdigen Liste als Zahlungsempfänger aus.",
"copyShareToken": "Kopieren & teilen",
Expand Down
2 changes: 1 addition & 1 deletion assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@
"invoiceHint": "This can take a few seconds...",
"lowBal": "Mint balance too low!",
"meltAddressbookHint": "Choose your own LNURL or any other contact as a payment receiver.",
"meltInputHint": "Create a Lightning invoice or paste a LNURL into an input field.",
"meltInputHint": "Paste Lightning invoice, LN-URL or Lightning address.",
"meltScanQRHint": "Create a Lightning invoice with another device and simply scan it.",
"meltSwapHint": "Pick another mint from your trusted list as the payment receiver.",
"copyShareToken": "Copy & quickshare",
Expand Down
2 changes: 1 addition & 1 deletion assets/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@
"invoiceHint": "Esto puede llevar unos segundos...",
"lowBal": "¡Saldo de la ceca muy bajo!",
"meltAddressbookHint": "Elige tu propia LNURL o cualquier otro contacto como receptor del pago.",
"meltInputHint": "Crea un recibo Lightning o pega una LNURL en el campo de entrada.",
"meltInputHint": "Pega la factura de Lightning, la LN-URL o la dirección de Lightning.",
"meltScanQRHint": "Crea un recibo Lightning con otro dispositivo y simplemente escanéalo.",
"meltSwapHint": "Elige otra ceca de tu lista de confianza como receptora del pago.",
"copyShareToken": "Copiar y compartir rápido",
Expand Down
2 changes: 1 addition & 1 deletion assets/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@
"invoiceHint": "Cela peut prendre quelques secondes...",
"lowBal": "Solde trop bas!",
"meltAddressbookHint": "Sélectionnez votre propre LNURL ou tout autre contact comme destinataire du paiement.",
"meltInputHint": "Créez une facture Lightning ou insérez une LNURL valide.",
"meltInputHint": "Collez une facture Lightning, LN-URL ou l'adresse Lightning.",
"meltScanQRHint": "Créez une facture Lightning avec un autre appareil et scannez-la simplement.",
"meltSwapHint": "Sélectionnez une autre mint de votre liste de confiance comme destinataire du paiement.",
"copyShareToken": "Copier et partager",
Expand Down
2 changes: 1 addition & 1 deletion assets/translations/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@
"invoiceHint": "Ez eltarthat pár másodpercig...",
"lowBal": "Verde egyenlege túl alacsony!",
"meltAddressbookHint": "Válaszd ki a saját LNURL-ed vagy bármelyik névjegyed a fizetés fogadójának.",
"meltInputHint": "Hozz létre egy Lightning számlát vagy illessz be egy LNURL-t a bemeneti mezőbe.",
"meltInputHint": "Illessze be a Lightning számlát, az LN-URL vagy a Lightning címet.",
"meltScanQRHint": "Hozz létre egy Lightning számlát egy másik eszközön és csak olvasd be.",
"meltSwapHint": "Válassz egy másik verdét a listádról a fizetés fogadójának.",
"copyShareToken": "Másol & megosztás",
Expand Down
2 changes: 1 addition & 1 deletion assets/translations/sw.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@
"invoiceHint": "Hii inaweza kuchukua sekunde kadhaa...",
"lowBal": "Salio la sarafu limepungua sana!",
"meltAddressbookHint": "Chagua LNURL yako mwenyewe au mawasiliano mengine yoyote kama mpokeaji wa malipo.",
"meltInputHint": "Unda ankara ya Lightning au weka LNURL kwenye uga wa kuingiza.",
"meltInputHint": "Weka ankara ya Lightning, LN-URL au anwani ya Lightning.",
"meltScanQRHint": "Unda ankara ya Lightning kwa kutumia kifaa kingine na ui-scan tu.",
"meltSwapHint": "Chagua sarafu nyingine kutoka kwa orodha yako ya sarafu za kuaminika kama mpokeaji wa malipo.",
"copyShareToken": "Nakili & Shiriki kwa haraka",
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

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

9 changes: 9 additions & 0 deletions src/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ export interface ILnUrl {
callback: string
pr: string
}

// TODO This interface is missing some properties?
export interface ILnUrlPayRequest {
tag: string
cb: string
minSendable: number
maxSendable: number
metadata: string
}
export interface IMint {
id: string
mintUrl: string
Expand Down
19 changes: 17 additions & 2 deletions src/model/nav.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import type { EventArg } from '@react-navigation/core'
import type { NativeStackScreenProps } from '@react-navigation/native-stack'

import type { IHistoryEntry, IMintUrl, IMintWithBalance, IProofSelection, ITokenInfo } from '.'
import type { IHistoryEntry, ILnUrlPayRequest, IMintUrl, IMintWithBalance, IProofSelection, ITokenInfo } from '.'
import type { HexKey, IContact } from './nostr'

export interface INostrSendData {
senderName: string
contact?: IContact
}

interface ILnurlNavData {
userInput: string
url?: string
data?: ILnUrlPayRequest
}
/**
* Stack Navigator
*/
Expand Down Expand Up @@ -48,6 +54,7 @@ export type RootStackParamList = {
invoice?: string
invoiceAmount?: number
estFee?: number
lnurl?: ILnurlNavData
scanned?: boolean
},
selectTarget: {
Expand All @@ -74,8 +81,9 @@ export type RootStackParamList = {
nostr?: INostrSendData
isSwap?: boolean
balance: number
lnurl?: string
lnurl?: ILnurlNavData
targetMint?: IMintUrl
scanned?: boolean
}
selectNostrAmount: {
mint: IMintUrl
Expand Down Expand Up @@ -118,12 +126,19 @@ export type RootStackParamList = {
'qr processing': {
tokenInfo?: ITokenInfo
token?: string
scanned?: boolean
ln?: {
invoice: string
mint?: IMintUrl
balance?: number
amount: number
}
lnurl?: {
mint?: IMintUrl
balance?: number
url: string
data: string
}
}
'mint confirm': {
mintUrl: string
Expand Down
4 changes: 2 additions & 2 deletions src/nostr/consts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Npub } from '@src/model/nostr'
import { isUrl } from '@src/util'
import type { Npub } from '@model/nostr'
import { isUrl } from '@util/lnurl'

import { normalizeURL } from './util'

Expand Down
3 changes: 1 addition & 2 deletions src/screens/Addressbook/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,7 @@ export default function AddressbookPage({ navigation, route }: TAddressBookPageP
openPromptAutoClose({ msg: t('receiverNoLnurl', { ns: NS.addrBook }) })
return
}
navigation.navigate('selectAmount', { isMelt, lnurl: contact.lud16, mint, balance })
return
return navigation.navigate('selectAmount', { isMelt, lnurl: { userInput: contact.lud16 }, mint, balance })
}
if (!nostrRef.current) { return }
// mint has already been selected
Expand Down
5 changes: 3 additions & 2 deletions src/screens/Payment/Processing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { useInitialURL } from '@src/context/Linking'
import { useNostrContext } from '@src/context/Nostr'
import { useThemeContext } from '@src/context/Theme'
import { NS } from '@src/i18n'
import { isLnurlOrAddress } from '@src/util/lnurl'
import { addLnPaymentToHistory } from '@store/HistoryStore'
import { addToHistory, updateLatestHistory } from '@store/latestHistoryEntries'
import { getDefaultMint } from '@store/mintStore'
import { globals } from '@styles'
import { decodeLnInvoice, getInvoiceFromLnurl, isErr, isLnurlOrAddress, isNum, uniqByIContacts } from '@util'
import { decodeLnInvoice, getInvoiceFromLnurl, isErr, 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 @@ -98,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
// recipient can be a LNURL or a LN invoice
if (recipient?.length && isLnurlOrAddress(recipient)) {
try {
invoice = await getInvoiceFromLnurl(recipient, +amount)
Expand Down
39 changes: 28 additions & 11 deletions src/screens/Payment/SelectAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ import { usePromptContext } from '@src/context/Prompt'
import { useThemeContext } from '@src/context/Theme'
import { NS } from '@src/i18n'
import { globals, highlight as hi, mainColors } from '@styles'
import { cleanUpNumericStr, formatSatStr, getInvoiceFromLnurl, vib } from '@util'
import { cleanUpNumericStr, formatInt, formatSatStr, getInvoiceFromLnurl, vib } from '@util'
import { getLnurlIdentifierFromMetadata, isLightningAddress } from '@util/lnurl'
import { checkFees, requestMint } from '@wallet'
import { createRef, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Animated, KeyboardAvoidingView, TextInput, View } from 'react-native'
import { s, ScaledSheet, vs } from 'react-native-size-matters'

export default function SelectAmountScreen({ navigation, route }: TSelectAmountPageProps) {
const { mint, balance, lnurl, isMelt, isSendEcash, nostr, isSwap, targetMint } = route.params
const { mint, balance, lnurl, isMelt, isSendEcash, nostr, isSwap, targetMint, scanned } = route.params
const { openPromptAutoClose } = usePromptContext()
const { t } = useTranslation([NS.wallet])
const { color, highlight } = useThemeContext()
Expand Down Expand Up @@ -53,14 +54,14 @@ export default function SelectAmountScreen({ navigation, route }: TSelectAmountP
return 'createInvoice'
}

const handleFeeEstimation = async (lnurl: string) => {
const handleFeeEstimation = async () => {
setFee(prev => ({ ...prev, isCalculating: true }))
try {
// check fee for payment to lnurl
if (lnurl.length) {
const lnurlInvoice = await getInvoiceFromLnurl(lnurl, +amount)
if (lnurl) {
const lnurlInvoice = await getInvoiceFromLnurl(lnurl.userInput, +amount)
if (!lnurlInvoice?.length) {
openPromptAutoClose({ msg: t('feeErr', { ns: NS.common, input: lnurl }) })
openPromptAutoClose({ msg: t('feeErr', { ns: NS.common, input: lnurl.url }) })
return setFee(prev => ({ ...prev, isCalculating: false }))
}
const estFee = await checkFees(mint.mintUrl, lnurlInvoice)
Expand Down Expand Up @@ -106,11 +107,12 @@ export default function SelectAmountScreen({ navigation, route }: TSelectAmountP
return
}
// estimate melting/swap fee
if (!isSendEcash && shouldEstimate && (lnurl?.length || isSwap)) {
return handleFeeEstimation(lnurl || '')
if (!isSendEcash && shouldEstimate && (lnurl || isSwap)) {
return handleFeeEstimation()
}
// send ecash / melt / swap
if (isSendingTX) {
const recipient = isLightningAddress(lnurl?.userInput || '') ? lnurl?.userInput : lnurl?.data ? getLnurlIdentifierFromMetadata(lnurl.data?.metadata) : undefined
// Check if user melts/swaps his whole mint balance, so there is no need for coin selection and that can be skipped here
if (!isSendEcash && isSendingWholeMintBal()) {
return navigation.navigate('processing', {
Expand All @@ -121,7 +123,7 @@ export default function SelectAmountScreen({ navigation, route }: TSelectAmountP
isSendEcash,
isSwap,
targetMint,
recipient: lnurl
recipient
})
}
return navigation.navigate('coinSelection', {
Expand All @@ -135,7 +137,7 @@ export default function SelectAmountScreen({ navigation, route }: TSelectAmountP
isSendEcash,
isSwap,
targetMint,
recipient: lnurl
recipient
})
}
// request new token from mint
Expand Down Expand Up @@ -169,7 +171,7 @@ export default function SelectAmountScreen({ navigation, route }: TSelectAmountP
<Screen
screenName={t(getScreenName(), { ns: NS.common })}
withBackBtn
handlePress={() => navigation.goBack()}
handlePress={() => scanned ? navigation.navigate('qr scan', {}) : navigation.goBack()}
mintBalance={balance}
disableMintBalance={isMelt || isSwap}
handleMintBalancePress={() => setAmount(`${balance}`)}
Expand All @@ -181,6 +183,21 @@ export default function SelectAmountScreen({ navigation, route }: TSelectAmountP
/>
}
<View style={[styles.overviewWrap, { marginTop: isMelt || isSwap ? 0 : vs(20) }]}>
{lnurl && (lnurl.data || lnurl.userInput) &&
<Txt
txt={
isLightningAddress(lnurl.userInput) ?
lnurl.userInput
:
lnurl.data ?
`${getLnurlIdentifierFromMetadata(lnurl.data.metadata)} requests ${lnurl.data.minSendable / 1000} to ${formatInt(lnurl.data.maxSendable / 1000)} Sats.`
:
''
}
bold
styles={[styles.sats, { marginBottom: vs(5), fontSize: s(10) }]}
/>
}
<Animated.View style={[styles.amountWrap, { transform: [{ translateX: anim.current }] }]}>
<TextInput
keyboardType='numeric'
Expand Down
5 changes: 3 additions & 2 deletions src/screens/Payment/Send/CoinSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ 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, isLnurlOrAddress, isNum } from '@util'
import { formatInt, formatMintUrl, formatSatStr, getSelectedAmount, isNum } from '@util'
import { isLightningAddress } from '@util/lnurl'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ScrollView, View } from 'react-native'
Expand Down Expand Up @@ -60,7 +61,7 @@ export default function CoinSelectionScreen({ navigation, route }: TCoinSelectio

const getRecipient = () => {
if (recipient) {
return !isLnurlOrAddress(recipient) ? truncateStr(recipient, 16) : recipient
return !isLightningAddress(recipient) ? truncateStr(recipient, 16) : recipient
}
const npub = npubEncode(nostr?.contact?.hex ?? '')
const receiverName = getNostrUsername(nostr?.contact)
Expand Down
17 changes: 13 additions & 4 deletions src/screens/Payment/Send/Inputfield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ 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, isLnurlOrAddress, openUrl } from '@util'
import { decodeLnInvoice, getStrFromClipboard, isErr, openUrl } from '@util'
import { decodeUrlOrAddress, getLnurlData, isLnurlOrAddress } from '@util/lnurl'
import { checkFees } from '@wallet'
import { createRef, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand Down Expand Up @@ -65,16 +66,24 @@ export default function InputfieldScreen({ navigation, route }: TMeltInputfieldP
}
}

const handleBtnPress = () => {
const handleBtnPress = async () => {
if (loading) { return }
// open user LN wallet
if (!input.length) {
return openUrl('lightning://')?.catch(e =>
openPromptAutoClose({ msg: isErr(e) ? e.message : t('deepLinkErr') }))
}
// user pasted a LNURL, we need to get the amount by the user
// user pasted an encoded LNURL, we need to get the amount by the user
if (isLnurlOrAddress(input)) {
return navigation.navigate('selectAmount', { mint, balance, isMelt: true, lnurl: input })
const decoded = decodeUrlOrAddress(input)
if (!decoded) { return openPromptAutoClose({ msg: 'Could not decode LNURL!' }) }
try {
const lnurlData = await getLnurlData(decoded)
if (!lnurlData) { return openPromptAutoClose({ msg: 'Could not fetch data from LNURL' }) }
return navigation.navigate('selectAmount', { mint, balance, isMelt: true, lnurl: { userInput: input, url: decoded, data: lnurlData } })
} catch (e) {
return openPromptAutoClose({ msg: 'Could not fetch data from LNURL' })
}
}
// not enough funds
if (decodedAmount + estFee > balance) {
Expand Down
2 changes: 1 addition & 1 deletion src/screens/Payment/Send/SelectTarget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default function SelectTargetScreen({ navigation, route }: TSelectTargetP
mint,
balance,
isMelt: true,
lnurl: lud16
lnurl: { userInput: lud16 }
})
}}
hasSeparator
Expand Down
Loading
Loading