diff --git a/class/lnurl.js b/class/lnurl.ts similarity index 63% rename from class/lnurl.js rename to class/lnurl.ts index 75972c65d..1435d66b3 100644 --- a/class/lnurl.js +++ b/class/lnurl.ts @@ -3,11 +3,51 @@ import bolt11 from 'bolt11'; import createHash from 'create-hash'; import { createHmac } from 'crypto'; import CryptoJS from 'crypto-js'; +// @ts-ignore theres no types for secp256k1 import secp256k1 from 'secp256k1'; import { parse } from 'url'; // eslint-disable-line n/no-deprecated-api const ONION_REGEX = /^(http:\/\/[^/:@]+\.onion(?::\d{1,5})?)(\/.*)?$/; // regex for onion URL +interface LnurlPayServicePayload { + callback: string; + fixed: boolean; + min: number; + max: number; + domain: string; + metadata: string; + description?: string; + image?: string; + amount: number; + commentAllowed?: number; +} + +interface LnurlPayServiceBolt11Payload { + pr: string; + successAction?: any; + disposable?: boolean; + tag: string; + metadata: any; + minSendable: number; + maxSendable: number; + callback: string; + commentAllowed: number; +} + +interface DecodedInvoice { + destination: string; + num_satoshis: string; + num_millisatoshis: string; + timestamp: string; + fallback_addr: string; + route_hints: any[]; + payment_hash?: string; + description_hash?: string; + cltv_expiry?: string; + expiry?: string; + description?: string; +} + /** * @see https://github.com/btcontract/lnurl-rfc/blob/master/lnurl-pay.md */ @@ -16,15 +56,21 @@ export default class Lnurl { static TAG_WITHDRAW_REQUEST = 'withdrawRequest'; // type of LNURL static TAG_LOGIN_REQUEST = 'login'; // type of LNURL - constructor(url, AsyncStorage) { - this._lnurl = url; + private _lnurl: string; + private _lnurlPayServiceBolt11Payload: LnurlPayServiceBolt11Payload | false; + private _lnurlPayServicePayload: LnurlPayServicePayload | false; + private _AsyncStorage: any; + private _preimage: string | false; + + constructor(url: string | false, AsyncStorage?: any) { + this._lnurl = url || ''; this._lnurlPayServiceBolt11Payload = false; this._lnurlPayServicePayload = false; this._AsyncStorage = AsyncStorage; this._preimage = false; } - static findlnurl(bodyOfText) { + static findlnurl(bodyOfText: string): string | null { const res = /^(?:http.*[&?]lightning=|lightning:)?(lnurl1[02-9ac-hj-np-z]+)/.exec(bodyOfText.toLowerCase()); if (res) { return res[1]; @@ -32,7 +78,7 @@ export default class Lnurl { return null; } - static getUrlFromLnurl(lnurlExample) { + static getUrlFromLnurl(lnurlExample: string): string | false { const found = Lnurl.findlnurl(lnurlExample); if (!found) { if (Lnurl.isLightningAddress(lnurlExample)) { @@ -49,22 +95,22 @@ export default class Lnurl { return Buffer.from(bech32.fromWords(decoded.words)).toString(); } - static isLnurl(url) { + static isLnurl(url: string): boolean { return Lnurl.findlnurl(url) !== null; } - static isOnionUrl(url) { + static isOnionUrl(url: string): boolean { return Lnurl.parseOnionUrl(url) !== null; } - static parseOnionUrl(url) { + static parseOnionUrl(url: string): [string, string] | null { const match = url.match(ONION_REGEX); if (match === null) return null; const [, baseURI, path] = match; return [baseURI, path]; } - async fetchGet(url) { + async fetchGet(url: string): Promise { const resp = await fetch(url, { method: 'GET' }); if (resp.status >= 300) { throw new Error('Bad response from server'); @@ -76,14 +122,14 @@ export default class Lnurl { return reply; } - decodeInvoice(invoice) { + decodeInvoice(invoice: string): DecodedInvoice { const { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice); - const decoded = { - destination: payeeNodeKey, + const decoded: DecodedInvoice = { + destination: payeeNodeKey ?? '', num_satoshis: satoshis ? satoshis.toString() : '0', num_millisatoshis: millisatoshis ? millisatoshis.toString() : '0', - timestamp: timestamp.toString(), + timestamp: timestamp?.toString() ?? '', fallback_addr: '', route_hints: [], }; @@ -92,10 +138,10 @@ export default class Lnurl { const { tagName, data } = tags[i]; switch (tagName) { case 'payment_hash': - decoded.payment_hash = data; + decoded.payment_hash = String(data); break; case 'purpose_commit_hash': - decoded.description_hash = data; + decoded.description_hash = String(data); break; case 'min_final_cltv_expiry': decoded.cltv_expiry = data.toString(); @@ -104,21 +150,21 @@ export default class Lnurl { decoded.expiry = data.toString(); break; case 'description': - decoded.description = data; + decoded.description = String(data); break; } } if (!decoded.expiry) decoded.expiry = '3600'; // default - if (parseInt(decoded.num_satoshis, 10) === 0 && decoded.num_millisatoshis > 0) { - decoded.num_satoshis = (decoded.num_millisatoshis / 1000).toString(); + if (parseInt(decoded.num_satoshis, 10) === 0 && parseInt(decoded.num_millisatoshis, 10) > 0) { + decoded.num_satoshis = (parseInt(decoded.num_millisatoshis, 10) / 1000).toString(); } return decoded; } - async requestBolt11FromLnurlPayService(amountSat, comment = '') { + async requestBolt11FromLnurlPayService(amountSat: number, comment: string = ''): Promise { if (!this._lnurlPayServicePayload) throw new Error('this._lnurlPayServicePayload is not set'); if (!this._lnurlPayServicePayload.callback) throw new Error('this._lnurlPayServicePayload.callback is not set'); if (amountSat < this._lnurlPayServicePayload.min || amountSat > this._lnurlPayServicePayload.max) @@ -132,15 +178,13 @@ export default class Lnurl { ); const nonce = Math.floor(Math.random() * 2e16).toString(16); const separator = this._lnurlPayServicePayload.callback.indexOf('?') === -1 ? '?' : '&'; - if (this.getCommentAllowed() && comment && comment.length > this.getCommentAllowed()) { - comment = comment.substr(0, this.getCommentAllowed()); + if (this.getCommentAllowed() && comment && comment.length > (this.getCommentAllowed() as number)) { + comment = comment.substr(0, this.getCommentAllowed() as number); } if (comment) comment = `&comment=${encodeURIComponent(comment)}`; const urlToFetch = this._lnurlPayServicePayload.callback + separator + 'amount=' + Math.floor(amountSat * 1000) + '&nonce=' + nonce + comment; - this._lnurlPayServiceBolt11Payload = await this.fetchGet(urlToFetch); - if (this._lnurlPayServiceBolt11Payload.status === 'ERROR') - throw new Error(this._lnurlPayServiceBolt11Payload.reason || 'requestBolt11FromLnurlPayService() error'); + this._lnurlPayServiceBolt11Payload = (await this.fetchGet(urlToFetch)) as LnurlPayServiceBolt11Payload; // check pr description_hash, amount etc: const decoded = this.decodeInvoice(this._lnurlPayServiceBolt11Payload.pr); @@ -155,11 +199,12 @@ export default class Lnurl { return this._lnurlPayServiceBolt11Payload; } - async callLnurlPayService() { + async callLnurlPayService(): Promise { if (!this._lnurl) throw new Error('this._lnurl is not set'); const url = Lnurl.getUrlFromLnurl(this._lnurl); + if (!url) throw new Error('Invalid LNURL'); // calling the url - const reply = await this.fetchGet(url); + const reply = (await this.fetchGet(url)) as LnurlPayServiceBolt11Payload; if (reply.tag !== Lnurl.TAG_PAY_REQUEST) { throw new Error('lnurl-pay expected, found tag ' + reply.tag); @@ -168,8 +213,8 @@ export default class Lnurl { const data = reply; // parse metadata and extract things from it - let image; - let description; + let image: string | undefined; + let description: string | undefined; const kvs = JSON.parse(data.metadata); for (let i = 0; i < kvs.length; i++) { const [k, v] = kvs[i]; @@ -185,14 +230,15 @@ export default class Lnurl { } // setting the payment screen with the parameters - const min = Math.ceil((data.minSendable || 0) / 1000); - const max = Math.floor(data.maxSendable / 1000); + const min = Math.ceil((data.minSendable ?? 0) / 1000); + const max = Math.floor((data.maxSendable ?? 0) / 1000); this._lnurlPayServicePayload = { callback: data.callback, fixed: min === max, min, max, + // @ts-ignore idk domain: data.callback.match(/^(https|http):\/\/([^/]+)\//)[2], metadata: data.metadata, description, @@ -204,7 +250,7 @@ export default class Lnurl { return this._lnurlPayServicePayload; } - async loadSuccessfulPayment(paymentHash) { + async loadSuccessfulPayment(paymentHash: string): Promise { if (!paymentHash) throw new Error('No paymentHash provided'); let data; try { @@ -224,7 +270,7 @@ export default class Lnurl { return true; } - async storeSuccess(paymentHash, preimage) { + async storeSuccess(paymentHash: string, preimage: string | { data: Buffer }): Promise { if (typeof preimage === 'object') { preimage = Buffer.from(preimage.data).toString('hex'); } @@ -241,35 +287,39 @@ export default class Lnurl { ); } - getSuccessAction() { - return this._lnurlPayServiceBolt11Payload.successAction; + getSuccessAction(): any | undefined { + return this._lnurlPayServiceBolt11Payload && 'successAction' in this._lnurlPayServiceBolt11Payload + ? this._lnurlPayServiceBolt11Payload.successAction + : undefined; } - getDomain() { - return this._lnurlPayServicePayload.domain; + getDomain(): string | undefined { + return this._lnurlPayServicePayload ? this._lnurlPayServicePayload.domain : undefined; } - getDescription() { - return this._lnurlPayServicePayload.description; + getDescription(): string | undefined { + return this._lnurlPayServicePayload ? this._lnurlPayServicePayload.description : undefined; } - getImage() { - return this._lnurlPayServicePayload.image; + getImage(): string | undefined { + return this._lnurlPayServicePayload ? this._lnurlPayServicePayload.image : undefined; } - getLnurl() { + getLnurl(): string { return this._lnurl; } - getDisposable() { - return this._lnurlPayServiceBolt11Payload.disposable; + getDisposable(): boolean | undefined { + return this._lnurlPayServiceBolt11Payload && 'disposable' in this._lnurlPayServiceBolt11Payload + ? this._lnurlPayServiceBolt11Payload.disposable + : undefined; } - getPreimage() { + getPreimage(): string | false { return this._preimage; } - static decipherAES(ciphertextBase64, preimageHex, ivBase64) { + static decipherAES(ciphertextBase64: string, preimageHex: string, ivBase64: string): string { const iv = CryptoJS.enc.Base64.parse(ivBase64); const key = CryptoJS.enc.Hex.parse(preimageHex); return CryptoJS.AES.decrypt(Buffer.from(ciphertextBase64, 'base64').toString('hex'), key, { @@ -279,27 +329,30 @@ export default class Lnurl { }).toString(CryptoJS.enc.Utf8); } - getCommentAllowed() { - return this?._lnurlPayServicePayload?.commentAllowed ? parseInt(this._lnurlPayServicePayload.commentAllowed, 10) : false; + getCommentAllowed(): number | false { + if (!this._lnurlPayServicePayload) return false; + return this._lnurlPayServicePayload.commentAllowed ? parseInt(this._lnurlPayServicePayload.commentAllowed.toString(), 10) : false; } - getMin() { - return this?._lnurlPayServicePayload?.min ? parseInt(this._lnurlPayServicePayload.min, 10) : false; + getMin(): number | false { + if (!this._lnurlPayServicePayload) return false; + return this._lnurlPayServicePayload.min ? parseInt(this._lnurlPayServicePayload.min.toString(), 10) : false; } - getMax() { - return this?._lnurlPayServicePayload?.max ? parseInt(this._lnurlPayServicePayload.max, 10) : false; + getMax(): number | false { + if (!this._lnurlPayServicePayload) return false; + return this._lnurlPayServicePayload.max ? parseInt(this._lnurlPayServicePayload.max.toString(), 10) : false; } - getAmount() { + getAmount(): number | false { return this.getMin(); } - authenticate(secret) { + authenticate(secret: string): Promise { return new Promise((resolve, reject) => { if (!this._lnurl) throw new Error('this._lnurl is not set'); - const url = parse(Lnurl.getUrlFromLnurl(this._lnurl), true); + const url = parse(Lnurl.getUrlFromLnurl(this._lnurl) || '', true); const hmac = createHmac('sha256', secret); hmac.on('readable', async () => { @@ -308,7 +361,7 @@ export default class Lnurl { if (!privateKey) return; const privateKeyBuf = Buffer.from(privateKey, 'hex'); const publicKey = secp256k1.publicKeyCreate(privateKeyBuf); - const signatureObj = secp256k1.sign(Buffer.from(url.query.k1, 'hex'), privateKeyBuf); + const signatureObj = secp256k1.sign(Buffer.from(url.query.k1 as string, 'hex'), privateKeyBuf); const derSignature = secp256k1.signatureExport(signatureObj.signature); const reply = await this.fetchGet(`${url.href}&sig=${derSignature.toString('hex')}&key=${publicKey.toString('hex')}`); @@ -326,7 +379,7 @@ export default class Lnurl { }); } - static isLightningAddress(address) { + static isLightningAddress(address: string) { // ensure only 1 `@` present: if (address.split('@').length !== 2) return false; const splitted = address.split('@'); diff --git a/components/TransactionsNavigationHeader.tsx b/components/TransactionsNavigationHeader.tsx index 244d29c6b..a4ee6da7c 100644 --- a/components/TransactionsNavigationHeader.tsx +++ b/components/TransactionsNavigationHeader.tsx @@ -218,7 +218,7 @@ const TransactionsNavigationHeader: React.FC {wallet.getPreferredBalanceUnit() === BitcoinUnit.LOCAL_CURRENCY - ? (preferredFiatCurrency?.endPointKey ?? FiatUnit.USD) + ? preferredFiatCurrency?.endPointKey ?? FiatUnit.USD : wallet.getPreferredBalanceUnit()} diff --git a/components/WalletsCarousel.tsx b/components/WalletsCarousel.tsx index c55b91eb4..489ebb5a3 100644 --- a/components/WalletsCarousel.tsx +++ b/components/WalletsCarousel.tsx @@ -248,7 +248,7 @@ export const WalletCarouselItem: React.FC = React.memo( return ( @@ -374,27 +374,31 @@ const WalletsCarousel = forwardRef((props const flatListRef = useRef>(null); - useImperativeHandle(ref, (): any => { - return { - scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params), - scrollToIndex: (params: { - animated?: boolean | null | undefined; - index: number; - viewOffset?: number | undefined; - viewPosition?: number | undefined; - }) => flatListRef.current?.scrollToIndex(params), - scrollToItem: (params: { - animated?: boolean | null | undefined; - item: any; - viewOffset?: number | undefined; - viewPosition?: number | undefined; - }) => flatListRef.current?.scrollToItem(params), - scrollToOffset: (params: { animated?: boolean | null | undefined; offset: number }) => flatListRef.current?.scrollToOffset(params), - recordInteraction: () => flatListRef.current?.recordInteraction(), - flashScrollIndicators: () => flatListRef.current?.flashScrollIndicators(), - getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(), - }; - }, []); + useImperativeHandle( + ref, + (): any => { + return { + scrollToEnd: (params: { animated?: boolean | null | undefined } | undefined) => flatListRef.current?.scrollToEnd(params), + scrollToIndex: (params: { + animated?: boolean | null | undefined; + index: number; + viewOffset?: number | undefined; + viewPosition?: number | undefined; + }) => flatListRef.current?.scrollToIndex(params), + scrollToItem: (params: { + animated?: boolean | null | undefined; + item: any; + viewOffset?: number | undefined; + viewPosition?: number | undefined; + }) => flatListRef.current?.scrollToItem(params), + scrollToOffset: (params: { animated?: boolean | null | undefined; offset: number }) => flatListRef.current?.scrollToOffset(params), + recordInteraction: () => flatListRef.current?.recordInteraction(), + flashScrollIndicators: () => flatListRef.current?.flashScrollIndicators(), + getNativeScrollRef: () => flatListRef.current?.getNativeScrollRef(), + }; + }, + [], + ); const onScrollToIndexFailed = (error: { averageItemLength: number; index: number }): void => { console.debug('onScrollToIndexFailed'); diff --git a/screen/lnd/lnurlPay.js b/screen/lnd/lnurlPay.tsx similarity index 73% rename from screen/lnd/lnurlPay.js rename to screen/lnd/lnurlPay.tsx index d127bc573..019d8f884 100644 --- a/screen/lnd/lnurlPay.js +++ b/screen/lnd/lnurlPay.tsx @@ -1,5 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; -import { useRoute } from '@react-navigation/native'; +import { RouteProp, useRoute } from '@react-navigation/native'; import React, { useEffect, useState } from 'react'; import { I18nManager, Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { Icon } from '@rneui/themed'; @@ -13,38 +13,40 @@ import Button from '../../components/Button'; import SafeArea from '../../components/SafeArea'; import { useTheme } from '../../components/themes'; import prompt from '../../helpers/prompt'; -import { useBiometrics, unlockWithBiometrics } from '../../hooks/useBiometrics'; +import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics'; import loc, { formatBalance, formatBalanceWithoutSuffix } from '../../loc'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import { useStorage } from '../../hooks/context/useStorage'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; +import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet'; +import { TWallet } from '../../class/wallets/types'; +import { pop } from '../../NavigationService'; -/** - * if user has default currency - fiat, attempting to pay will trigger conversion from entered in input field fiat value - * to satoshi, and attempt to pay this satoshi value, which might be a little bit off from `min` & `max` values - * provided by LnUrl. thats why we cache initial precise conversion rate so the reverse conversion wont be off. - */ -const _cacheFiatToSat = {}; +type RouteParams = { + walletID: string; + lnurl: string; +}; -const LnurlPay = () => { +const _cacheFiatToSat: Record = {}; + +const LnurlPay: React.FC = () => { const { wallets } = useStorage(); const { isBiometricUseCapableAndEnabled } = useBiometrics(); - const { walletID, lnurl } = useRoute().params; - /** @type {LightningCustodianWallet} */ - const wallet = wallets.find(w => w.getID() === walletID); - const [unit, setUnit] = useState(wallet.getPreferredBalanceUnit()); - const [isLoading, setIsLoading] = useState(true); - const [_LN, setLN] = useState(); - const [payButtonDisabled, setPayButtonDisabled] = useState(true); - const [payload, setPayload] = useState(); - const { setParams, pop, navigate } = useExtendedNavigation(); - const [amount, setAmount] = useState(); + const route = useRoute, string>>(); + const { walletID, lnurl } = route.params; + const wallet = wallets.find(w => w.getID() === walletID) as LightningCustodianWallet; + const [unit, setUnit] = useState(wallet?.getPreferredBalanceUnit() ?? BitcoinUnit.BTC); + const [isLoading, setIsLoading] = useState(true); + const [_LN, setLN] = useState(); + const [payButtonDisabled, setPayButtonDisabled] = useState(true); + const [payload, setPayload] = useState(); + const { setParams, navigate } = useExtendedNavigation(); + const [amount, setAmount] = useState(); const { colors } = useTheme(); const stylesHook = StyleSheet.create({ root: { backgroundColor: colors.background, }, - walletWrapLabel: { color: colors.buttonAlternativeTextColor, }, @@ -68,18 +70,16 @@ const LnurlPay = () => { setLN(ln); setIsLoading(false); } - }, [lnurl, pop]); + }, [lnurl]); useEffect(() => { setPayButtonDisabled(isLoading); }, [isLoading]); useEffect(() => { - if (payload) { - /** @type {Lnurl} */ - const LN = _LN; - let originalSatAmount; - let newAmount = (originalSatAmount = LN.getMin()); + if (payload && _LN) { + let originalSatAmount: number | false; + let newAmount: number | boolean | string = (originalSatAmount = _LN.getMin()); if (!newAmount) { presentAlert({ message: 'Internal error: incorrect LNURL amount' }); return; @@ -90,22 +90,21 @@ const LnurlPay = () => { break; case BitcoinUnit.LOCAL_CURRENCY: newAmount = satoshiToLocalCurrency(newAmount, false); - _cacheFiatToSat[newAmount] = originalSatAmount; + _cacheFiatToSat[newAmount] = String(originalSatAmount); break; } - setAmount(newAmount); + setAmount(newAmount.toString()); } - }, [payload]); // eslint-disable-line react-hooks/exhaustive-deps + }, [payload, _LN, unit]); - const onWalletSelect = w => { + const onWalletSelect = (w: TWallet) => { setParams({ walletID: w.getID() }); pop(); }; const pay = async () => { setPayButtonDisabled(true); - /** @type {Lnurl} */ - const LN = _LN; + if (!_LN || !amount) return; const isBiometricsEnabled = await isBiometricUseCapableAndEnabled(); if (isBiometricsEnabled) { @@ -114,39 +113,41 @@ const LnurlPay = () => { } } - let amountSats = amount; + let amountSats: number | false; switch (unit) { case BitcoinUnit.SATS: - amountSats = parseInt(amountSats, 10); // nop + amountSats = parseInt(amount, 10); break; case BitcoinUnit.BTC: - amountSats = btcToSatoshi(amountSats); + amountSats = btcToSatoshi(amount); break; case BitcoinUnit.LOCAL_CURRENCY: - if (_cacheFiatToSat[amount]) { - amountSats = _cacheFiatToSat[amount]; + if (_cacheFiatToSat[String(amount)]) { + amountSats = parseInt(_cacheFiatToSat[amount], 10); } else { - amountSats = btcToSatoshi(fiatToBTC(amountSats)); + amountSats = btcToSatoshi(fiatToBTC(parseFloat(amount))); } break; + default: + throw new Error('Unknown unit type'); } - let bolt11payload; try { - let comment; - if (LN.getCommentAllowed()) { + let comment: string | undefined; + if (_LN.getCommentAllowed()) { comment = await prompt('Comment', '', false, 'plain-text'); } - bolt11payload = await LN.requestBolt11FromLnurlPayService(amountSats, comment); + const bolt11payload = await _LN.requestBolt11FromLnurlPayService(amountSats, comment); + // @ts-ignore fixme after lnurl.js converted to ts await wallet.payInvoice(bolt11payload.pr); + // @ts-ignore fixme after lnurl.js converted to ts const decoded = wallet.decodeInvoice(bolt11payload.pr); setPayButtonDisabled(false); - // success, probably triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); if (wallet.last_paid_invoice_result && wallet.last_paid_invoice_result.payment_preimage) { - await LN.storeSuccess(decoded.payment_hash, wallet.last_paid_invoice_result.payment_preimage); + await _LN.storeSuccess(decoded.payment_hash, wallet.last_paid_invoice_result.payment_preimage); } navigate('ScanLndInvoiceRoot', { @@ -158,12 +159,12 @@ const LnurlPay = () => { }, }); setIsLoading(false); - } catch (Err) { - console.log(Err.message); + } catch (err) { + console.log((err as Error).message); setIsLoading(false); setPayButtonDisabled(false); triggerHapticFeedback(HapticFeedbackTypes.NotificationError); - return presentAlert({ message: Err.message }); + return presentAlert({ message: (err as Error).message }); } }; @@ -198,15 +199,16 @@ const LnurlPay = () => { const renderGotPayload = () => { return ( - + @@ -234,7 +236,7 @@ const LnurlPay = () => { ); }; - return isLoading || wallet === undefined || amount === undefined ? ( + return isLoading || !wallet || amount === undefined ? ( @@ -246,6 +248,7 @@ const LnurlPay = () => { export default LnurlPay; const styles = StyleSheet.create({ + scrollviewContainer: { justifyContent: 'space-around' }, img: { width: 200, height: 200, alignSelf: 'center' }, alignSelfCenter: { alignSelf: 'center', diff --git a/screen/lnd/lnurlPaySuccess.tsx b/screen/lnd/lnurlPaySuccess.tsx index e6d5dd466..dde822d1a 100644 --- a/screen/lnd/lnurlPaySuccess.tsx +++ b/screen/lnd/lnurlPaySuccess.tsx @@ -43,6 +43,9 @@ const LnurlPaySuccess: React.FC = () => { switch (successAction.tag) { case 'aes': { const preimage = lnurl.getPreimage(); + if (!preimage) { + break; + } setMessage(Lnurl.decipherAES(successAction.ciphertext, preimage, successAction.iv)); setPreamble(successAction.description); break; diff --git a/screen/send/SendDetails.tsx b/screen/send/SendDetails.tsx index 225c574e2..b9b6899fb 100644 --- a/screen/send/SendDetails.tsx +++ b/screen/send/SendDetails.tsx @@ -110,7 +110,7 @@ const SendDetails = () => { const [dumb, setDumb] = useState(false); const { isEditable } = routeParams; // if utxo is limited we use it to calculate available balance - const balance: number = utxo ? utxo.reduce((prev, curr) => prev + curr.value, 0) : (wallet?.getBalance() ?? 0); + const balance: number = utxo ? utxo.reduce((prev, curr) => prev + curr.value, 0) : wallet?.getBalance() ?? 0; const allBalance = formatBalanceWithoutSuffix(balance, BitcoinUnit.BTC, true); // if cutomFee is not set, we need to choose highest possible fee for wallet balance diff --git a/screen/transactions/TransactionDetails.tsx b/screen/transactions/TransactionDetails.tsx index 9374b519e..045e3e479 100644 --- a/screen/transactions/TransactionDetails.tsx +++ b/screen/transactions/TransactionDetails.tsx @@ -139,7 +139,7 @@ const TransactionDetails = () => { // okay, this txid _was_ with someone using payment codes, so we show the label edit dialog // and load user-defined alias for the pc if any - setCounterpartyLabel(counterpartyMetadata ? (counterpartyMetadata[foundPaymentCode]?.label ?? '') : ''); + setCounterpartyLabel(counterpartyMetadata ? counterpartyMetadata[foundPaymentCode]?.label ?? '' : ''); setIsCounterpartyLabelVisible(true); setPaymentCode(foundPaymentCode); }