Skip to content

Commit cf154d4

Browse files
committed
Updating tiered fees to dynamically adjust recommended fee rates for replacement transactions so that we do not present fee rates that are lower than the initial fee rate.
1 parent 5f32e60 commit cf154d4

3 files changed

Lines changed: 94 additions & 166 deletions

File tree

src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
5353

5454
// Debounced effect to calculate transaction size when the amount changes, with JWT
5555
useEffect(() => {
56-
56+
5757
const debounceTimeout = setTimeout(() => {
5858
const fetchTransactionSize = async () => {
5959
if (isValidAddress(formData.address) && isDetailsOpen) {
@@ -185,7 +185,7 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
185185
await login(); // Perform login if not authenticated
186186
}
187187

188-
188+
189189
// Step 2: Initiate the new transaction with the JWT token
190190
const response = await fetch('http://localhost:9003/transaction', {
191191
method: 'POST',
@@ -301,7 +301,13 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
301301
/>
302302
RBF Opt In
303303
</S.RBFWrapper>
304-
<TieredFees inValidAmount={inValidAmount} handleFeeChange={handleFeeRateChange} txSize={txSize} />
304+
<TieredFees
305+
invalidAmount={inValidAmount} // Note: 'invalidAmount' property is now camelCase to match prop names
306+
handleFeeChange={handleFeeRateChange}
307+
transactionSize={txSize} // Pass the transaction size value
308+
originalFeeRate={0} // Pass 0 or appropriate value for new transactions
309+
/>
310+
305311
</S.TiersContainer>
306312
<BaseRow justify={'center'}>
307313
<S.SendFormButton
Lines changed: 74 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import React, { useEffect, useState } from 'react';
2-
import * as S from './TieredFees.styles';
3-
import { useResponsive } from '@app/hooks/useResponsive';
4-
import { tiers } from '../../SendForm';
5-
import { set } from 'date-fns';
1+
import React, { useState, useEffect } from "react";
2+
import * as S from "./TieredFees.styles";
3+
import { useResponsive } from "@app/hooks/useResponsive";
64

75
interface FeeRecommendation {
86
fastestFee: number;
@@ -11,107 +9,140 @@ interface FeeRecommendation {
119
economyFee: number;
1210
minimumFee: number;
1311
}
14-
type Fees = {
15-
[key in tiers]: number;
16-
};
12+
13+
type Tier = "low" | "med" | "high";
14+
15+
interface Fees {
16+
low: number;
17+
med: number;
18+
high: number;
19+
}
20+
21+
22+
// interface Fees {
23+
// [key in Tier]: number;
24+
// }
25+
1726
interface TieredFeesProps {
1827
handleFeeChange: (fee: number) => void;
19-
inValidAmount: boolean;
20-
txSize: number | null; // Transaction size passed down from SendForm
28+
invalidAmount: boolean;
29+
transactionSize: number | null; // Transaction size passed down from parent
30+
originalFeeRate?: number; // Optional original fee rate (used for replacements)
2131
}
2232

23-
const TieredFees: React.FC<TieredFeesProps> = ({ inValidAmount, handleFeeChange, txSize }) => {
33+
const DEFAULT_FEES: Fees = { low: 3, med: 4, high: 5 };
34+
35+
const TieredFees: React.FC<TieredFeesProps> = ({
36+
handleFeeChange,
37+
invalidAmount,
38+
transactionSize,
39+
originalFeeRate = 0,
40+
}) => {
2441
const { isDesktop, isTablet } = useResponsive();
42+
const [selectedTier, setSelectedTier] = useState<Tier>("low");
43+
const [fees, setFees] = useState<Fees>(DEFAULT_FEES);
44+
const [estimatedFee, setEstimatedFee] = useState<Fees>({ low: 0, med: 0, high: 0 });
2545
const [loadingRecommendation, setLoadingRecommendation] = useState(false);
26-
const [fetchedrecommendation, setFetchedRecommendation] = useState(false);
27-
const [fees, setFees] = useState<Fees>({ low: 0, med: 0, high: 0 });
28-
const [selectedTier, setSelectedTier] = useState<tiers | null>('low');
29-
const [estimatedFee, setEstimatedFee] = useState({ low: 0, med: 0, high: 0 });
46+
const [fetchedRecommendation, setFetchedRecommendation] = useState(false);
47+
48+
// Adjust fees for replacement transactions if originalFeeRate > 0
49+
const adjustFees = (fetchedFees: Fees) => {
50+
if (originalFeeRate > 0) {
51+
const adjustedFees = { ...fetchedFees };
52+
adjustedFees.low = Math.max(originalFeeRate + 1, fetchedFees.low);
53+
adjustedFees.med = Math.max(adjustedFees.low + 1, fetchedFees.med);
54+
adjustedFees.high = Math.max(adjustedFees.med + 1, fetchedFees.high);
55+
return adjustedFees;
56+
}
57+
return fetchedFees;
58+
};
3059

3160
useEffect(() => {
32-
if (loadingRecommendation || fetchedrecommendation) return;
61+
if (loadingRecommendation || fetchedRecommendation) return;
3362
const fetchFees = async () => {
3463
setLoadingRecommendation(true);
3564
try {
36-
const response = await fetch('https://mempool.space/api/v1/fees/recommended');
65+
const response = await fetch("https://mempool.space/api/v1/fees/recommended");
3766
const data: FeeRecommendation = await response.json();
38-
setFees({
67+
const fetchedFees: Fees = {
3968
low: data.economyFee,
4069
med: data.halfHourFee,
4170
high: data.fastestFee,
42-
});
71+
};
72+
const adjustedFees = adjustFees(fetchedFees);
73+
setFees(adjustedFees);
4374
setFetchedRecommendation(true);
4475
} catch (error) {
45-
console.error('Failed to fetch fees:', error);
76+
console.error("Failed to fetch fees:", error);
77+
setFees(adjustFees(DEFAULT_FEES));
4678
}
4779
setLoadingRecommendation(false);
4880
};
49-
5081
fetchFees();
51-
}, []);
82+
}, [originalFeeRate]);
5283

5384
// Update estimated fees whenever the fees or transaction size change
5485
useEffect(() => {
55-
if (txSize) {
86+
if (transactionSize) {
5687
setEstimatedFee({
57-
low: txSize * fees.low,
58-
med: txSize * fees.med,
59-
high: txSize * fees.high,
88+
low: Math.ceil(transactionSize * fees.low),
89+
med: Math.ceil(transactionSize * fees.med),
90+
high: Math.ceil(transactionSize * fees.high),
6091
});
6192
}
62-
}, [fees, txSize]);
93+
}, [fees, transactionSize]);
6394

64-
const handleTierChange = (tier: any) => {
65-
setSelectedTier(tier.id);
95+
const handleTierChange = (tier: Tier) => {
96+
setSelectedTier(tier);
6697
};
6798

6899
useEffect(() => {
69-
handleFeeChange(fees[selectedTier as tiers]);
70-
}, [selectedTier, fees]);
100+
handleFeeChange(fees[selectedTier]);
101+
}, [selectedTier, fees, handleFeeChange]);
71102

72103
return (
73104
<S.TiersWrapper $isMobile={!isDesktop && !isTablet}>
74105
<S.TierCard
75106
$isMobile={!isDesktop}
76-
onClick={() => handleTierChange({ id: 'low' })}
77-
className={`tier-hover ${selectedTier === 'low' ? 'selected' : ''} ${
78-
selectedTier === 'low' && inValidAmount ? 'invalidAmount' : ''
107+
onClick={() => handleTierChange("low")}
108+
className={`tier-hover ${selectedTier === "low" ? "selected" : ""} ${
109+
selectedTier === "low" && invalidAmount ? "invalidAmount" : ""
79110
} `}
80111
>
81112
<S.TierCardContent>
82113
{`Low`}
83114
<br />
84115
{`Priority`}
85116
<S.RateValueWrapper>
86-
{`${fees.low}`} <br /> {`sat/vB`}
117+
{`${fees.low}`} <br /> {`sat/vB`}
87118
<S.RateValue>{`${estimatedFee.low} Sats`}</S.RateValue> {/* Show estimated fee */}
88119
</S.RateValueWrapper>
89120
</S.TierCardContent>
90121
</S.TierCard>
91122

92123
<S.TierCard
93124
$isMobile={!isDesktop}
94-
onClick={() => handleTierChange({ id: 'med' })}
95-
className={`tier-hover ${selectedTier === 'med' ? 'selected' : ''} ${
96-
selectedTier === 'med' && inValidAmount ? 'invalidAmount' : ''
125+
onClick={() => handleTierChange("med")}
126+
className={`tier-hover ${selectedTier === "med" ? "selected" : ""} ${
127+
selectedTier === "med" && invalidAmount ? "invalidAmount" : ""
97128
} `}
98129
>
99130
<S.TierCardContent>
100131
{`Medium`}
101132
<br />
102133
{`Priority`}
103134
<S.RateValueWrapper>
104-
{`${fees.med}`} <br /> {`sat/vB`}
135+
{`${fees.med}`} <br /> {`sat/vB`}
105136
<S.RateValue>{`${estimatedFee.med} Sats`}</S.RateValue> {/* Show estimated fee */}
106137
</S.RateValueWrapper>
107138
</S.TierCardContent>
108139
</S.TierCard>
109140

110141
<S.TierCard
111142
$isMobile={!isDesktop}
112-
onClick={() => handleTierChange({ id: 'high' })}
113-
className={`tier-hover ${selectedTier === 'high' ? 'selected' : ''} ${
114-
selectedTier === 'high' && inValidAmount ? 'invalidAmount' : ''
143+
onClick={() => handleTierChange("high")}
144+
className={`tier-hover ${selectedTier === "high" ? "selected" : ""} ${
145+
selectedTier === "high" && invalidAmount ? "invalidAmount" : ""
115146
} `}
116147
>
117148
<S.TierCardContent>
@@ -130,119 +161,4 @@ const TieredFees: React.FC<TieredFeesProps> = ({ inValidAmount, handleFeeChange,
130161
);
131162
};
132163

133-
export default TieredFees;
134-
135-
// import React, { useEffect, useState } from 'react';
136-
// import * as S from './TieredFees.styles';
137-
// import { useResponsive } from '@app/hooks/useResponsive';
138-
// import { tiers } from '../../SendForm';
139-
140-
// interface FeeRecommendation {
141-
// fastestFee: number;
142-
// halfHourFee: number;
143-
// hourFee: number;
144-
// economyFee: number;
145-
// minimumFee: number;
146-
// }
147-
// type Fees = {
148-
// [key in tiers]: number;
149-
// };
150-
// interface TieredFeesProps {
151-
// // Define the props for your component here
152-
// handleFeeChange: (fee: number) => void;
153-
// inValidAmount: boolean;
154-
// }
155-
156-
// const TieredFees: React.FC<TieredFeesProps> = ({ inValidAmount, handleFeeChange }) => {
157-
// const { isDesktop, isTablet } = useResponsive();
158-
// const [fees, setFees] = useState<Fees>({ low: 0, med: 0, high: 0 });
159-
// const [selectedTier, setSelectedTier] = useState<tiers | null>('low');
160-
161-
// useEffect(() => {
162-
// const fetchFees = async () => {
163-
// try {
164-
// const response = await fetch('https://mempool.space/api/v1/fees/recommended');
165-
// const data: FeeRecommendation = await response.json();
166-
// setFees({
167-
// low: data.economyFee,
168-
// med: data.halfHourFee,
169-
// high: data.fastestFee,
170-
// });
171-
// } catch (error) {
172-
// console.error('Failed to fetch fees:', error);
173-
// }
174-
// };
175-
176-
// fetchFees();
177-
// }, []);
178-
// const handleTierChange = (tier: any) => {
179-
// console.log(tier);
180-
// setSelectedTier(tier.id);
181-
// };
182-
183-
// useEffect(() => {
184-
// handleFeeChange(fees[selectedTier as tiers]);
185-
// }, [selectedTier]);
186-
// return (
187-
// <S.TiersWrapper $isMobile={!isDesktop || !isTablet }>
188-
// <S.TierCard
189-
// $isMobile={!isDesktop}
190-
// onClick={() => handleTierChange({ id: 'low', rate: fees.med })}
191-
// className={`tier-hover ${selectedTier === 'low' ? 'selected' : ''} ${
192-
// selectedTier === 'low' && inValidAmount ? 'invalidAmount' : ''
193-
// } `}
194-
// >
195-
// <S.TierCardContent>
196-
// {`Low`}
197-
// <br />
198-
// {`Priority`}
199-
// <S.RateValueWrapper>
200-
// <span>{`${fees.low} sat/vB`}</span>
201-
// <S.RateValue>{`${fees.low} Sats`}</S.RateValue>
202-
// </S.RateValueWrapper>
203-
// </S.TierCardContent>
204-
// </S.TierCard>
205-
206-
// <S.TierCard
207-
// $isMobile={!isDesktop}
208-
// onClick={() => handleTierChange({ id: 'med', rate: fees.med })}
209-
// className={`tier-hover ${selectedTier === 'med' ? 'selected' : ''} ${
210-
// selectedTier === 'med' && inValidAmount ? 'invalidAmount' : ''
211-
// } `}
212-
// >
213-
// <S.TierCardContent>
214-
// {`Medium`}
215-
// <br />
216-
// {`Priority`}
217-
// <S.RateValueWrapper>
218-
// <span>{`${fees.med} sat/vB`}</span>
219-
// <S.RateValue>{`${fees.med} Sats`}</S.RateValue>
220-
// </S.RateValueWrapper>
221-
// </S.TierCardContent>
222-
// </S.TierCard>
223-
224-
// <S.TierCard
225-
// $isMobile={!isDesktop}
226-
// onClick={() => handleTierChange({ id: 'high', rate: fees.high })}
227-
// className={`tier-hover ${selectedTier === 'high' ? 'selected' : ''} ${
228-
// selectedTier === 'high' && inValidAmount ? 'invalidAmount' : ''
229-
// } `}
230-
// >
231-
// <S.TierCardContent>
232-
// <S.TierCardAmount>
233-
// {' '}
234-
// {`High`}
235-
// <br />
236-
// {`Priority`}
237-
// </S.TierCardAmount>
238-
// <S.RateValueWrapper>
239-
// <span>{`${fees.high} sat/vB`}</span>
240-
// <S.RateValue>{`${fees.high} Sats`}</S.RateValue>
241-
// </S.RateValueWrapper>
242-
// </S.TierCardContent>
243-
// </S.TierCard>
244-
// </S.TiersWrapper>
245-
// );
246-
// };
247-
248-
// export default TieredFees;
164+
export default TieredFees;

src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ interface ReplaceTransactionProps {
2121
const ReplaceTransaction: React.FC<ReplaceTransactionProps> = ({ onCancel, onReplace, transaction }) => {
2222
const { isDesktop, isTablet } = useResponsive();
2323
const { balanceData, isLoading: isBalanceLoading } = useBalanceData(); // Fetch balance data using the hook
24-
const { isAuthenticated, login, token} = useWalletAuth(); // Use wallet authentication
24+
const { isAuthenticated, login, token } = useWalletAuth(); // Use wallet authentication
2525

2626
const [isLoadingSize, setIsLoadingSize] = useState(false); //tx size fetching states
2727

@@ -223,8 +223,8 @@ const ReplaceTransaction: React.FC<ReplaceTransactionProps> = ({ onCancel, onRep
223223
<S.FieldLabel>Transaction ID</S.FieldLabel>
224224
<S.ValueWrapper $isMobile={!isDesktop || !isTablet}>
225225
<S.FieldValue>{transaction.txid}
226-
{" "}
227-
<ClipboardCopy textToCopy={transaction.txid} />
226+
{" "}
227+
<ClipboardCopy textToCopy={transaction.txid} />
228228
</S.FieldValue>
229229
</S.ValueWrapper>
230230
</S.FieldDisplay>
@@ -242,7 +242,13 @@ const ReplaceTransaction: React.FC<ReplaceTransactionProps> = ({ onCancel, onRep
242242
/>
243243
RBF Opt In
244244
</S.RBFWrapper> */}
245-
<TieredFees inValidAmount={inValidAmount} handleFeeChange={handleFeeRateChange} txSize={txSize} />
245+
<TieredFees
246+
invalidAmount={inValidAmount} // Make sure to pass the correct boolean prop
247+
handleFeeChange={handleFeeRateChange}
248+
transactionSize={txSize} // Pass the estimated transaction size
249+
originalFeeRate={transaction.feeRate} // Pass the original fee rate from the transaction to adjust the replacement fees
250+
/>
251+
246252
<S.FieldDisplay>
247253
<S.FieldLabel>New Fee Rate</S.FieldLabel>
248254
<S.ValueWrapper $isMobile={!isDesktop || !isTablet}>
@@ -257,7 +263,7 @@ const ReplaceTransaction: React.FC<ReplaceTransactionProps> = ({ onCancel, onRep
257263
}
258264
}}
259265
min={0} // Minimum value of 0 for the fee
260-
step={0.25} // Optional: define the increment step for the fee input
266+
step={1} // Optional: define the increment step for the fee input
261267
/>
262268
</S.ValueWrapper>
263269
</S.FieldDisplay>

0 commit comments

Comments
 (0)