@@ -22,7 +22,8 @@ export function useSend(options: UseSendOptions = {}): UseSendReturn {
2222 asset : initialAsset ?? null ,
2323 } ) ;
2424
25- const feeInitKeyRef = useRef < string | null > ( null ) ;
25+ const feeEstimateDebounceRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
26+ const feeEstimateSeqRef = useRef ( 0 ) ;
2627
2728 const isBioforestChain = chainConfig ?. chainKind === 'bioforest' ;
2829 const isWeb3Chain =
@@ -81,33 +82,83 @@ export function useSend(options: UseSendOptions = {}): UseSendReturn {
8182 setState ( ( prev ) => ( {
8283 ...prev ,
8384 asset,
84- feeLoading : true ,
85+ feeAmount : null ,
86+ feeMinAmount : null ,
87+ feeSymbol : '' ,
88+ feeLoading : false ,
8589 } ) ) ;
90+ } ,
91+ [ ] ,
92+ ) ;
8693
87- const shouldUseMock = useMock || ( ! isBioforestChain && ! isWeb3Chain ) || ! chainConfig || ! fromAddress ;
94+ useEffect ( ( ) => {
95+ if ( feeEstimateDebounceRef . current ) {
96+ clearTimeout ( feeEstimateDebounceRef . current ) ;
97+ feeEstimateDebounceRef . current = null ;
98+ }
8899
89- if ( shouldUseMock ) {
90- // Mock fee estimation delay
91- setTimeout ( ( ) => {
92- const fee = MOCK_FEES [ asset . assetType ] ?? { amount : '0.001' , symbol : asset . assetType } ;
93- const feeAmount = Amount . fromFormatted ( fee . amount , asset . decimals , fee . symbol ) ;
94- setState ( ( prev ) => ( {
95- ...prev ,
96- feeAmount : feeAmount ,
97- feeMinAmount : feeAmount ,
98- feeSymbol : fee . symbol ,
99- feeLoading : false ,
100- } ) ) ;
101- } , 300 ) ;
102- return ;
103- }
100+ if ( ! state . asset ) {
101+ return ;
102+ }
103+
104+ const shouldUseMock = useMock || ( ! isBioforestChain && ! isWeb3Chain ) || ! chainConfig || ! fromAddress ;
105+ const toAddress = state . toAddress . trim ( ) ;
106+ const hasValidAmount = state . amount ?. isPositive ( ) === true ;
107+ const isTronSelfTransfer =
108+ chainConfig ?. chainKind === 'tron' &&
109+ fromAddress !== undefined &&
110+ fromAddress . trim ( ) . length > 0 &&
111+ fromAddress . trim ( ) === toAddress ;
112+ const canEstimateFee = toAddress . length > 0 && hasValidAmount && ! isTronSelfTransfer ;
113+
114+ if ( ! canEstimateFee ) {
115+ setState ( ( prev ) => {
116+ if ( ! prev . feeLoading && prev . feeAmount === null && prev . feeMinAmount === null && prev . feeSymbol === '' ) {
117+ return prev ;
118+ }
119+ return {
120+ ...prev ,
121+ feeLoading : false ,
122+ feeAmount : null ,
123+ feeMinAmount : null ,
124+ feeSymbol : '' ,
125+ } ;
126+ } ) ;
127+ return ;
128+ }
104129
130+ setState ( ( prev ) => ( {
131+ ...prev ,
132+ feeLoading : true ,
133+ feeAmount : null ,
134+ feeMinAmount : null ,
135+ feeSymbol : '' ,
136+ } ) ) ;
137+
138+ const requestSeq = ++ feeEstimateSeqRef . current ;
139+ feeEstimateDebounceRef . current = setTimeout ( ( ) => {
105140 void ( async ( ) => {
106141 try {
107- // Use appropriate fee fetcher based on chain type
108- const feeEstimate = isWeb3Chain
109- ? await fetchWeb3Fee ( chainConfig , fromAddress )
110- : await fetchBioforestFee ( chainConfig , fromAddress ) ;
142+ const feeEstimate = shouldUseMock
143+ ? ( ( ) => {
144+ const fee = MOCK_FEES [ state . asset ! . assetType ] ?? { amount : '0.001' , symbol : state . asset ! . assetType } ;
145+ return {
146+ amount : Amount . fromFormatted ( fee . amount , state . asset ! . decimals , fee . symbol ) ,
147+ symbol : fee . symbol ,
148+ } ;
149+ } ) ( )
150+ : isWeb3Chain && chainConfig && fromAddress
151+ ? await fetchWeb3Fee ( {
152+ chainConfig,
153+ fromAddress,
154+ toAddress,
155+ amount : state . amount ?? undefined ,
156+ } )
157+ : await fetchBioforestFee ( chainConfig ! , fromAddress ! ) ;
158+
159+ if ( requestSeq !== feeEstimateSeqRef . current ) {
160+ return ;
161+ }
111162
112163 setState ( ( prev ) => ( {
113164 ...prev ,
@@ -117,32 +168,37 @@ export function useSend(options: UseSendOptions = {}): UseSendReturn {
117168 feeLoading : false ,
118169 } ) ) ;
119170 } catch ( error ) {
171+ if ( requestSeq !== feeEstimateSeqRef . current ) {
172+ return ;
173+ }
120174 setState ( ( prev ) => ( {
121175 ...prev ,
176+ feeAmount : null ,
177+ feeMinAmount : null ,
178+ feeSymbol : '' ,
122179 feeLoading : false ,
123180 errorMessage : error instanceof Error ? error . message : t ( 'error:transaction.feeEstimateFailed' ) ,
124181 } ) ) ;
125182 }
126183 } ) ( ) ;
127- } ,
128- [ chainConfig , fromAddress , isBioforestChain , isWeb3Chain , useMock ] ,
129- ) ;
184+ } , 300 ) ;
130185
131- useEffect ( ( ) => {
132- if ( ! state . asset ) return ;
133- if ( state . feeLoading ) return ;
134-
135- const feeKey = `${ chainConfig ?. id ?? 'unknown' } :${ fromAddress ?? '' } :${ state . asset . assetType } ` ;
136- const feeKeyChanged = feeInitKeyRef . current !== feeKey ;
137- if ( feeKeyChanged ) {
138- feeInitKeyRef . current = feeKey ;
139- }
140-
141- if ( ! feeKeyChanged && state . feeAmount ) return ;
142- if ( ! feeKeyChanged && ! state . feeAmount ) return ;
143-
144- setAsset ( state . asset ) ;
145- } , [ chainConfig ?. id , fromAddress , setAsset , state . asset , state . feeAmount , state . feeLoading ] ) ;
186+ return ( ) => {
187+ if ( feeEstimateDebounceRef . current ) {
188+ clearTimeout ( feeEstimateDebounceRef . current ) ;
189+ feeEstimateDebounceRef . current = null ;
190+ }
191+ } ;
192+ } , [
193+ chainConfig ,
194+ fromAddress ,
195+ isBioforestChain ,
196+ isWeb3Chain ,
197+ state . amount ,
198+ state . asset ,
199+ state . toAddress ,
200+ useMock ,
201+ ] ) ;
146202
147203 // Get current balance from external source (single source of truth)
148204 const currentBalance = useMemo ( ( ) => {
@@ -291,7 +347,7 @@ export function useSend(options: UseSendOptions = {}): UseSendReturn {
291347 txHash : null ,
292348 errorMessage : result . message ,
293349 } ) ) ;
294- return { status : 'error' as const } ;
350+ return { status : 'error' as const , message : result . message } ;
295351 }
296352
297353 setState ( ( prev ) => ( {
0 commit comments