@@ -3,19 +3,22 @@ import { Chain } from '@/models/chain'
3
3
import { NotificationSeverity } from '@/models/notification'
4
4
import { Token } from '@/models/token'
5
5
import { AmountInfo } from '@/models/transfer'
6
- import { EthereumTokens , PolkadotTokens } from '@/registry/mainnet/tokens'
7
6
import { getCachedTokenPrice } from '@/services/balance'
8
7
import { Direction , resolveDirection } from '@/services/transfer'
9
8
import { getPlaceholderAddress } from '@/utils/address'
10
- import { getCurrencyId , getNativeToken , getRelayNode } from '@/utils/paraspell'
11
- import { safeConvertAmount , toHuman } from '@/utils/transfer'
12
- import { getOriginFeeDetails , getTNode } from '@paraspell/sdk'
9
+ import { getCurrencyId , getNativeToken , getRelayNode , getParaSpellNode } from '@/utils/paraspell'
10
+ import { toHuman } from '@/utils/transfer'
11
+ import { getOriginFeeDetails , TNodeDotKsmWithRelayChains } from '@paraspell/sdk'
13
12
import { captureException } from '@sentry/nextjs'
14
- import { Context , toEthereum , toPolkadot } from '@snowbridge/api'
15
13
import { useCallback , useEffect , useState } from 'react'
16
14
import useEnvironment from './useEnvironment'
17
15
import useSnowbridgeContext from './useSnowbridgeContext'
18
- import { ContractTransaction } from 'ethers'
16
+ import { getRoute } from '@/utils/routes'
17
+ import { getFeeEstimate } from '@/utils/snowbridge'
18
+
19
+ export type Fee =
20
+ | { origin : 'Ethereum' ; bridging : AmountInfo ; execution : AmountInfo | null }
21
+ | { origin : 'Polkadot' ; fee : AmountInfo }
19
22
20
23
const useFees = (
21
24
sourceChain ?: Chain | null ,
@@ -41,123 +44,93 @@ const useFees = (
41
44
return
42
45
}
43
46
44
- const direction = resolveDirection ( sourceChain , destinationChain )
45
- // TODO: this should be the fee token, not necessarily the native token. Also adjust the USD value accordingly below.
46
- const nativeToken = getNativeToken ( sourceChain )
47
+ const route = getRoute ( env , sourceChain , destinationChain )
48
+ if ( ! route ) throw new Error ( 'Route not supported' )
49
+
50
+ // TODO: this should be the fee token, not necessarily the native token.
51
+ const feeToken = getNativeToken ( sourceChain )
47
52
48
53
try {
49
54
setLoading ( true )
50
- let fees : string
51
- let tokenUSDValue : number = 0
52
-
53
- if (
54
- ( direction === Direction . ToEthereum || direction === Direction . ToPolkadot ) &&
55
- isSnowbridgeContextLoading
56
- ) {
57
- setFees ( null )
58
- setEthereumTxFees ( null )
59
- return
60
- }
61
55
62
- switch ( direction ) {
63
- case Direction . ToEthereum : {
64
- if ( ! snowbridgeContext || snowbridgeContextError )
65
- throw snowbridgeContextError ?? new Error ( 'Snowbridge context undefined' )
66
- tokenUSDValue = ( await getCachedTokenPrice ( PolkadotTokens . DOT ) ) ?. usd ?? 0
67
- fees = ( await toEthereum . getSendFee ( snowbridgeContext ) ) . toString ( )
56
+ switch ( route . sdk ) {
57
+ case 'ParaSpellApi' : {
58
+ const relay = getRelayNode ( env )
59
+ const sourceChainNode = getParaSpellNode ( sourceChain , relay )
60
+ if ( ! sourceChainNode ) throw new Error ( 'Source chain id not found' )
61
+
62
+ const destinationChainNode = getParaSpellNode ( destinationChain , relay )
63
+ if ( ! destinationChainNode ) throw new Error ( 'Destination chain id not found' )
64
+
65
+ const currency = getCurrencyId ( env , sourceChainNode , sourceChain . uid , token )
66
+ const info = await getOriginFeeDetails ( {
67
+ origin : sourceChainNode as TNodeDotKsmWithRelayChains ,
68
+ destination : destinationChainNode ,
69
+ currency : { ...currency , amount : BigInt ( 10 ** token . decimals ) . toString ( ) } , // hardcoded amount because the fee is usually independent of the amount
70
+ account : getPlaceholderAddress ( sourceChain . supportedAddressTypes [ 0 ] ) , // hardcode sender address because the fee is usually independent of the sender
71
+ accountDestination : getPlaceholderAddress ( destinationChain . supportedAddressTypes [ 0 ] ) , // hardcode recipient address because the fee is usually independent of the recipient
72
+ api : sourceChain . rpcConnection ,
73
+ ahAccount : getPlaceholderAddress ( sourceChain . supportedAddressTypes [ 0 ] ) ,
74
+ } )
75
+
76
+ const feeTokenInDollars = ( await getCachedTokenPrice ( feeToken ) ) ?. usd ?? 0
77
+ const fee = info . xcmFee
78
+ setFees ( {
79
+ amount : fee ,
80
+ token : feeToken ,
81
+ inDollars : feeTokenInDollars ? toHuman ( fee , feeToken ) * feeTokenInDollars : 0 ,
82
+ } )
83
+ setCanPayFees ( info . sufficientForXCM )
84
+
68
85
break
69
86
}
70
87
71
- case Direction . ToPolkadot : {
88
+ case 'SnowbridgeApi' : {
89
+ const direction = resolveDirection ( sourceChain , destinationChain )
90
+ if (
91
+ ( direction === Direction . ToEthereum || direction === Direction . ToPolkadot ) &&
92
+ isSnowbridgeContextLoading
93
+ ) {
94
+ setFees ( null )
95
+ setEthereumTxFees ( null )
96
+ return
97
+ }
98
+
72
99
if ( ! snowbridgeContext || snowbridgeContextError )
73
100
throw snowbridgeContextError ?? new Error ( 'Snowbridge context undefined' )
74
- tokenUSDValue = ( await getCachedTokenPrice ( EthereumTokens . ETH ) ) ?. usd ?? 0
75
101
76
- const sendFee = await toPolkadot . getSendFee (
102
+ const fee = await getFeeEstimate (
103
+ token ,
104
+ destinationChain ,
105
+ direction ,
77
106
snowbridgeContext ,
78
- token . address ,
79
- destinationChain . chainId ,
80
- BigInt ( 0 ) ,
107
+ senderAddress ,
108
+ recipientAddress ,
109
+ amount ,
81
110
)
82
- fees = sendFee . toString ( )
111
+ if ( ! fee ) {
112
+ setFees ( null )
113
+ setEthereumTxFees ( null )
114
+ return
115
+ }
83
116
84
- try {
85
- if ( ! senderAddress || ! recipientAddress || ! amount || ! sendFee ) {
86
- setEthereumTxFees ( null )
117
+ switch ( fee . origin ) {
118
+ case 'Ethereum' : {
119
+ setFees ( fee . bridging )
120
+ setEthereumTxFees ( fee . execution )
121
+ break
122
+ }
123
+ case 'Polkadot' : {
124
+ setFees ( fee . fee )
87
125
break
88
126
}
89
- // Sender, Recipient and amount can't be defaulted here since the Smart contract verify the ERC20 token allowance.
90
- const { tx } = await toPolkadot . createTx (
91
- snowbridgeContext . config . appContracts . gateway ,
92
- senderAddress ,
93
- recipientAddress ,
94
- token . address ,
95
- destinationChain . chainId ,
96
- safeConvertAmount ( amount , token ) ?? 0n ,
97
- sendFee ,
98
- BigInt ( 0 ) ,
99
- )
100
-
101
- const { txFees, txFeesInDollars } = await estimateTransactionFees (
102
- tx ,
103
- snowbridgeContext ,
104
- nativeToken ,
105
- tokenUSDValue ,
106
- )
107
-
108
- setEthereumTxFees ( {
109
- amount : txFees ,
110
- token : nativeToken ,
111
- inDollars : txFeesInDollars ? txFeesInDollars : 0 ,
112
- } )
113
- break
114
- } catch ( error ) {
115
- // Estimation can fail for multiple reasons, including errors such as insufficient token approval.
116
- console . log ( 'Estimated Tx cost failed' , error instanceof Error && { ...error } )
117
- captureException ( new Error ( 'Estimated Tx cost failed' ) , {
118
- level : 'warning' ,
119
- tags : {
120
- useFeesHook :
121
- error instanceof Error && 'action' in error && typeof error . action === 'string'
122
- ? error . action
123
- : 'estimateTransactionFees' ,
124
- } ,
125
- extra : { error } ,
126
- } )
127
- break
128
127
}
129
- }
130
-
131
- case Direction . WithinPolkadot : {
132
- const relay = getRelayNode ( env )
133
- const sourceChainNode = getTNode ( sourceChain . chainId , relay )
134
- const destinationChainNode = getTNode ( destinationChain . chainId , relay )
135
- if ( ! sourceChainNode || ! destinationChainNode ) throw new Error ( 'Chain id not found' )
136
- const currency = getCurrencyId ( env , sourceChainNode , sourceChain . uid , token )
137
-
138
- const info = await getOriginFeeDetails ( {
139
- origin : sourceChainNode ,
140
- destination : destinationChainNode ,
141
- currency : { ...currency , amount : BigInt ( 10 ** token . decimals ) . toString ( ) } , // hardcoded amount because the fee is usually independent of the amount
142
- account : getPlaceholderAddress ( sourceChain . supportedAddressTypes [ 0 ] ) , // hardcode sender address because the fee is usually independent of the sender
143
- accountDestination : getPlaceholderAddress ( destinationChain . supportedAddressTypes [ 0 ] ) , // hardcode recipient address because the fee is usually independent of the recipient
144
- api : sourceChain . rpcConnection ,
145
- } )
146
- tokenUSDValue = ( await getCachedTokenPrice ( nativeToken ) ) ?. usd ?? 0
147
- fees = info . xcmFee . toString ( )
148
- setCanPayFees ( info . sufficientForXCM )
149
128
break
150
129
}
151
130
152
131
default :
153
132
throw new Error ( 'Unsupported direction' )
154
133
}
155
-
156
- setFees ( {
157
- amount : fees ,
158
- token : nativeToken ,
159
- inDollars : tokenUSDValue ? toHuman ( fees , nativeToken ) * tokenUSDValue : 0 ,
160
- } )
161
134
} catch ( error ) {
162
135
setFees ( null )
163
136
setEthereumTxFees ( null )
@@ -171,6 +144,7 @@ const useFees = (
171
144
} finally {
172
145
setLoading ( false )
173
146
}
147
+
174
148
// eslint-disable-next-line react-hooks/exhaustive-deps
175
149
} , [
176
150
env ,
@@ -191,35 +165,4 @@ const useFees = (
191
165
return { fees, ethereumTxfees, loading, refetch : fetchFees , canPayFees }
192
166
}
193
167
194
- /**
195
- * Estimates the gas cost for a given Ethereum transaction in both native token and USD value.
196
- *
197
- * @param tx - The contract transaction object.
198
- * @param snowbridgeContext - The Snowbridge context containing Ethereum API.
199
- * @param nativeToken - The native token.
200
- * @param nativeTokenUSDValue - The USD value of the native token.
201
- * @returns An object containing the tx estimate gas fee in native tokens and its USD value.
202
- */
203
- const estimateTransactionFees = async (
204
- tx : ContractTransaction ,
205
- snowbridgeContext : Context ,
206
- nativeToken : Token ,
207
- nativeTokenUSDValue : number ,
208
- ) => {
209
- // Fetch gas estimation and fee data
210
- const [ txGas , { gasPrice, maxPriorityFeePerGas } ] = await Promise . all ( [
211
- snowbridgeContext . ethereum ( ) . estimateGas ( tx ) ,
212
- snowbridgeContext . ethereum ( ) . getFeeData ( ) ,
213
- ] )
214
-
215
- // Get effective fee per gas & get USD fee value
216
- const effectiveFeePerGas = ( gasPrice ?? 0n ) + ( maxPriorityFeePerGas ?? 0n )
217
- const txFeesInToken = toHuman ( ( txGas * effectiveFeePerGas ) . toString ( ) , nativeToken )
218
-
219
- return {
220
- txFees : txFeesInToken ,
221
- txFeesInDollars : txFeesInToken * nativeTokenUSDValue ,
222
- }
223
- }
224
-
225
168
export default useFees
0 commit comments