@xswap-link/sdk 0.10.5 → 0.10.7

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.
Files changed (54) hide show
  1. package/.github/workflows/main.yml +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/dist/index.d.mts +46 -12
  4. package/dist/index.d.ts +46 -12
  5. package/dist/index.global.js +5290 -261
  6. package/dist/index.js +9 -9
  7. package/dist/index.mjs +9 -9
  8. package/package.json +13 -2
  9. package/src/assets/icons/StarsIcon.tsx +18 -0
  10. package/src/assets/icons/index.ts +1 -0
  11. package/src/components/AllWalletsConfig/index.tsx +40 -0
  12. package/src/components/Swap/HistoryView/index.tsx +28 -5
  13. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +73 -47
  14. package/src/components/Swap/SwapView/SwapButton/index.tsx +45 -10
  15. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +20 -2
  16. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +124 -4
  17. package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +71 -0
  18. package/src/components/Swap/SwapView/SwapPanel/index.tsx +4 -0
  19. package/src/components/Swap/SwapView/WalletPicker/index.tsx +63 -28
  20. package/src/components/Swap/index.tsx +2 -2
  21. package/src/components/TxConfigForm/index.tsx +12 -34
  22. package/src/components/TxDataCard/TxDataCardUI/index.tsx +16 -10
  23. package/src/components/TxDataCard/index.tsx +1 -0
  24. package/src/components/TxWidgetWC/index.tsx +4 -0
  25. package/src/components/TxWidgetWCWrapped/index.tsx +2 -0
  26. package/src/components/index.ts +1 -0
  27. package/src/config/wagmiConfig.ts +1 -6
  28. package/src/constants/index.ts +14 -0
  29. package/src/context/HistoryProvider.tsx +121 -25
  30. package/src/context/SwapProvider.tsx +104 -48
  31. package/src/context/TransactionProvider.tsx +60 -1
  32. package/src/context/TxUIWrapper.tsx +187 -71
  33. package/src/context/WalletProvider.tsx +108 -0
  34. package/src/contracts/addresses.ts +4 -0
  35. package/src/contracts/idl/jupiter.ts +2879 -0
  36. package/src/models/Ecosystem.ts +1 -0
  37. package/src/models/Route.ts +14 -3
  38. package/src/models/SolanaTransaction.ts +38 -0
  39. package/src/models/SolanaWeb3Network.ts +5 -0
  40. package/src/models/TxUIWrapper.ts +8 -4
  41. package/src/models/index.ts +1 -0
  42. package/src/models/payloads/GetBalancesPayload.ts +2 -1
  43. package/src/models/payloads/GetSolanaRoutePayload.ts +8 -0
  44. package/src/models/payloads/ModalIntegrationPayload.ts +1 -0
  45. package/src/models/payloads/WidgetIntegrationPayload.ts +1 -0
  46. package/src/models/payloads/index.ts +1 -0
  47. package/src/services/api.ts +21 -3
  48. package/src/services/integrations/transactions.ts +2 -0
  49. package/src/services/solana/jupiter/extract-swap-data.ts +183 -0
  50. package/src/services/solana/jupiter/get-events.ts +37 -0
  51. package/src/services/solana/jupiter/instruction-parser.ts +217 -0
  52. package/src/services/solana/jupiter/utils.ts +37 -0
  53. package/src/utils/strings.ts +16 -0
  54. package/test/context/SwapProvider.test.tsx +6 -9
@@ -1,13 +1,19 @@
1
+ import { Connection, PublicKey } from "@solana/web3.js";
1
2
  import { CloseIcon } from "@src/assets/icons";
2
3
  import { Button, SnackMessage } from "@src/components";
3
4
  import {
4
5
  CCIP_EXPLORER,
5
6
  FALLBACK_CROSSCHAIN_ESTIMATION_TIME,
7
+ SOLANA_MAINNET_RPC_URL,
6
8
  crosschainEstimationTimes,
7
9
  } from "@src/constants";
10
+ import { useWallet } from "@src/context/WalletProvider";
8
11
  import { useDebounce } from "@src/hooks";
9
12
  import { Transaction, TransactionStatus } from "@src/models";
13
+ import { TransactionWithMeta } from "@src/models/SolanaTransaction";
10
14
  import { getHistory } from "@src/services";
15
+ import { extractSolanaSwapData } from "@src/services/solana/jupiter/extract-swap-data";
16
+ import { isJupiterTransaction } from "@src/services/solana/jupiter/utils";
11
17
  import { isServer } from "@src/utils";
12
18
  import BigNumberJS from "bignumber.js";
13
19
  import { useSnackbar } from "notistack";
@@ -21,7 +27,6 @@ import {
21
27
  useMemo,
22
28
  useState,
23
29
  } from "react";
24
- import { useAccount } from "wagmi";
25
30
 
26
31
  export interface HistoryState {
27
32
  history: Transaction[];
@@ -34,6 +39,8 @@ export interface HistoryState {
34
39
  userAddress: string,
35
40
  ) => void;
36
41
  historyLoadedOnce: boolean;
42
+ solanaHistory: Transaction[];
43
+ solanaHistoryLoadedOnce: boolean;
37
44
  }
38
45
 
39
46
  const HistoryContext: Context<HistoryState> = createContext<HistoryState>(
@@ -63,11 +70,48 @@ const setLocalHistory = (data: LocalHistory) => {
63
70
  }
64
71
  };
65
72
 
73
+ async function processSolanaTransactions(
74
+ transactions: (TransactionWithMeta | null)[],
75
+ ): Promise<Transaction[]> {
76
+ const results: Transaction[] = [];
77
+
78
+ for (let i = 0; i < transactions.length; i++) {
79
+ try {
80
+ const tx = transactions[i];
81
+ if (!tx) continue;
82
+
83
+ const signature = tx.transaction.signatures[0];
84
+
85
+ if (!signature) continue;
86
+
87
+ const connection = new Connection(SOLANA_MAINNET_RPC_URL, "confirmed");
88
+
89
+ const swapDetails = await extractSolanaSwapData(
90
+ signature,
91
+ connection,
92
+ tx,
93
+ );
94
+
95
+ if (swapDetails) {
96
+ results.push(swapDetails);
97
+ }
98
+ } catch (error) {
99
+ console.error(`Error processing transaction:`, error);
100
+ }
101
+ }
102
+
103
+ return results;
104
+ }
105
+
66
106
  export const HistoryProvider = ({ children }: HistoryProviderProps) => {
67
- const { address } = useAccount();
107
+ const { evm, solana } = useWallet();
108
+ const { address: address } = evm;
109
+ const { address: solanaAddress } = solana;
68
110
 
111
+ const [solanaHistory, setSolanaHistory] = useState<Transaction[]>([]);
69
112
  const [history, setHistory] = useState<Transaction[]>([]);
70
113
  const [historyLoadedOnce, setHistoryLoadedOnce] = useState(false);
114
+ const [solanaHistoryLoadedOnce, setSolanaHistoryLoadedOnce] = useState(false);
71
115
 
72
116
  const { enqueueSnackbar, closeSnackbar } = useSnackbar();
73
117
  const { debounce: getHistoryDebounce } = useDebounce({
@@ -82,16 +126,70 @@ export const HistoryProvider = ({ children }: HistoryProviderProps) => {
82
126
  }
83
127
  }, []);
84
128
 
129
+ const fetchLastSignatures = async (senderAddress: string, limit = 50) => {
130
+ const connection = new Connection(SOLANA_MAINNET_RPC_URL, "confirmed");
131
+ const userPubkey = new PublicKey(senderAddress);
132
+
133
+ try {
134
+ const signatures = await connection.getSignaturesForAddress(userPubkey, {
135
+ limit,
136
+ });
137
+
138
+ const transactions = await connection.getParsedTransactions(
139
+ signatures.map((s) => s.signature),
140
+ {
141
+ maxSupportedTransactionVersion: 0,
142
+ },
143
+ );
144
+
145
+ const jupiterTransactions = transactions
146
+ .filter((val) => !!val)
147
+ .filter((tx) => isJupiterTransaction(tx));
148
+
149
+ const swapDetails = await processSolanaTransactions(
150
+ jupiterTransactions as TransactionWithMeta[],
151
+ );
152
+ return swapDetails;
153
+ } catch (error) {
154
+ console.error("Error fetching transactions:", error);
155
+ return [];
156
+ }
157
+ };
158
+
159
+ const fetchSolanaHistory = async () => {
160
+ if (!solanaAddress) {
161
+ setSolanaHistoryLoadedOnce(true);
162
+ return;
163
+ }
164
+
165
+ try {
166
+ const lastSignatures = await fetchLastSignatures(solanaAddress);
167
+ setSolanaHistory(lastSignatures);
168
+ } finally {
169
+ setSolanaHistoryLoadedOnce(true);
170
+ }
171
+ };
172
+
173
+ useEffect(() => {
174
+ if (solanaAddress) {
175
+ fetchSolanaHistory();
176
+ } else {
177
+ setSolanaHistory([]);
178
+ setSolanaHistoryLoadedOnce(true);
179
+ }
180
+ // eslint-disable-next-line react-hooks/exhaustive-deps
181
+ }, [solanaAddress]);
182
+
85
183
  const fetchHistory = useCallback(async () => {
86
184
  try {
87
185
  if (!address) {
88
186
  return;
89
187
  }
90
188
 
189
+ const fetchedTransactions: Transaction[] = [];
91
190
  const fetchedHistory = await getHistory({
92
191
  walletAddress: address,
93
192
  });
94
- const fetchedTransactions: Transaction[] = [];
95
193
 
96
194
  for (const entry of fetchedHistory.history) {
97
195
  if (!entry.source) {
@@ -158,15 +256,16 @@ export const HistoryProvider = ({ children }: HistoryProviderProps) => {
158
256
  for (const [walletAddress, transactions] of Object.entries(
159
257
  localHistory,
160
258
  )) {
161
- if (
162
- walletAddress.toLowerCase().toLowerCase() !== address.toLowerCase()
163
- ) {
259
+ if (walletAddress.toLowerCase() !== address.toLowerCase()) {
164
260
  continue;
165
261
  }
166
262
  const newLocalHistoryForWallet: Transaction[] = [];
167
263
 
168
264
  for (const transaction of transactions) {
169
- if (!txHashToTxFromAPI[transaction.hash]) {
265
+ const txHash = transaction.hash;
266
+ const apiTransaction = txHashToTxFromAPI[txHash];
267
+
268
+ if (!apiTransaction) {
170
269
  // localStorage has fresher data.
171
270
  fetchedTransactions.push(transaction);
172
271
  newLocalHistoryForWallet.push(transaction);
@@ -174,20 +273,17 @@ export const HistoryProvider = ({ children }: HistoryProviderProps) => {
174
273
  }
175
274
 
176
275
  if (
177
- txHashToTxFromAPI[transaction.hash] &&
276
+ apiTransaction &&
178
277
  transaction.status === "DONE" &&
179
- txHashToTxFromAPI[transaction.hash]?.status === "IN_PROGRESS"
278
+ apiTransaction?.status === "IN_PROGRESS"
180
279
  ) {
181
280
  // If we know on the frontend that the tx is done we should update backend result
182
- // @ts-ignore
183
- txHashToTxFromAPI[transaction.hash].status = "DONE";
281
+ apiTransaction.status = "DONE";
184
282
  }
185
283
 
186
284
  if (
187
- // @ts-ignore
188
- txHashToTxFromAPI[transaction.hash].status === "DONE" ||
189
- // @ts-ignore
190
- txHashToTxFromAPI[transaction.hash].status === "REVERTED"
285
+ apiTransaction.status === "DONE" ||
286
+ apiTransaction.status === "REVERTED"
191
287
  ) {
192
288
  // API has the same or newer data.
193
289
  continue;
@@ -212,7 +308,7 @@ export const HistoryProvider = ({ children }: HistoryProviderProps) => {
212
308
  } finally {
213
309
  setHistoryLoadedOnce(true);
214
310
  }
215
- }, [address, getHistory, enqueueSnackbar]);
311
+ }, [address, enqueueSnackbar]);
216
312
 
217
313
  useEffect(() => {
218
314
  if (!historyLoadedOnce) {
@@ -222,13 +318,7 @@ export const HistoryProvider = ({ children }: HistoryProviderProps) => {
222
318
  getHistoryDebounce(fetchHistory);
223
319
  }, 60000);
224
320
  return () => clearInterval(timer);
225
- }, [
226
- address,
227
- fetchHistory,
228
- getHistory,
229
- getHistoryDebounce,
230
- historyLoadedOnce,
231
- ]);
321
+ }, [address, fetchHistory, getHistoryDebounce, historyLoadedOnce]);
232
322
 
233
323
  const addTransactionToLocalHistory = useCallback(
234
324
  (transaction: Transaction, userAddress: string) => {
@@ -281,7 +371,7 @@ export const HistoryProvider = ({ children }: HistoryProviderProps) => {
281
371
  { persist: false, key: messageId },
282
372
  );
283
373
  },
284
- [enqueueSnackbar],
374
+ [closeSnackbar, enqueueSnackbar],
285
375
  );
286
376
 
287
377
  const updateTransactionInLocalHistory = useCallback(
@@ -319,8 +409,10 @@ export const HistoryProvider = ({ children }: HistoryProviderProps) => {
319
409
 
320
410
  useEffect(() => {
321
411
  setHistoryLoadedOnce(false);
412
+ setSolanaHistoryLoadedOnce(false);
322
413
  setHistory([]);
323
- }, [address]);
414
+ setSolanaHistory([]);
415
+ }, [address, solanaAddress]);
324
416
 
325
417
  const state = useMemo<HistoryState>(
326
418
  () => ({
@@ -328,12 +420,16 @@ export const HistoryProvider = ({ children }: HistoryProviderProps) => {
328
420
  addTransactionToLocalHistory,
329
421
  updateTransactionInLocalHistory,
330
422
  historyLoadedOnce,
423
+ solanaHistory,
424
+ solanaHistoryLoadedOnce,
331
425
  }),
332
426
  [
333
427
  history,
334
428
  addTransactionToLocalHistory,
335
429
  updateTransactionInLocalHistory,
336
430
  historyLoadedOnce,
431
+ solanaHistory,
432
+ solanaHistoryLoadedOnce,
337
433
  ],
338
434
  );
339
435
 
@@ -3,16 +3,21 @@ import {
3
3
  DEFAULT_DESTINATION_CHAIN_ID,
4
4
  DEFAULT_REQUEST_ABORT_MSG,
5
5
  DEFAULT_SOURCE_CHAIN_ID,
6
+ SOLANA_NATIVE_TOKEN_ADDRESS,
7
+ SOLANA_TOKEN_PROGRAM_ID,
6
8
  } from "@src/constants";
7
9
  import { ADDRESSES } from "@src/contracts";
8
10
  import {
9
11
  BridgeToken,
10
12
  BridgeTokensDictionary,
11
13
  Chain,
12
- Route,
14
+ Ecosystem,
15
+ EVMRoute,
16
+ SolanaRoute,
13
17
  Token,
14
18
  TokenOption,
15
19
  TokenPrices,
20
+ UnifiedRoute,
16
21
  Web3Environment,
17
22
  } from "@src/models";
18
23
  import {
@@ -20,6 +25,7 @@ import {
20
25
  getBridgeTokens,
21
26
  getPrices,
22
27
  getRoute,
28
+ getSolanaRoute,
23
29
  } from "@src/services";
24
30
  import {
25
31
  deepMergeObjects,
@@ -45,8 +51,9 @@ import {
45
51
  useRef,
46
52
  useState,
47
53
  } from "react";
48
- import { Config, useAccount } from "wagmi";
49
54
  import { mapToValidPath } from "@src/utils/validation";
55
+ import { useWallet } from "./WalletProvider";
56
+ import { Connection, PublicKey } from "@solana/web3.js";
50
57
 
51
58
  type Tab = "Swap" | "History" | "Settings";
52
59
 
@@ -57,7 +64,6 @@ type ChainOption = Chain & {
57
64
  type SwapContext = {
58
65
  bridgeUI: boolean;
59
66
  integrationConfig: Partial<TxConfigFormPayload>;
60
- wagmiConfig: Config;
61
67
  supportedChains: Chain[];
62
68
  tab: Tab;
63
69
  setTab: Dispatch<SetStateAction<Tab>>;
@@ -98,8 +104,8 @@ type SwapContext = {
98
104
  setInfiniteApproval: Dispatch<SetStateAction<boolean>>;
99
105
  slippage: string;
100
106
  setSlippage: Dispatch<SetStateAction<string>>;
101
- route: Route | undefined;
102
- setRoute: Dispatch<SetStateAction<Route | undefined>>;
107
+ route: UnifiedRoute | undefined;
108
+ setRoute: Dispatch<SetStateAction<UnifiedRoute | undefined>>;
103
109
  isAsyncDataLoaded: boolean;
104
110
  srcTokenBalanceWei: string;
105
111
  setSrcTokenBalanceWei: Dispatch<SetStateAction<string>>;
@@ -130,7 +136,6 @@ const functionNotImplemented = () => {
130
136
  const init: SwapContext = {
131
137
  bridgeUI: false,
132
138
  integrationConfig: {},
133
- wagmiConfig: {} as Config,
134
139
  supportedChains: [],
135
140
  tab: "Swap",
136
141
  overlay: false,
@@ -196,21 +201,20 @@ type Props = {
196
201
  children: ReactNode;
197
202
  integrationConfig: Partial<TxConfigFormPayload>;
198
203
  supportedChains: Chain[];
199
- wagmiConfig: Config;
200
204
  overlay: boolean;
201
205
  };
202
206
  export const SwapProvider = ({
203
207
  children,
204
208
  integrationConfig,
205
209
  supportedChains,
206
- wagmiConfig,
207
210
  overlay,
208
211
  }: Props) => {
209
212
  // =============================================================================
210
213
  // Hooks
211
214
  // =============================================================================
212
- const { address, chainId: walletChainId } = useAccount();
213
-
215
+ const { evm, solana } = useWallet();
216
+ const { address: evmAddress, chainId: walletChainId } = evm;
217
+ const { address: solanaAddress } = solana;
214
218
  // =============================================================================
215
219
  // State
216
220
  // =============================================================================
@@ -472,7 +476,7 @@ export const SwapProvider = ({
472
476
  );
473
477
  // useMemo: computes a formatted balance string for the source token based on its balance in Wei.
474
478
  const srcTokenBalanceInfo = useMemo(() => {
475
- if (!srcToken || !srcTokenBalanceWei || !address) {
479
+ if (!srcToken || !srcTokenBalanceWei || (!evmAddress && !solanaAddress)) {
476
480
  return "";
477
481
  }
478
482
 
@@ -496,7 +500,7 @@ export const SwapProvider = ({
496
500
  decimals: srcToken.decimals,
497
501
  precisionFractionalPlaces: 5,
498
502
  });
499
- }, [srcToken, srcTokenBalanceWei]);
503
+ }, [evmAddress, solanaAddress, srcToken, srcTokenBalanceWei]);
500
504
 
501
505
  // =============================================================================
502
506
  // Callbacks
@@ -572,30 +576,54 @@ export const SwapProvider = ({
572
576
  routeAbortController.current.abort(DEFAULT_REQUEST_ABORT_MSG);
573
577
  }
574
578
  routeAbortController.current = new AbortController();
575
- const route = await getRoute(
576
- {
579
+ if (srcChain.ecosystem === Ecosystem.SOLANA) {
580
+ const solanaRouteData = await getSolanaRoute({
577
581
  integratorId: integrationConfig.integratorId!,
578
- fromChain: srcChain.chainId,
579
- toChain: dstChain.chainId,
580
- fromAddress:
581
- address || ADDRESSES[srcChain.chainId]?.FeeCollector || "",
582
- toAddress: address || ADDRESSES[dstChain.chainId]?.FeeCollector || "",
583
582
  fromToken: srcToken.address,
584
583
  toToken: dstToken.address,
585
584
  fromAmount: srcValueWei,
585
+ fromAddress:
586
+ solanaAddress || ADDRESSES[srcChain.chainId]?.FeeCollector || "",
586
587
  slippage: Number(slippage),
587
- customContractCalls: integrationConfig.customContractCalls,
588
- paymentToken: constants.AddressZero,
589
- expressDelivery: expressDelivery,
590
- infiniteApproval: infiniteApproval,
591
- integratorFee: integrationConfig.integratorFee,
592
- integratorFeeReceiverAddress:
593
- integrationConfig.integratorFeeReceiverAddress,
594
- },
595
- routeAbortController.current.signal,
596
- );
588
+ });
589
+
590
+ const unifiedRoute: SolanaRoute = {
591
+ ...solanaRouteData,
592
+ ecosystem: "solana",
593
+ };
594
+ setRoute(unifiedRoute);
595
+ } else {
596
+ const evmRouteData = await getRoute(
597
+ {
598
+ integratorId: integrationConfig.integratorId!,
599
+ fromChain: srcChain.chainId,
600
+ toChain: dstChain.chainId,
601
+ fromAddress:
602
+ evmAddress || ADDRESSES[srcChain.chainId]?.FeeCollector || "",
603
+ toAddress:
604
+ evmAddress || ADDRESSES[dstChain.chainId]?.FeeCollector || "",
605
+ fromToken: srcToken.address,
606
+ toToken: dstToken.address,
607
+ fromAmount: srcValueWei,
608
+ slippage: Number(slippage),
609
+ customContractCalls: integrationConfig.customContractCalls,
610
+ paymentToken: constants.AddressZero,
611
+ expressDelivery: expressDelivery,
612
+ infiniteApproval: infiniteApproval,
613
+ integratorFee: integrationConfig.integratorFee,
614
+ integratorFeeReceiverAddress:
615
+ integrationConfig.integratorFeeReceiverAddress,
616
+ },
617
+ routeAbortController.current.signal,
618
+ );
619
+
620
+ const unifiedRoute: EVMRoute = {
621
+ ...evmRouteData,
622
+ ecosystem: "evm",
623
+ };
624
+ setRoute(unifiedRoute);
625
+ }
597
626
 
598
- setRoute(route);
599
627
  routeAbortController.current = null;
600
628
  } catch (error) {
601
629
  if (error !== DEFAULT_REQUEST_ABORT_MSG) {
@@ -611,7 +639,7 @@ export const SwapProvider = ({
611
639
  }
612
640
  // eslint-disable-next-line react-hooks/exhaustive-deps
613
641
  }, [
614
- address,
642
+ evmAddress,
615
643
  bridgeUI,
616
644
  dstChain,
617
645
  dstToken,
@@ -625,7 +653,7 @@ export const SwapProvider = ({
625
653
  // refreshes and updates the balance of the source token using its RPC URL.
626
654
  const refreshBalance = useCallback(async () => {
627
655
  if (
628
- !address ||
656
+ (!evmAddress && !solanaAddress) ||
629
657
  !srcChain?.publicRpcUrls[0] ||
630
658
  !srcToken ||
631
659
  !srcToken?.address
@@ -637,26 +665,52 @@ export const SwapProvider = ({
637
665
  refreshSelectedTokenBalanceNonce.current = nonce;
638
666
 
639
667
  setIsFetchingBalance(true);
668
+ let balance = "0";
669
+ if (srcChain?.ecosystem === Ecosystem.EVM && evmAddress) {
670
+ const balanceUI = await getBalanceOf(
671
+ evmAddress,
672
+ srcToken.address,
673
+ srcChain?.publicRpcUrls[0],
674
+ );
675
+ balance = humanReadableToWei({
676
+ amount: balanceUI,
677
+ decimals: srcToken.decimals,
678
+ });
679
+ } else if (srcChain?.ecosystem === Ecosystem.SOLANA && solanaAddress) {
680
+ const solanaPublicKey = new PublicKey(solanaAddress);
681
+ const connection = new Connection(srcChain.publicRpcUrls[0]!);
682
+ if (srcToken.address === SOLANA_NATIVE_TOKEN_ADDRESS.toBase58()) {
683
+ balance = new BigNumberJS(
684
+ await connection.getBalance(solanaPublicKey),
685
+ ).toString();
686
+ } else {
687
+ const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
688
+ solanaPublicKey,
689
+ {
690
+ programId: SOLANA_TOKEN_PROGRAM_ID,
691
+ },
692
+ );
693
+ const tokenAccount = tokenAccounts.value.find(
694
+ (account) =>
695
+ account.account.data.parsed.info.mint === srcToken.address,
696
+ );
640
697
 
641
- const balance = await getBalanceOf(
642
- address,
643
- srcToken.address,
644
- srcChain?.publicRpcUrls[0],
645
- );
698
+ if (!tokenAccount) {
699
+ balance = "0";
700
+ } else {
701
+ balance = tokenAccount.account.data.parsed.info.tokenAmount.amount;
702
+ }
703
+ }
704
+ }
646
705
 
647
706
  if (nonce !== refreshSelectedTokenBalanceNonce.current) {
648
707
  return;
649
708
  }
650
709
 
651
- setSrcTokenBalanceWei(
652
- humanReadableToWei({
653
- amount: balance,
654
- decimals: srcToken.decimals,
655
- }),
656
- );
710
+ setSrcTokenBalanceWei(balance);
657
711
 
658
712
  setIsFetchingBalance(false);
659
- }, [address, srcChain, srcToken]);
713
+ }, [evmAddress, solanaAddress, srcChain, srcToken]);
660
714
  // retrieves token details for a given token address and chain from supported tokens mapping. (including imported tokens)
661
715
  const findTokenDataByAddressAndChain = useCallback(
662
716
  (tokenAddress: string, chainId: string): Token | undefined => {
@@ -724,14 +778,17 @@ export const SwapProvider = ({
724
778
  }, [refreshBalance]);
725
779
  // fetches user all token balances when a wallet address is available and supported chains are loaded.
726
780
  useEffect(() => {
727
- if (address && supportedChains.length > 0) {
781
+ if ((evmAddress || solanaAddress) && supportedChains.length > 0) {
728
782
  setIsFetchingBalances(true);
729
- getBalances({ walletAddress: address }).then((balances) => {
783
+ getBalances({
784
+ walletAddress: evmAddress,
785
+ solanaWalletAddress: solanaAddress,
786
+ }).then((balances) => {
730
787
  setTokenBalances(balances);
731
788
  setIsFetchingBalances(false);
732
789
  });
733
790
  }
734
- }, [address, supportedChains.length]);
791
+ }, [evmAddress, solanaAddress, supportedChains.length]);
735
792
  // sets the fee token from the source chain's tokens by selecting the native token (AddressZero).
736
793
  useEffect(() => {
737
794
  setFeeToken(
@@ -1152,7 +1209,6 @@ export const SwapProvider = ({
1152
1209
  value={{
1153
1210
  bridgeUI,
1154
1211
  integrationConfig,
1155
- wagmiConfig,
1156
1212
  supportedChains,
1157
1213
  tab,
1158
1214
  setTab,
@@ -1,3 +1,5 @@
1
+ import { Connection } from "@solana/web3.js";
2
+ import { SOLANA_MAINNET_RPC_URL } from "@src/constants";
1
3
  import { Ecosystem, EnqueueTxProps } from "@src/models";
2
4
  import { ContractReceipt, ContractTransaction } from "ethers";
3
5
  import {
@@ -26,6 +28,12 @@ export interface EvmHandlers {
26
28
  onConfirm?: (data?: ContractTransaction) => void;
27
29
  }
28
30
 
31
+ export interface SolanaHandlers {
32
+ onSuccess?: (data: string) => void;
33
+ onError?: <T>(data: T) => void;
34
+ onConfirm?: (txHash?: string) => void;
35
+ }
36
+
29
37
  export const TransactionProvider = ({ children }: TransactionProviderProps) => {
30
38
  const evmTransactionExecutor = useCallback(
31
39
  async ({ executeTransaction, handlers }) => {
@@ -48,6 +56,52 @@ export const TransactionProvider = ({ children }: TransactionProviderProps) => {
48
56
  },
49
57
  [],
50
58
  );
59
+ const customConnection = useMemo(
60
+ () => new Connection(SOLANA_MAINNET_RPC_URL, "confirmed"),
61
+ [],
62
+ );
63
+ const solanaTransactionExecutor = useCallback(
64
+ async ({ executeTransaction, handlers }) => {
65
+ try {
66
+ const signedTransaction = await executeTransaction();
67
+
68
+ if (!signedTransaction) {
69
+ throw new Error("Transaction is undefined");
70
+ }
71
+ handlers?.onConfirm?.(signedTransaction);
72
+
73
+ const txHash = await customConnection.sendTransaction(
74
+ signedTransaction,
75
+ {
76
+ skipPreflight: false, // Set to `true` to bypass simulation checks
77
+ preflightCommitment: "confirmed",
78
+ },
79
+ );
80
+
81
+ const latestBlockhash = await customConnection.getLatestBlockhash();
82
+
83
+ const confirmation = await customConnection.confirmTransaction(
84
+ {
85
+ signature: txHash,
86
+ blockhash: latestBlockhash.blockhash,
87
+ lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
88
+ },
89
+ "confirmed", // or "finalized" for stricter confirmation
90
+ );
91
+ if (confirmation.value.err) {
92
+ throw new Error(
93
+ `Transaction failed: ${JSON.stringify(confirmation.value.err)}`,
94
+ );
95
+ }
96
+ handlers?.onSuccess?.(txHash);
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ } catch (err: any) {
99
+ console.error(err);
100
+ handlers?.onError?.(err);
101
+ }
102
+ },
103
+ [customConnection],
104
+ );
51
105
 
52
106
  const enqueueTransaction = useCallback(
53
107
  ({
@@ -61,10 +115,15 @@ export const TransactionProvider = ({ children }: TransactionProviderProps) => {
61
115
  executeTransaction,
62
116
  handlers,
63
117
  });
118
+ } else if (network === Ecosystem.SOLANA) {
119
+ solanaTransactionExecutor({
120
+ executeTransaction,
121
+ handlers,
122
+ });
64
123
  }
65
124
  return currentKey;
66
125
  },
67
- [evmTransactionExecutor],
126
+ [evmTransactionExecutor, solanaTransactionExecutor],
68
127
  );
69
128
 
70
129
  const state = useMemo<TransactionState>(