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

Fix Max Fee Checks #1732

Merged
merged 3 commits into from
Apr 25, 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
22 changes: 21 additions & 1 deletion 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 @@ -44,6 +56,14 @@ const FeeCustom = ({
const onPress = (key: string): void => {
const current = feeRate.toString();
const newAmount = handleNumberPadPress(key, current, { maxLength: 3 });
if (Number(newAmount) > maxFee) {
showToast({
type: 'info',
title: t('max_possible_fee_rate'),
description: `${maxFee} ${t('sats_per_vbyte')}`,
});
return;
}
setFeeRate(Number(newAmount));
};

Expand Down
52 changes: 38 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,20 @@ 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.satsPerByte,
transaction,
});
if (feeInfo.isOk()) {
setMaxFee(feeInfo.value.maxSatPerByte);
}
}, [transaction]);

const transactionTotal = useCallback(() => {
return getTransactionOutputValue({
outputs: transaction.outputs,
Expand Down Expand Up @@ -73,40 +91,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
2 changes: 2 additions & 0 deletions src/utils/i18n/locales/en/wallet.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"send_fee_and_speed": "Speed and fee",
"send_fee_speed": "Speed",
"send_fee_error": "Fee Invalid",
"max_possible_fee_rate": "Max possible fee rate",
"sats_per_vbyte": "sats/vbyte",
"send_output_to_small_title": "Send Amount Too Small",
"send_output_to_small_description": "Please increase your send amount to proceed.",
"send_coin_selection_output_to_small_description": "Please remove UTXO's or increase your send amount to proceed.",
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 { resetActivityState } from '../../store/slices/activity';
import BitcoinActions from '../bitcoin-actions';
import { bitkitLedger, syncLedger } from '../ledger';
import { TGetTotalFeeObj } from 'beignet/dist/types/types';

bitcoin.initEccLib(ecc);
const bip32 = BIP32Factory(ecc);
Expand Down Expand Up @@ -1609,3 +1610,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,
});
};
Loading