Skip to content

Commit

Permalink
feat: alert if onchain fees are too high
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Burtey committed Nov 23, 2023
1 parent dfadf1e commit 6f9a39d
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 8 deletions.
2 changes: 1 addition & 1 deletion app/components/custom-modal/custom-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ type UseStylesProps = {
const useStyles = makeStyles(({ colors }, props: UseStylesProps) => ({
container: {
backgroundColor: colors.white,
maxHeight: "80%",
maxHeight: "95%",
minHeight: props.minHeight || "auto",
borderRadius: 16,
padding: 20,
Expand Down
3 changes: 2 additions & 1 deletion app/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2281,7 +2281,8 @@ const en: BaseTranslation = {
lnurlInvoiceIncorrectDescription:
"The lnurl server responded with an invoice with an incorrect description hash.",
confirmFeesModal: {
title: "Fees are high!",
title: "High fee alert!",
content: "Your fee is more than 50% bigger than the amount sent. Are you sure you want to proceed?\n\nTo reduce fees, ask the receiver to accept transaction via Lightning",
confirmButton: "I'm 100% sure",
},
},
Expand Down
16 changes: 14 additions & 2 deletions app/i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7084,9 +7084,15 @@ type RootTranslation = {
lnurlInvoiceIncorrectDescription: string
confirmFeesModal: {
/**
* F​e​e​s​ ​a​r​e​ ​h​i​g​h​!
* H​i​g​h​ ​f​e​e​ ​a​l​e​r​t​!
*/
title: string
/**
* Y​o​u​r​ ​f​e​e​ ​i​s​ ​m​o​r​e​ ​t​h​a​n​ ​5​0​%​ ​b​i​g​g​e​r​ ​t​h​a​n​ ​t​h​e​ ​a​m​o​u​n​t​ ​s​e​n​t​.​ ​A​r​e​ ​y​o​u​ ​s​u​r​e​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​p​r​o​c​e​e​d​?​
​T​o​ ​r​e​d​u​c​e​ ​f​e​e​s​,​ ​a​s​k​ ​t​h​e​ ​r​e​c​e​i​v​e​r​ ​t​o​ ​a​c​c​e​p​t​ ​t​r​a​n​s​a​c​t​i​o​n​ ​v​i​a​ ​L​i​g​h​t​n​i​n​g
*/
content: string
/**
* I​'​m​ ​1​0​0​%​ ​s​u​r​e
*/
Expand Down Expand Up @@ -15800,9 +15806,15 @@ export type TranslationFunctions = {
lnurlInvoiceIncorrectDescription: () => LocalizedString
confirmFeesModal: {
/**
* Fees are high!
* High fee alert!
*/
title: () => LocalizedString
/**
* Your fee is more than 50% bigger than the amount sent. Are you sure you want to proceed?

To reduce fees, ask the receiver to accept transaction via Lightning
*/
content: () => LocalizedString
/**
* I'm 100% sure
*/
Expand Down
3 changes: 2 additions & 1 deletion app/i18n/raw-i18n/source/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2208,7 +2208,8 @@
"lnurlInvoiceIncorrectAmount": "The lnurl server responded with an invoice with an incorrect amount.",
"lnurlInvoiceIncorrectDescription": "The lnurl server responded with an invoice with an incorrect description hash.",
"confirmFeesModal": {
"title": "Fees are high!",
"title": "High fee alert!",
"content": "Your fee is more than 50% bigger than the amount sent. Are you sure you want to proceed?\n\nTo reduce fees, ask the receiver to accept transaction via Lightning",
"confirmButton": "I'm 100% sure"
}
},
Expand Down
83 changes: 83 additions & 0 deletions app/screens/send-bitcoin-screen/confirm-fees-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { GaloyIcon } from "@app/components/atomic/galoy-icon"
import CustomModal from "@app/components/custom-modal/custom-modal"
import { useI18nContext } from "@app/i18n/i18n-react"
import { Text, makeStyles, useTheme } from "@rneui/themed"
import React from "react"
import { View } from "react-native"

export type ConfirmFeesModalProps = {
isVisible: boolean
action: () => void
cancel: () => void
}

export const ConfirmFeesModal: React.FC<ConfirmFeesModalProps> = ({
action,
cancel,
isVisible,
}) => {
const styles = useStyles()
const {
theme: { colors },
} = useTheme()
const { LL } = useI18nContext()

console.log({ isVisible, action })

return (
<CustomModal
isVisible={isVisible}
toggleModal={cancel}
title={LL.SendBitcoinScreen.confirmFeesModal.title()}
image={<GaloyIcon name="info" size={80} color={colors.primary3} />}
body={
<View style={styles.body}>
<Text type={"p2"} style={styles.warningText}>
{LL.SendBitcoinScreen.confirmFeesModal.content()}
</Text>
</View>
}
primaryButtonOnPress={action}
primaryButtonTitle={LL.SendBitcoinScreen.confirmFeesModal.confirmButton()}
secondaryButtonTitle={LL.common.cancel()}
secondaryButtonOnPress={cancel}
/>
)
}

const useStyles = makeStyles(({ colors }) => ({
modalCard: {
backgroundColor: colors.white,
borderRadius: 16,
padding: 18,
},
warningText: {
textAlign: "center",
},
body: {
rowGap: 12,
},
buttonContainer: {
rowGap: 12,
},
titleContainer: {
marginBottom: 12,
},
checkBox: {
paddingLeft: 0,
backgroundColor: "transparent",
},
checkBoxTouchable: {
marginTop: 12,
},
checkBoxContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
backgroundColor: colors.grey5,
borderRadius: 8,
},
checkBoxText: {
flex: 1,
},
}))
83 changes: 80 additions & 3 deletions app/screens/send-bitcoin-screen/send-bitcoin-details-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { AmountInput } from "@app/components/amount-input/amount-input"
import { GaloyPrimaryButton } from "@app/components/atomic/galoy-primary-button"
import { Screen } from "@app/components/screen"
import {
Network,
useOnChainTxFeeLazyQuery,
useSendBitcoinDetailsScreenQuery,
useSendBitcoinInternalLimitsQuery,
useSendBitcoinWithdrawalLimitsQuery,
Expand Down Expand Up @@ -40,6 +42,7 @@ import { getBtcWallet, getDefaultWallet, getUsdWallet } from "@app/graphql/walle
import { NoteInput } from "@app/components/note-input"
import { PaymentDestinationDisplay } from "@app/components/payment-destination-display"
import { useHideAmount } from "@app/graphql/hide-amount-context"
import { ConfirmFeesModal } from "./confirm-fees-modal"

gql`
query sendBitcoinDetailsScreen {
Expand Down Expand Up @@ -119,6 +122,7 @@ const SendBitcoinDetailsScreen: React.FC<Props> = ({ route }) => {
const { formatDisplayAndWalletAmount } = useDisplayCurrency()
const { LL } = useI18nContext()
const [isLoadingLnurl, setIsLoadingLnurl] = useState(false)
const [modalHighFeesVisible, setModalHighFeesVisible] = useState(false)

const { convertMoneyAmount: _convertMoneyAmount } = usePriceConversion()
const { zeroDisplayAmount } = useDisplayCurrency()
Expand Down Expand Up @@ -203,6 +207,12 @@ const SendBitcoinDetailsScreen: React.FC<Props> = ({ route }) => {
zeroDisplayAmount,
])

const alertHighFees = useOnchainFeeAlert(
paymentDetail,
btcWallet?.id as string,
network,
)

if (!paymentDetail) {
return <></>
}
Expand Down Expand Up @@ -374,9 +384,13 @@ const SendBitcoinDetailsScreen: React.FC<Props> = ({ route }) => {
}

if (paymentDetailForConfirmation.sendPaymentMutation) {
navigation.navigate("sendBitcoinConfirmation", {
paymentDetail: paymentDetailForConfirmation,
})
if (alertHighFees) {
setModalHighFeesVisible(true)
} else {
navigation.navigate("sendBitcoinConfirmation", {
paymentDetail: paymentDetailForConfirmation,
})
}
}
})

Expand Down Expand Up @@ -417,6 +431,13 @@ const SendBitcoinDetailsScreen: React.FC<Props> = ({ route }) => {
keyboardOffset="navigationHeader"
keyboardShouldPersistTaps="handled"
>
<ConfirmFeesModal
action={() => {
navigation.navigate("sendBitcoinConfirmation", { paymentDetail })
}}
isVisible={modalHighFeesVisible}
cancel={() => setModalHighFeesVisible(false)}
/>
<View style={styles.sendBitcoinAmountContainer}>
<View style={styles.fieldContainer}>
<Text style={styles.fieldTitleText}>{LL.SendBitcoinScreen.destination()}</Text>
Expand Down Expand Up @@ -661,3 +682,59 @@ const useStyles = makeStyles(({ colors }) => ({
height: 18,
},
}))

const useOnchainFeeAlert = (
paymentDetail: PaymentDetail<WalletCurrency> | null,
walletId: string,
network: Network | undefined,
) => {
const dummyAddress =
network === "mainnet"
? "bc1qk2cpytjea36ry6vga8wwr7297sl3tdkzwzy2cw"
: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"

// we need to have an approximate value for the onchain fees
// by the time the user tap on the next button
// so we are fetching some fees when the screen loads
// the fees are approximate but that doesn't matter for the use case
// of warning the user if the fees are high compared to the amount sent
const [getOnChainTxFee] = useOnChainTxFeeLazyQuery({
fetchPolicy: "cache-and-network",
variables: {
walletId,
amount: 100000,
address: dummyAddress,
},
})

const [onChainTxFee, setOnChainTxFee] = useState(0)

useEffect(() => {
;(async () => {
const result = await getOnChainTxFee()
const fees = result.data?.onChainTxFee.amount
if (fees) {
setOnChainTxFee(fees)
} else {
console.error("failed to get onchain fees")
}
})()
}, [getOnChainTxFee])

if (!walletId || !paymentDetail || paymentDetail.paymentType !== "onchain") {
return false
}

const { convertMoneyAmount } = paymentDetail

// alert will shows if amount is less than fees * ratioFeesToAmount
const ratioFeesToAmount = 2
const ratioedFees = toBtcMoneyAmount(onChainTxFee * ratioFeesToAmount)

const alertHighFees =
paymentDetail.paymentType === "onchain" &&
convertMoneyAmount(paymentDetail.settlementAmount, WalletCurrency.Btc).amount <
ratioedFees.amount

return alertHighFees
}

0 comments on commit 6f9a39d

Please sign in to comment.