Skip to content

Commit 140b1a2

Browse files
authored
Show error message if amount + fee is greater than balance in send-bitcoin-details screen (#315)
* useIbexFee hook is added to calculate fee in send bitcoin details screen * updata send bitcoind details screen logic and use useIbexFee hook to calculate the fee * update return type of fetchBreezFee method
1 parent 4006410 commit 140b1a2

File tree

4 files changed

+195
-24
lines changed

4 files changed

+195
-24
lines changed

app/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from "./use-app-config"
44
export * from "./useBreez"
55
export * from "./useActivityIndicator"
66
export * from "./use-display-currency"
7+
export * from "./useIbexFee"

app/hooks/useIbexFee.tsx

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { useCallback } from "react"
2+
import { gql } from "@apollo/client"
3+
import { WalletAmount } from "@app/types/amounts"
4+
import crashlytics from "@react-native-firebase/crashlytics"
5+
import { GetFee } from "@app/screens/send-bitcoin-screen/payment-details"
6+
import {
7+
WalletCurrency,
8+
useLnInvoiceFeeProbeMutation,
9+
useLnNoAmountInvoiceFeeProbeMutation,
10+
useLnNoAmountUsdInvoiceFeeProbeMutation,
11+
useLnUsdInvoiceFeeProbeMutation,
12+
useOnChainTxFeeLazyQuery,
13+
useOnChainUsdTxFeeAsBtcDenominatedLazyQuery,
14+
useOnChainUsdTxFeeLazyQuery,
15+
} from "@app/graphql/generated"
16+
17+
export type FeeType =
18+
| {
19+
status: "loading" | "error" | "unset"
20+
amount?: undefined | null
21+
}
22+
| {
23+
amount: WalletAmount<WalletCurrency>
24+
status: "set"
25+
}
26+
| {
27+
amount?: WalletAmount<WalletCurrency>
28+
status: "error"
29+
}
30+
31+
gql`
32+
mutation lnNoAmountInvoiceFeeProbe($input: LnNoAmountInvoiceFeeProbeInput!) {
33+
lnNoAmountInvoiceFeeProbe(input: $input) {
34+
errors {
35+
message
36+
}
37+
amount
38+
}
39+
}
40+
41+
mutation lnInvoiceFeeProbe($input: LnInvoiceFeeProbeInput!) {
42+
lnInvoiceFeeProbe(input: $input) {
43+
errors {
44+
message
45+
}
46+
amount
47+
}
48+
}
49+
50+
mutation lnUsdInvoiceFeeProbe($input: LnUsdInvoiceFeeProbeInput!) {
51+
lnUsdInvoiceFeeProbe(input: $input) {
52+
errors {
53+
message
54+
}
55+
amount
56+
}
57+
}
58+
59+
mutation lnNoAmountUsdInvoiceFeeProbe($input: LnNoAmountUsdInvoiceFeeProbeInput!) {
60+
lnNoAmountUsdInvoiceFeeProbe(input: $input) {
61+
errors {
62+
message
63+
}
64+
amount
65+
}
66+
}
67+
68+
query onChainTxFee(
69+
$walletId: WalletId!
70+
$address: OnChainAddress!
71+
$amount: SatAmount!
72+
) {
73+
onChainTxFee(walletId: $walletId, address: $address, amount: $amount) {
74+
amount
75+
}
76+
}
77+
78+
query onChainUsdTxFee(
79+
$walletId: WalletId!
80+
$address: OnChainAddress!
81+
$amount: CentAmount!
82+
) {
83+
onChainUsdTxFee(walletId: $walletId, address: $address, amount: $amount) {
84+
amount
85+
}
86+
}
87+
88+
query onChainUsdTxFeeAsBtcDenominated(
89+
$walletId: WalletId!
90+
$address: OnChainAddress!
91+
$amount: SatAmount!
92+
) {
93+
onChainUsdTxFeeAsBtcDenominated(
94+
walletId: $walletId
95+
address: $address
96+
amount: $amount
97+
) {
98+
amount
99+
}
100+
}
101+
`
102+
103+
export const useIbexFee = <T extends WalletCurrency>() => {
104+
const [lnInvoiceFeeProbe] = useLnInvoiceFeeProbeMutation()
105+
const [lnNoAmountInvoiceFeeProbe] = useLnNoAmountInvoiceFeeProbeMutation()
106+
const [lnUsdInvoiceFeeProbe] = useLnUsdInvoiceFeeProbeMutation()
107+
const [lnNoAmountUsdInvoiceFeeProbe] = useLnNoAmountUsdInvoiceFeeProbeMutation()
108+
const [onChainTxFee] = useOnChainTxFeeLazyQuery()
109+
const [onChainUsdTxFee] = useOnChainUsdTxFeeLazyQuery()
110+
const [onChainUsdTxFeeAsBtcDenominated] = useOnChainUsdTxFeeAsBtcDenominatedLazyQuery()
111+
112+
const fetchIbexFee = useCallback(
113+
async (getFeeFn?: GetFee<T> | null) => {
114+
if (!getFeeFn) {
115+
return
116+
}
117+
try {
118+
const feeResponse = await getFeeFn({
119+
lnInvoiceFeeProbe,
120+
lnNoAmountInvoiceFeeProbe,
121+
lnUsdInvoiceFeeProbe,
122+
lnNoAmountUsdInvoiceFeeProbe,
123+
onChainTxFee,
124+
onChainUsdTxFee,
125+
onChainUsdTxFeeAsBtcDenominated,
126+
})
127+
128+
if (feeResponse.errors?.length || !feeResponse.amount) {
129+
return undefined
130+
}
131+
132+
return feeResponse.amount
133+
} catch (err) {
134+
if (err instanceof Error) {
135+
crashlytics().recordError(err)
136+
}
137+
return undefined
138+
}
139+
},
140+
[
141+
lnInvoiceFeeProbe,
142+
lnNoAmountInvoiceFeeProbe,
143+
lnUsdInvoiceFeeProbe,
144+
lnNoAmountUsdInvoiceFeeProbe,
145+
onChainTxFee,
146+
onChainUsdTxFee,
147+
onChainUsdTxFeeAsBtcDenominated,
148+
],
149+
)
150+
151+
return fetchIbexFee
152+
}

app/screens/send-bitcoin-screen/send-bitcoin-details-screen.tsx

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { getUsdWallet } from "@app/graphql/wallets-utils"
2626
// hooks
2727
import { useIsAuthed } from "@app/graphql/is-authed-context"
2828
import { useLevel } from "@app/graphql/level-context"
29-
import { useActivityIndicator, useBreez, usePriceConversion } from "@app/hooks"
29+
import { useBreez, useIbexFee, usePriceConversion } from "@app/hooks"
3030
import { useDisplayCurrency } from "@app/hooks/use-display-currency"
3131
import { useI18nContext } from "@app/i18n/i18n-react"
3232
import { usePersistentStateContext } from "@app/store/persistent-state"
@@ -38,7 +38,7 @@ import { PaymentDetail } from "./payment-details/index.types"
3838
import { Satoshis } from "lnurl-pay/dist/types/types"
3939

4040
// utils
41-
import { toBtcMoneyAmount, toUsdMoneyAmount } from "@app/types/amounts"
41+
import { DisplayCurrency, toBtcMoneyAmount, toUsdMoneyAmount } from "@app/types/amounts"
4242
import { isValidAmount } from "./payment-details"
4343
import { requestInvoice, utils } from "lnurl-pay"
4444
import { fetchBreezFee } from "@app/utils/breez-sdk-liquid"
@@ -58,8 +58,8 @@ const SendBitcoinDetailsScreen: React.FC<Props> = ({ route }) => {
5858
const { btcWallet } = useBreez()
5959
const { persistentState } = usePersistentStateContext()
6060
const { convertMoneyAmount: _convertMoneyAmount } = usePriceConversion("network-only")
61-
const { zeroDisplayAmount, formatMoneyAmount } = useDisplayCurrency()
62-
const { toggleActivityIndicator } = useActivityIndicator()
61+
const { zeroDisplayAmount, formatDisplayAndWalletAmount } = useDisplayCurrency()
62+
const getIbexFee = useIbexFee()
6363

6464
const { paymentDestination, flashUserAddress } = route.params
6565

@@ -132,24 +132,38 @@ const SendBitcoinDetailsScreen: React.FC<Props> = ({ route }) => {
132132
zeroDisplayAmount,
133133
])
134134

135-
useEffect(() => {
136-
if (paymentDetail?.sendingWalletDescriptor.currency === "BTC") {
137-
fetchSendingFee()
138-
}
139-
}, [paymentDetail])
140-
141-
const fetchSendingFee = async () => {
142-
if (paymentDetail) {
143-
toggleActivityIndicator(true)
144-
const { fee, err }: { fee: any; err: any } = await fetchBreezFee(
145-
paymentDetail?.paymentType,
146-
paymentDetail?.destination,
147-
paymentDetail?.settlementAmount.amount,
148-
)
149-
toggleActivityIndicator(false)
150-
if (fee === null && err) {
151-
setAsyncErrorMessage(`${err?.message} (amount + fee)` || "")
135+
const fetchSendingFee = async (pd: PaymentDetail<WalletCurrency>) => {
136+
if (pd) {
137+
if (pd?.sendingWalletDescriptor.currency === "BTC") {
138+
const { fee, err }: { fee: any; err: any } = await fetchBreezFee(
139+
pd?.paymentType,
140+
pd?.destination,
141+
pd?.settlementAmount.amount,
142+
)
143+
if (fee === null && err) {
144+
setAsyncErrorMessage(`${err?.message} (amount + fee)` || "")
145+
return false
146+
}
147+
} else {
148+
const estimatedFee = await getIbexFee(pd.getFee)
149+
if (
150+
_convertMoneyAmount &&
151+
estimatedFee &&
152+
pd.settlementAmount.amount + estimatedFee?.amount > usdBalanceMoneyAmount.amount
153+
) {
154+
const amount = formatDisplayAndWalletAmount({
155+
displayAmount: _convertMoneyAmount(usdBalanceMoneyAmount, DisplayCurrency),
156+
walletAmount: usdBalanceMoneyAmount,
157+
})
158+
setAsyncErrorMessage(
159+
LL.SendBitcoinScreen.amountExceed({
160+
balance: amount,
161+
}) + "(amount + fee)",
162+
)
163+
return false
164+
}
152165
}
166+
return true
153167
}
154168
}
155169

@@ -208,7 +222,9 @@ const SendBitcoinDetailsScreen: React.FC<Props> = ({ route }) => {
208222
}
209223
}
210224

211-
if (paymentDetailForConfirmation.sendPaymentMutation) {
225+
const res = await fetchSendingFee(paymentDetailForConfirmation)
226+
227+
if (res && paymentDetailForConfirmation.sendPaymentMutation) {
212228
navigation.navigate("sendBitcoinConfirmation", {
213229
paymentDetail: paymentDetailForConfirmation,
214230
})

app/utils/breez-sdk-liquid/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,20 +161,22 @@ export const fetchBreezFee = async (
161161
amount: { type: PayAmountVariant.RECEIVER, amountSat: receiverAmountSat },
162162
})
163163
return { fee: response.totalFeesSat, err: null }
164+
return { fee: response.totalFeesSat, err: null }
164165
} else if (
165166
(paymentType === "intraledger" || paymentType === "lnurl") &&
166167
!!invoice &&
167168
!!receiverAmountSat
168169
) {
169170
const input = await parse(invoice)
170171
if (input.type === InputTypeVariant.LN_URL_PAY) {
171-
const prepareResponse = await prepareLnurlPay({
172+
const response = await prepareLnurlPay({
172173
data: input.data,
173174
amountMsat: receiverAmountSat * 1000,
174175
})
175-
return { fee: prepareResponse.feesSat, err: null }
176+
return { fee: response.feesSat, err: null }
176177
}
177178
return { fee: null, err: "Wrong payment type" }
179+
return { fee: null, err: "Wrong payment type" }
178180
} else {
179181
return { fee: null, err: "Wrong payment type" }
180182
}

0 commit comments

Comments
 (0)