Skip to content

Commit

Permalink
fix(wallet): Fee Updates
Browse files Browse the repository at this point in the history
Prevents user from selecting or setting a fee-rate above what the max is.
Resolves UI bug in #1619
  • Loading branch information
coreyphillips authored and pwltr committed Apr 25, 2024
1 parent 5548983 commit 96bc77a
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 20 deletions.
24 changes: 22 additions & 2 deletions src/screens/Wallets/Send/FeeCustom.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactElement, memo, useMemo, useState } from 'react';
import React, { ReactElement, memo, useMemo, useState, useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import { useTranslation } from 'react-i18next';

Expand All @@ -16,13 +16,25 @@ import { useAppSelector } from '../../../hooks/redux';
import { useDisplayValues } from '../../../hooks/displayValues';
import { transactionSelector } from '../../../store/reselect/wallet';
import type { SendScreenProps } from '../../../navigation/types';
import { getFeeInfo } from '../../../utils/wallet';

const FeeCustom = ({
navigation,
}: SendScreenProps<'FeeCustom'>): ReactElement => {
const { t } = useTranslation('wallet');
const transaction = useAppSelector(transactionSelector);
const [feeRate, setFeeRate] = useState(transaction.satsPerByte);
const [maxFee, setMaxFee] = useState(0);

useEffect(() => {
const feeInfo = getFeeInfo({
satsPerByte: transaction.satsPerByte,
transaction,
});
if (feeInfo.isOk()) {
setMaxFee(feeInfo.value.maxSatPerByte);
}
}, [transaction]);

const totalFee = getTotalFee({
satsPerByte: feeRate,
Expand All @@ -43,7 +55,15 @@ const FeeCustom = ({

const onPress = (key: string): void => {
const current = feeRate.toString();
const newAmount = handleNumberPadPress(key, current, { maxLength: 3 });
const newAmount = handleNumberPadPress(key, current, { maxLength: 4 });
if (Number(newAmount) > maxFee) {
showToast({
type: 'info',
title: 'Max possible fee rate',
description: `${maxFee} sats/vbyte`,
});
return;
}
setFeeRate(Number(newAmount));
};

Expand Down
50 changes: 36 additions & 14 deletions src/screens/Wallets/Send/FeeRate.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import React, { memo, ReactElement, useMemo, useCallback } from 'react';
import React, {
memo,
ReactElement,
useMemo,
useCallback,
useState,
useEffect,
} from 'react';
import { StyleSheet, View } from 'react-native';
import { useTranslation } from 'react-i18next';

Expand All @@ -24,6 +31,7 @@ import {
} from '../../../store/reselect/wallet';
import SafeAreaInset from '../../../components/SafeAreaInset';
import { EFeeId } from 'beignet';
import { getFeeInfo } from '../../../utils/wallet';

const FeeRate = ({ navigation }: SendScreenProps<'FeeRate'>): ReactElement => {
const { t } = useTranslation('wallet');
Expand All @@ -32,10 +40,18 @@ const FeeRate = ({ navigation }: SendScreenProps<'FeeRate'>): ReactElement => {
const selectedNetwork = useAppSelector(selectedNetworkSelector);
const transaction = useAppSelector(transactionSelector);
const feeEstimates = useAppSelector((store) => store.fees.onchain);

const [maxFee, setMaxFee] = useState(0);
const selectedFeeId = transaction.selectedFeeId;
const satsPerByte = transaction.satsPerByte;

useEffect(() => {
const feeInfo = getFeeInfo({ satsPerByte, transaction });
if (feeInfo.isOk()) {
setMaxFee(feeInfo.value.maxSatPerByte);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [transaction]);

const transactionTotal = useCallback(() => {
return getTransactionOutputValue({
outputs: transaction.outputs,
Expand Down Expand Up @@ -73,40 +89,46 @@ const FeeRate = ({ navigation }: SendScreenProps<'FeeRate'>): ReactElement => {

const displayFast = useMemo(() => {
return (
onchainBalance >= transactionTotal() + getFee(feeEstimates.fast) ||
transaction.max
maxFee >= feeEstimates.fast &&
(onchainBalance >= transactionTotal() + getFee(feeEstimates.fast) ||
transaction.max)
);
}, [
onchainBalance,
maxFee,
feeEstimates.fast,
getFee,
onchainBalance,
transactionTotal,
getFee,
transaction.max,
]);

const displayNormal = useMemo(() => {
return (
onchainBalance >= transactionTotal() + getFee(feeEstimates.normal) ||
transaction.max
feeEstimates.normal <= maxFee &&
(onchainBalance >= transactionTotal() + getFee(feeEstimates.normal) ||
transaction.max)
);
}, [
onchainBalance,
maxFee,
feeEstimates.normal,
getFee,
onchainBalance,
transactionTotal,
getFee,
transaction.max,
]);

const displaySlow = useMemo(() => {
return (
onchainBalance >= transactionTotal() + getFee(feeEstimates.slow) ||
transaction.max
maxFee >= feeEstimates.slow &&
(onchainBalance >= transactionTotal() + getFee(feeEstimates.slow) ||
transaction.max)
);
}, [
onchainBalance,
maxFee,
feeEstimates.slow,
getFee,
onchainBalance,
transactionTotal,
getFee,
transaction.max,
]);

Expand Down
22 changes: 18 additions & 4 deletions src/store/actions/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,8 +638,11 @@ export const setupFeeForOnChainTransaction = (): Result<string> => {
transactionSpeed === ETransactionSpeed.custom
? customFeeRate
: fees[transactionSpeed];
const selectedFeeId = txSpeedToFeeId(getSettingsStore().transactionSpeed);
const satsPerByte =
const selectedFeeId =
transaction.selectedFeeId === 'none'
? txSpeedToFeeId(getSettingsStore().transactionSpeed)
: transaction.selectedFeeId;
let satsPerByte =
transaction.selectedFeeId === 'none'
? preferredFeeRate
: transaction.satsPerByte;
Expand All @@ -650,8 +653,19 @@ export const setupFeeForOnChainTransaction = (): Result<string> => {
if (feeSetupRes.isOk()) {
return feeSetupRes;
}
// If unable to set up fee using the selectedFeeId, attempt 1 satsPerByte. Otherwise, return error.
const updateRes = updateFee({ satsPerByte: 1, transaction });

// If unable to set up fee using the selectedFeeId set maxSatPerByte from getFeeInfo.
const txFeeInfo = wallet.getFeeInfo({
satsPerByte,
transaction,
});
if (txFeeInfo.isErr()) {
return err(txFeeInfo.error.message);
}
if (txFeeInfo.value.maxSatPerByte < satsPerByte) {
satsPerByte = txFeeInfo.value.maxSatPerByte;
}
const updateRes = updateFee({ satsPerByte, transaction });
if (updateRes.isErr()) {
return err(feeSetupRes.error.message);
}
Expand Down
28 changes: 28 additions & 0 deletions src/utils/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import { updateUi } from '../../store/slices/ui';
import { ICustomGetScriptHash } from 'beignet/src/types/wallet';
import { ldk } from '@synonymdev/react-native-ldk';
import { resetActivityState } from '../../store/slices/activity';
import { TGetTotalFeeObj } from 'beignet/dist/types/types';

bitcoin.initEccLib(ecc);
const bip32 = BIP32Factory(ecc);
Expand Down Expand Up @@ -1525,3 +1526,30 @@ export const switchNetwork = async (
setTimeout(updateActivityList, 500);
return ok(true);
};

/**
* Returns a fee object for the current/provided transaction.
* @param {number} [satsPerByte]
* @param {string} [message]
* @param {Partial<ISendTransaction>} [transaction]
* @param {boolean} [fundingLightning]
* @returns {Result<TGetTotalFeeObj>}
*/
export const getFeeInfo = ({
satsPerByte,
transaction,
message,
fundingLightning,
}: {
satsPerByte: number;
message?: string;
transaction?: Partial<ISendTransaction>;
fundingLightning?: boolean;
}): Result<TGetTotalFeeObj> => {
return wallet.getFeeInfo({
satsPerByte,
transaction,
message,
fundingLightning,
});
};

0 comments on commit 96bc77a

Please sign in to comment.