@xswap-link/sdk 0.10.15 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xswap-link/sdk",
3
- "version": "0.10.15",
3
+ "version": "0.11.0",
4
4
  "description": "JavaScript SDK for XSwap platform",
5
5
  "homepage": "https://github.com/xswap-link/xswap-sdk",
6
6
  "repository": {
@@ -38,7 +38,7 @@
38
38
  "@fortawesome/react-fontawesome": "^0.2.0",
39
39
  "@r2wc/core": "^1.1.0",
40
40
  "@solana/spl-token": "^0.4.13",
41
- "@solana/wallet-adapter-react": "^0.15.35",
41
+ "@solana/wallet-adapter-react": "^0.15.38",
42
42
  "@solana/wallet-adapter-wallets": "^0.19.32",
43
43
  "@solana/web3.js": "^1.98.0",
44
44
  "@tanstack/react-query": "^5.64.2",
@@ -0,0 +1,12 @@
1
+ export const DotRedIcon = () => {
2
+ return (
3
+ <svg
4
+ width="6"
5
+ height="7"
6
+ viewBox="0 0 6 7"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ >
9
+ <circle cx="3" cy="3.91418" r="3" fill="#F44336" />
10
+ </svg>
11
+ );
12
+ };
@@ -15,6 +15,7 @@ export * from "./CloseIcon";
15
15
  export * from "./CoinsIcon";
16
16
  export * from "./CopyIcon";
17
17
  export * from "./DotGreenIcon";
18
+ export * from "./DotRedIcon";
18
19
  export * from "./DownArrorIcon";
19
20
  export * from "./ErrorIcon";
20
21
  export * from "./ErrorModalStatusIcon";
@@ -2,6 +2,7 @@ import { TokenLogo } from "@src/components";
2
2
  import { useSwapContext } from "@src/context";
3
3
  import { SwapPanelType } from "../../../index";
4
4
  import { useMemo } from "react";
5
+ import { Ecosystem } from "@src/models";
5
6
 
6
7
  type Props = {
7
8
  type: SwapPanelType;
@@ -16,6 +17,7 @@ export const SwapPanel = ({ type }: Props) => {
16
17
  srcValueUsd,
17
18
  dstValue,
18
19
  dstValueUsd,
20
+ bridgeUI,
19
21
  } = useSwapContext();
20
22
 
21
23
  const token = useMemo(
@@ -28,15 +30,20 @@ export const SwapPanel = ({ type }: Props) => {
28
30
  [dstChain, srcChain, type],
29
31
  );
30
32
 
31
- const value = useMemo(
32
- () => (type === "source" ? srcValue : dstValue),
33
- [dstValue, srcValue, type],
34
- );
33
+ const value = useMemo(() => {
34
+ if (
35
+ type === "destination" &&
36
+ bridgeUI &&
37
+ srcChain?.ecosystem === Ecosystem.SOLANA
38
+ ) {
39
+ return srcValue;
40
+ }
41
+ return type === "source" ? srcValue : dstValue;
42
+ }, [type, bridgeUI, srcChain?.ecosystem, srcValue, dstValue]);
35
43
 
36
- const valueUsd = useMemo(
37
- () => (type === "source" ? srcValueUsd : dstValueUsd),
38
- [dstValueUsd, srcValueUsd, type],
39
- );
44
+ const valueUsd = useMemo(() => {
45
+ return type === "source" ? srcValueUsd : dstValueUsd;
46
+ }, [dstValueUsd, srcValueUsd, type]);
40
47
 
41
48
  return (
42
49
  <div className="flex justify-between bg-gradient-to-r from-t_main_accent_light/20 to-t_main_accent_dark/20 p-5 rounded-xl">
@@ -18,7 +18,11 @@ import {
18
18
  } from "@src/contracts";
19
19
  import { useEvmContractApi } from "@src/hooks";
20
20
  import useNetworks from "@src/hooks/networkManagement/useNetworks";
21
- import { Ecosystem, Transaction, TxStatus } from "@src/models";
21
+ import {
22
+ Ecosystem,
23
+ TxStatus,
24
+ Transaction as EVMTransaction,
25
+ } from "@src/models";
22
26
  import { renderTxStatus } from "@src/services";
23
27
  import { ContractReceipt, constants, ethers } from "ethers";
24
28
  import { useCallback } from "react";
@@ -28,20 +32,31 @@ import { Header } from "./Header";
28
32
  import { Steps } from "./Steps";
29
33
  import { SwapPanel } from "./SwapPanel";
30
34
  import { useWallet } from "@solana/wallet-adapter-react";
35
+
31
36
  import {
32
37
  Connection,
33
- Keypair,
34
38
  PublicKey,
39
+ Transaction,
35
40
  VersionedTransaction,
36
41
  } from "@solana/web3.js";
37
42
  import {
43
+ AddressConversion,
38
44
  CCIPClient,
39
45
  CCIPContext,
40
46
  CCIPCoreConfig,
41
47
  CCIPProvider,
42
48
  CCIPSendRequest,
43
49
  } from "@src/services/svm";
44
- import BigNumber from "bignumber.js";
50
+ import { BN } from "@coral-xyz/anchor";
51
+ import {
52
+ Account,
53
+ createApproveInstruction,
54
+ getAccount,
55
+ getAssociatedTokenAddressSync,
56
+ TOKEN_2022_PROGRAM_ID,
57
+ TOKEN_PROGRAM_ID,
58
+ } from "@solana/spl-token";
59
+ import { findFeeBillingSignerPDA } from "@src/services/svm/utils/pdas";
45
60
 
46
61
  type Props = {
47
62
  onCloseClick: () => void;
@@ -77,7 +92,8 @@ export const TxOverview = ({ onCloseClick }: Props) => {
77
92
  evm: { signer, address: evmAddress },
78
93
  },
79
94
  } = useNetworks();
80
- const wallet = useWallet();
95
+ const { publicKey, signTransaction } = useWallet();
96
+
81
97
  const evmContractApi = useEvmContractApi();
82
98
 
83
99
  const trackTransaction = useCallback(
@@ -86,7 +102,7 @@ export const TxOverview = ({ onCloseClick }: Props) => {
86
102
  return;
87
103
  }
88
104
 
89
- const transactionData: Transaction = {
105
+ const transactionData: EVMTransaction = {
90
106
  hash: txReceipt.transactionHash,
91
107
  timestamp: Date.now() / 1000,
92
108
  sourceChainId: srcChain.chainId,
@@ -278,18 +294,57 @@ export const TxOverview = ({ onCloseClick }: Props) => {
278
294
  "confirmed",
279
295
  );
280
296
 
297
+ if (!publicKey) {
298
+ throw new Error("No public key found");
299
+ }
300
+
301
+ // Delegate token authority first (APPROVE)
302
+ await checkAndDelegateTokenAuthority(
303
+ connection,
304
+ publicKey,
305
+ new PublicKey(srcToken.address),
306
+ CCIP_ROUTER_PROGRAM_ID,
307
+ signTransaction as (
308
+ tx: VersionedTransaction,
309
+ ) => Promise<VersionedTransaction>,
310
+ );
311
+
281
312
  const provider: CCIPProvider = {
282
313
  connection,
283
- wallet: Keypair.generate(),
314
+ wallet: null,
284
315
  getAddress() {
285
- return wallet.publicKey || PublicKey.default;
316
+ return publicKey || PublicKey.default;
286
317
  },
287
318
  async signTransaction(tx: VersionedTransaction) {
288
- const signedTransaction = await wallet.signTransaction?.(tx);
289
- if (!signedTransaction) {
290
- throw new Error("Failed to sign the transaction.");
319
+ if (!signTransaction) {
320
+ throw new Error("No sign transaction function found");
321
+ }
322
+
323
+ if (!(tx instanceof VersionedTransaction)) {
324
+ throw new Error(
325
+ "Transaction must be a VersionedTransaction",
326
+ );
327
+ }
328
+
329
+ try {
330
+ const signedTx = await signTransaction(tx);
331
+
332
+ if (!signedTx.signatures[0]?.some((byte) => byte !== 0)) {
333
+ throw new Error("Transaction was not properly signed");
334
+ }
335
+
336
+ return signedTx;
337
+ } catch (error) {
338
+ console.error("Transaction signing/sending failed:", error);
339
+ if (error instanceof Error && "getLogs" in error) {
340
+ console.error(
341
+ "Transaction logs:",
342
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
343
+ await (error as any).getLogs(),
344
+ );
345
+ }
346
+ throw error;
291
347
  }
292
- return signedTransaction;
293
348
  },
294
349
  };
295
350
 
@@ -312,37 +367,45 @@ export const TxOverview = ({ onCloseClick }: Props) => {
312
367
  const client = new CCIPClient(context);
313
368
 
314
369
  const extraArgs = client.createExtraArgs({
315
- gasLimit: 0,
316
370
  allowOutOfOrderExecution: true,
317
371
  });
318
372
 
319
- if (!dstToken) return undefined;
373
+ if (
374
+ !evmAddress ||
375
+ !evmAddress.startsWith("0x") ||
376
+ evmAddress.length !== 42
377
+ ) {
378
+ throw new Error(
379
+ "Invalid EVM address. Must be a valid 20-byte address starting with 0x",
380
+ );
381
+ }
320
382
 
321
383
  const sendRequest: CCIPSendRequest = {
322
- destChainSelector: new BigNumber(dstChain?.ccipChainId ?? ""),
323
- receiver: Buffer.from(
324
- evmAddress && evmAddress.startsWith("0x")
325
- ? evmAddress.slice(2)
326
- : evmAddress || "",
327
- "hex",
328
- ),
329
- data: Buffer.alloc(0),
384
+ destChainSelector: new BN(dstChain?.ccipChainId || "0"),
385
+ receiver: AddressConversion.evmAddressToSolanaBytes(evmAddress),
386
+ data: new Uint8Array(0),
330
387
  tokenAmounts: [
331
388
  {
332
389
  token: new PublicKey(srcToken.address),
333
- amount: new BigNumber(srcValueWei.toString()), // TODO: Check if this is correct, probably not
390
+ amount: new BN(srcValueWei.toString()),
334
391
  },
335
392
  ],
336
393
  feeToken: PublicKey.default,
337
394
  extraArgs: extraArgs,
338
395
  };
339
396
 
340
- const result = await client.sendWithMessageId(sendRequest);
341
- console.log(`Message sent! Transaction: ${result.txSignature}`);
342
- console.log(`Message ID: ${result.messageId}`);
397
+ try {
398
+ const result = await client.sendWithMessageId(sendRequest);
399
+
400
+ if (result.txSignature) {
401
+ return result.txSignature;
402
+ }
403
+ } catch (sendError) {
404
+ console.error("CCIP transaction failed:", sendError);
405
+ }
343
406
  }
344
407
 
345
- if (!route || !route.swapTransaction || !wallet) {
408
+ if (!route || !route.swapTransaction || !publicKey) {
346
409
  return;
347
410
  }
348
411
 
@@ -350,7 +413,7 @@ export const TxOverview = ({ onCloseClick }: Props) => {
350
413
  Buffer.from(route.swapTransaction, "base64"),
351
414
  );
352
415
 
353
- return wallet.signTransaction?.(transaction);
416
+ return signTransaction?.(transaction);
354
417
  },
355
418
  txStatus: TxStatus.SIGNING_TX,
356
419
  handlers: {
@@ -376,11 +439,12 @@ export const TxOverview = ({ onCloseClick }: Props) => {
376
439
  trackTransaction,
377
440
  dstChain?.chainId,
378
441
  dstChain?.ccipChainId,
379
- dstToken,
442
+ dstToken?.address,
380
443
  dstValueWei,
381
444
  setTxNeedsApproval,
382
445
  bridgeUI,
383
- wallet,
446
+ signTransaction,
447
+ publicKey,
384
448
  evmAddress,
385
449
  srcValueWei,
386
450
  ]);
@@ -417,3 +481,106 @@ export const TxOverview = ({ onCloseClick }: Props) => {
417
481
  </div>
418
482
  );
419
483
  };
484
+
485
+ // Add this function to check token delegation and delegate if necessary
486
+ async function checkAndDelegateTokenAuthority(
487
+ connection: Connection,
488
+ publicKey: PublicKey,
489
+ tokenMint: PublicKey,
490
+ routerProgramId: PublicKey,
491
+ signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>,
492
+ ) {
493
+ try {
494
+ const [feeBillingSignerPDA] = findFeeBillingSignerPDA(routerProgramId);
495
+
496
+ const tokenAccount = getAssociatedTokenAddressSync(
497
+ tokenMint,
498
+ publicKey,
499
+ false,
500
+ TOKEN_PROGRAM_ID,
501
+ );
502
+
503
+ const token2022Account = getAssociatedTokenAddressSync(
504
+ tokenMint,
505
+ publicKey,
506
+ false,
507
+ TOKEN_2022_PROGRAM_ID,
508
+ );
509
+ let is2022Token = false;
510
+ try {
511
+ let tokenAccountInfo: Account | undefined;
512
+ try {
513
+ tokenAccountInfo = await getAccount(
514
+ connection,
515
+ tokenAccount,
516
+ connection.commitment,
517
+ TOKEN_PROGRAM_ID,
518
+ );
519
+ } catch (err) {
520
+ console.error("Failed to fetch Token account:", err);
521
+ }
522
+ if (!tokenAccountInfo) {
523
+ try {
524
+ tokenAccountInfo = await getAccount(
525
+ connection,
526
+ token2022Account,
527
+ connection.commitment,
528
+ TOKEN_2022_PROGRAM_ID,
529
+ );
530
+ is2022Token = true;
531
+ } catch (err) {
532
+ console.error("Failed to fetch Token-2022 account:", err);
533
+ }
534
+ }
535
+ if (!tokenAccountInfo) {
536
+ throw new Error("Failed to fetch tokenAccountInfo");
537
+ }
538
+ if (
539
+ tokenAccountInfo.delegate !== null &&
540
+ tokenAccountInfo.delegate.equals(feeBillingSignerPDA) &&
541
+ tokenAccountInfo.delegatedAmount > BigInt(0)
542
+ ) {
543
+ // Token already properly delegated, skipping approval
544
+ return;
545
+ }
546
+ } catch (error) {
547
+ console.error(
548
+ "Error checking token account, proceeding with delegation:",
549
+ error,
550
+ );
551
+ }
552
+
553
+ const MAX_UINT64 = ((BigInt(1) << BigInt(64)) - BigInt(1)).toString();
554
+ const approveInstruction = createApproveInstruction(
555
+ is2022Token ? token2022Account : tokenAccount,
556
+ feeBillingSignerPDA,
557
+ publicKey,
558
+ BigInt(MAX_UINT64),
559
+ [],
560
+ is2022Token ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID,
561
+ );
562
+
563
+ const { blockhash, lastValidBlockHeight } =
564
+ await connection.getLatestBlockhash();
565
+ const transaction = new Transaction({
566
+ feePayer: publicKey,
567
+ blockhash,
568
+ lastValidBlockHeight,
569
+ });
570
+
571
+ transaction.add(approveInstruction);
572
+
573
+ const versionedTx = new VersionedTransaction(transaction.compileMessage());
574
+ const signedTx = await signTransaction(versionedTx);
575
+ const signature = await connection.sendRawTransaction(signedTx.serialize());
576
+
577
+ await connection.confirmTransaction({
578
+ signature,
579
+ blockhash,
580
+ lastValidBlockHeight,
581
+ });
582
+ } catch (error) {
583
+ console.error("Failed to check or delegate token authority:", error);
584
+ throw error;
585
+ }
586
+ }
@@ -7,6 +7,8 @@ import {
7
7
  } from "@src/assets/icons";
8
8
  import { useSwapContext, useTxUIWrapper } from "@src/context";
9
9
  import { TokenItem } from "./TokenItem";
10
+ import { useMemo } from "react";
11
+ import { Ecosystem } from "@src/models";
10
12
 
11
13
  type Props = {
12
14
  onCloseClick: () => void;
@@ -22,8 +24,16 @@ export const TxResult = ({ onCloseClick }: Props) => {
22
24
  dstToken,
23
25
  srcValue,
24
26
  dstValue,
27
+ bridgeUI,
25
28
  } = useSwapContext();
26
29
 
30
+ const value = useMemo(() => {
31
+ if (bridgeUI && srcChain?.ecosystem === Ecosystem.SOLANA) {
32
+ return srcValue;
33
+ }
34
+ return dstValue;
35
+ }, [bridgeUI, srcChain?.ecosystem, srcValue, dstValue]);
36
+
27
37
  return (
28
38
  <div className="flex flex-col gap-5 text-t_text_primary justify-center items-center">
29
39
  {txError ? (
@@ -63,7 +73,7 @@ export const TxResult = ({ onCloseClick }: Props) => {
63
73
  <div className="-rotate-90 flex justify-center w-8 fill-t_text_primary opacity-50">
64
74
  <ArrowDownLongIcon />
65
75
  </div>
66
- <TokenItem chain={dstChain} token={dstToken} value={dstValue} />
76
+ <TokenItem chain={dstChain} token={dstToken} value={value} />
67
77
  </div>
68
78
  )}
69
79
  <a
@@ -1,9 +1,12 @@
1
1
  import { useSwapContext } from "@src/context";
2
2
  import { weiToHumanReadable } from "@src/utils";
3
- import { FC } from "react";
4
3
  import { Fee } from "../Fee";
5
4
 
6
- export const Fees: FC = () => {
5
+ export const Fees = ({
6
+ solanaBridgeFee,
7
+ }: {
8
+ solanaBridgeFee: string | null;
9
+ }) => {
7
10
  const { route, slippage, feeToken, dstToken, expressDelivery } =
8
11
  useSwapContext();
9
12
 
@@ -11,11 +14,13 @@ export const Fees: FC = () => {
11
14
  {
12
15
  name: "CCIP Fee: ",
13
16
  value: `${
14
- weiToHumanReadable({
15
- amount: route?.xSwapFees.ccipFee || "0",
16
- decimals: feeToken?.decimals || 18,
17
- precisionFractionalPlaces: 5,
18
- }) || 0
17
+ solanaBridgeFee
18
+ ? solanaBridgeFee
19
+ : weiToHumanReadable({
20
+ amount: route?.xSwapFees.ccipFee || "0",
21
+ decimals: feeToken?.decimals || 18,
22
+ precisionFractionalPlaces: 5,
23
+ }) || 0
19
24
  } ${feeToken?.symbol || ""}`,
20
25
  },
21
26
  {
@@ -2,15 +2,156 @@ import { ArrowDownIcon, TokensIcon } from "@src/assets/icons";
2
2
  import { Skeleton } from "@src/components";
3
3
  import { useSwapContext } from "@src/context";
4
4
  import { safeBigNumberFrom, weiToHumanReadable } from "@src/utils";
5
- import { useMemo, useState } from "react";
5
+ import { useEffect, useMemo, useState } from "react";
6
6
  import { DeliveryInfo } from "./DeliveryInfo";
7
7
  import { Fees } from "./Fees";
8
+ import { Ecosystem } from "@src/models";
9
+ import { Connection, PublicKey } from "@solana/web3.js";
10
+ import {
11
+ CCIP_ROUTER_PROGRAM_ID,
12
+ FEE_QUOTER_PROGRAM_ID,
13
+ LINK_TOKEN_MINT,
14
+ PROGRAM_ID,
15
+ RMN_REMOTE_PROGRAM_ID,
16
+ SOLANA_MAINNET_RPC_URL,
17
+ SYSTEM_PROGRAM_ID,
18
+ } from "@src/constants";
19
+ import {
20
+ AddressConversion,
21
+ CCIPClient,
22
+ CCIPContext,
23
+ CCIPCoreConfig,
24
+ CCIPProvider,
25
+ } from "@src/services/svm";
26
+ import { BN } from "@coral-xyz/anchor";
27
+ import { useWallet } from "@solana/wallet-adapter-react";
28
+ import useNetworks from "@src/hooks/networkManagement/useNetworks";
8
29
 
9
30
  export const FeesPanel = () => {
10
31
  const [expanded, setExpanded] = useState(false);
32
+ const [isFetchingSolanaFee, setIsFetchingSolanaFee] = useState(false);
33
+ const [solanaBridgeFee, setSolanaBridgeFee] = useState<string | null>(null);
11
34
 
12
- const { route, feeToken, isFetchingRoute, srcChain, dstChain } =
13
- useSwapContext();
35
+ const {
36
+ route,
37
+ feeToken,
38
+ isFetchingRoute,
39
+ srcChain,
40
+ dstChain,
41
+ srcToken,
42
+ srcValueWei,
43
+ bridgeUI,
44
+ } = useSwapContext();
45
+
46
+ const { publicKey } = useWallet();
47
+ const {
48
+ networks: {
49
+ evm: { address: evmAddress },
50
+ },
51
+ } = useNetworks();
52
+
53
+ useEffect(() => {
54
+ const fetchSolanaBridgeFee = async () => {
55
+ if (
56
+ bridgeUI &&
57
+ srcChain?.ecosystem === Ecosystem.SOLANA &&
58
+ dstChain?.ccipChainId &&
59
+ srcToken &&
60
+ srcValueWei &&
61
+ publicKey &&
62
+ evmAddress
63
+ ) {
64
+ try {
65
+ setIsFetchingSolanaFee(true);
66
+
67
+ const connection = new Connection(
68
+ SOLANA_MAINNET_RPC_URL,
69
+ "confirmed",
70
+ );
71
+
72
+ const provider: CCIPProvider = {
73
+ connection,
74
+ wallet: null,
75
+ getAddress() {
76
+ return publicKey;
77
+ },
78
+ async signTransaction(tx) {
79
+ return tx;
80
+ },
81
+ };
82
+
83
+ const config: CCIPCoreConfig = {
84
+ ccipRouterProgramId: CCIP_ROUTER_PROGRAM_ID,
85
+ feeQuoterProgramId: FEE_QUOTER_PROGRAM_ID,
86
+ tokenMint: new PublicKey(srcToken.address),
87
+ rmnRemoteProgramId: RMN_REMOTE_PROGRAM_ID,
88
+ linkTokenMint: LINK_TOKEN_MINT,
89
+ nativeSol: PublicKey.default,
90
+ systemProgramId: SYSTEM_PROGRAM_ID,
91
+ programId: PROGRAM_ID,
92
+ };
93
+
94
+ const context: CCIPContext = {
95
+ provider,
96
+ config,
97
+ };
98
+
99
+ const client = new CCIPClient(context);
100
+
101
+ const extraArgs = client.createExtraArgs({
102
+ allowOutOfOrderExecution: true,
103
+ });
104
+
105
+ const receiver =
106
+ AddressConversion.evmAddressToSolanaBytes(evmAddress);
107
+
108
+ const feeRequest = {
109
+ destChainSelector: new BN(dstChain.ccipChainId),
110
+ message: {
111
+ receiver: receiver,
112
+ data: new Uint8Array(0),
113
+ tokenAmounts: [
114
+ {
115
+ token: new PublicKey(srcToken.address),
116
+ amount: new BN(srcValueWei.toString()),
117
+ },
118
+ ],
119
+ feeToken: PublicKey.default,
120
+ extraArgs: extraArgs,
121
+ },
122
+ };
123
+
124
+ const feeResult = await client.getFee(feeRequest);
125
+ const feeInLamports = feeResult.amount.toString();
126
+ setSolanaBridgeFee(feeInLamports);
127
+
128
+ const feeInSol = weiToHumanReadable({
129
+ amount: feeInLamports,
130
+ decimals: 9,
131
+ precisionFractionalPlaces: 5,
132
+ });
133
+ setSolanaBridgeFee(feeInSol);
134
+ } catch (error) {
135
+ setSolanaBridgeFee(null);
136
+ } finally {
137
+ setIsFetchingSolanaFee(false);
138
+ }
139
+ } else {
140
+ setSolanaBridgeFee(null);
141
+ }
142
+ };
143
+
144
+ fetchSolanaBridgeFee();
145
+ }, [
146
+ bridgeUI,
147
+ srcChain?.ecosystem,
148
+ dstChain?.ccipChainId,
149
+ srcToken?.address,
150
+ srcValueWei,
151
+ publicKey,
152
+ evmAddress,
153
+ srcToken,
154
+ ]);
14
155
 
15
156
  const totalFee = useMemo(() => {
16
157
  if (!route) {
@@ -28,6 +169,12 @@ export const FeesPanel = () => {
28
169
  });
29
170
  }, [feeToken?.decimals, route]);
30
171
 
172
+ const isLoading =
173
+ isFetchingRoute ||
174
+ (srcChain?.ecosystem === Ecosystem.SOLANA &&
175
+ bridgeUI &&
176
+ isFetchingSolanaFee);
177
+
31
178
  return (
32
179
  <div className="flex flex-col x-border rounded-xl py-2 px-4 bg-t_bg_tertiary bg-opacity-5">
33
180
  <button
@@ -39,16 +186,18 @@ export const FeesPanel = () => {
39
186
  <div className="flex gap-2 items-center text-xs font-medium sm:text-sm fill-t_text_primary">
40
187
  <TokensIcon />
41
188
  <p className="opacity-60">Fee: </p>
42
- {isFetchingRoute ? (
189
+ {isLoading ? (
43
190
  <Skeleton className="w-[100px] h-full" />
44
191
  ) : (
45
- <p>{`${totalFee} ${feeToken?.symbol || ""}`}</p>
192
+ <p>{`${solanaBridgeFee ? solanaBridgeFee : totalFee} ${
193
+ feeToken?.symbol || ""
194
+ }`}</p>
46
195
  )}
47
196
  </div>
48
197
  <div className="flex gap-2 items-center fill-t_text_primary">
49
- {route &&
50
- !isFetchingRoute &&
51
- srcChain?.chainId !== dstChain?.chainId && <DeliveryInfo />}
198
+ {route && !isLoading && srcChain?.chainId !== dstChain?.chainId && (
199
+ <DeliveryInfo />
200
+ )}
52
201
  {expanded ? (
53
202
  <div className="w-4 h-2 rotate-180">
54
203
  <ArrowDownIcon />
@@ -60,7 +209,7 @@ export const FeesPanel = () => {
60
209
  )}
61
210
  </div>
62
211
  </div>
63
- {expanded && <Fees />}
212
+ {expanded && <Fees solanaBridgeFee={solanaBridgeFee} />}
64
213
  </button>
65
214
  </div>
66
215
  );