@xswap-link/sdk 0.10.6 → 0.10.8

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 (47) hide show
  1. package/.github/workflows/main.yml +1 -1
  2. package/.github/workflows/publish.yml +10 -10
  3. package/CHANGELOG.md +13 -0
  4. package/dist/index.d.mts +39 -8
  5. package/dist/index.d.ts +39 -8
  6. package/dist/index.global.js +827 -440
  7. package/dist/index.js +9 -9
  8. package/dist/index.mjs +9 -9
  9. package/package.json +12 -1
  10. package/src/components/AllWalletsConfig/index.tsx +40 -0
  11. package/src/components/Swap/HistoryView/index.tsx +28 -5
  12. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +73 -47
  13. package/src/components/Swap/SwapView/SwapButton/index.tsx +45 -10
  14. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +123 -4
  15. package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +71 -0
  16. package/src/components/Swap/SwapView/SwapPanel/index.tsx +4 -0
  17. package/src/components/Swap/SwapView/WalletPicker/index.tsx +63 -28
  18. package/src/components/Swap/index.tsx +2 -2
  19. package/src/components/TxConfigForm/index.tsx +9 -34
  20. package/src/components/TxDataCard/TxDataCardUI/index.tsx +2 -2
  21. package/src/components/TxDataCard/index.tsx +7 -2
  22. package/src/components/index.ts +1 -0
  23. package/src/config/wagmiConfig.ts +1 -6
  24. package/src/constants/index.ts +14 -0
  25. package/src/context/HistoryProvider.tsx +121 -25
  26. package/src/context/SwapProvider.tsx +104 -48
  27. package/src/context/TransactionProvider.tsx +60 -1
  28. package/src/context/TxUIWrapper.tsx +187 -71
  29. package/src/context/WalletProvider.tsx +108 -0
  30. package/src/contracts/addresses.ts +4 -0
  31. package/src/contracts/idl/jupiter.ts +2879 -0
  32. package/src/models/Ecosystem.ts +1 -0
  33. package/src/models/Route.ts +14 -3
  34. package/src/models/SolanaTransaction.ts +38 -0
  35. package/src/models/SolanaWeb3Network.ts +5 -0
  36. package/src/models/TxUIWrapper.ts +8 -4
  37. package/src/models/index.ts +1 -0
  38. package/src/models/payloads/GetBalancesPayload.ts +2 -1
  39. package/src/models/payloads/GetSolanaRoutePayload.ts +8 -0
  40. package/src/models/payloads/index.ts +1 -0
  41. package/src/services/api.ts +21 -3
  42. package/src/services/solana/jupiter/extract-swap-data.ts +182 -0
  43. package/src/services/solana/jupiter/get-events.ts +37 -0
  44. package/src/services/solana/jupiter/instruction-parser.ts +217 -0
  45. package/src/services/solana/jupiter/utils.ts +37 -0
  46. package/src/utils/strings.ts +16 -0
  47. package/test/context/SwapProvider.test.tsx +6 -9
@@ -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>(
@@ -28,9 +28,13 @@ import {
28
28
  useRef,
29
29
  useState,
30
30
  } from "react";
31
- import { useAccount } from "wagmi";
32
31
  import { useSwapContext } from "./SwapProvider";
33
- import { EvmHandlers, useTransaction } from "./TransactionProvider";
32
+ import {
33
+ EvmHandlers,
34
+ SolanaHandlers,
35
+ useTransaction,
36
+ } from "./TransactionProvider";
37
+ import { useWallet } from "./WalletProvider";
34
38
 
35
39
  const TxUIWrapperContext: Context<TxUIWrapperState> =
36
40
  createContext<TxUIWrapperState>({} as TxUIWrapperState);
@@ -39,7 +43,9 @@ type Props = {
39
43
  children: ReactNode;
40
44
  };
41
45
  export const TxUIWrapper = ({ children }: Props) => {
42
- const { chainId } = useAccount();
46
+ const { evm, solana } = useWallet();
47
+ const { chainId } = evm;
48
+ const { chainId: solanaChainId } = solana;
43
49
  const { enqueueTransaction: nativeEnqueueTransaction } = useTransaction();
44
50
  const { enqueueSnackbar, closeSnackbar } = useSnackbar();
45
51
  const [currentNetwork, setCurrentNetwork] = useState(Ecosystem.EVM);
@@ -175,59 +181,159 @@ export const TxUIWrapper = ({ children }: Props) => {
175
181
 
176
182
  newHandlers.onSuccess = (result) => {
177
183
  closeSnackbar(result?.transactionHash);
184
+
185
+ if (txKey.current !== currentKey) {
186
+ return;
187
+ }
188
+
189
+ setTxReceipt(result);
190
+ setTxError("");
191
+ setLoading(false);
192
+
193
+ if (handlers?.onSuccess) {
194
+ handlers.onSuccess(result);
195
+ }
196
+
197
+ if (isTxModalOpenRef.current || !showDefaultSuccessMessage) {
198
+ return;
199
+ }
200
+
201
+ const snackbarKey = `${txHash}-success`;
202
+ enqueueSnackbar(
203
+ <SnackMessage
204
+ message={{
205
+ text: `Transaction mined successfully!`,
206
+ variant: "success",
207
+ action: () => (
208
+ <div className="flex items-center flex-nowrap">
209
+ <Button
210
+ type="button"
211
+ onClick={() => {
212
+ window.open(
213
+ getTxExplorerUrl(
214
+ txHash,
215
+ supportedChains.find(
216
+ (chain) =>
217
+ chain.chainId === chainId?.toString() &&
218
+ chain.ecosystem === currentNetwork,
219
+ )?.transactionExplorer || "",
220
+ result,
221
+ ),
222
+ );
223
+ }}
224
+ >
225
+ View
226
+ </Button>
227
+ <div
228
+ className="fill-t_text_primary w-[14px] h-[14px] cursor-pointer p-[14px] leading-[0px]"
229
+ onClick={() => closeSnackbar(snackbarKey)}
230
+ >
231
+ <CloseIcon />
232
+ </div>
233
+ </div>
234
+ ),
235
+ }}
236
+ />,
237
+ { persist: false, key: snackbarKey },
238
+ );
239
+ };
240
+
241
+ newHandlers.onError = (err) => {
178
242
  if (txKey.current === currentKey) {
179
- setTxReceipt(result);
180
- if (!isTxModalOpenRef.current) {
181
- if (showDefaultSuccessMessage) {
182
- const snackbarKey = `${txHash}-success`;
183
- enqueueSnackbar(
184
- <SnackMessage
185
- message={{
186
- text: `Transaction mined successfully!`,
187
- variant: "success",
188
- action: () => (
189
- <div className="flex items-center flex-nowrap">
190
- <Button
191
- type="button"
192
- onClick={() => {
193
- window.open(
194
- getTxExplorerUrl(
195
- txHash,
196
- supportedChains.find(
197
- (chain) =>
198
- chain.chainId === chainId?.toString() &&
199
- chain.ecosystem === currentNetwork,
200
- )?.transactionExplorer || "",
201
- result,
202
- ),
203
- );
204
- }}
205
- >
206
- View
207
- </Button>
208
-
209
- <div
210
- className="fill-t_text_primary w-[14px] h-[14px] cursor-pointer p-[14px] leading-[0px]"
211
- onClick={() => closeSnackbar(snackbarKey)}
212
- >
213
- <CloseIcon />
214
- </div>
215
- </div>
216
- ),
217
- }}
218
- />,
219
- { persist: false, key: snackbarKey },
220
- );
221
- }
222
- }
223
- if (handlers?.onSuccess) {
224
- handlers?.onSuccess(result);
225
- }
226
- setTxError("");
243
+ setTxError(parseWeb3Error(err));
244
+ handlers?.onError?.(err);
227
245
  setLoading(false);
228
246
  }
229
247
  };
230
248
 
249
+ return newHandlers;
250
+ },
251
+ [
252
+ closeSnackbar,
253
+ txHash,
254
+ enqueueSnackbar,
255
+ getTxExplorerUrl,
256
+ supportedChains,
257
+ chainId,
258
+ currentNetwork,
259
+ ],
260
+ );
261
+
262
+ const addUILogicToHandlersSolana = useCallback(
263
+ (
264
+ currentKey: number,
265
+ handlers?: SolanaHandlers,
266
+ showDefaultSuccessMessage = true,
267
+ ): SolanaHandlers => {
268
+ const newHandlers: SolanaHandlers = { ...handlers };
269
+
270
+ newHandlers.onConfirm = (signedTransaction) => {
271
+ if (txKey.current === currentKey) {
272
+ if (handlers?.onConfirm) {
273
+ handlers?.onConfirm(signedTransaction);
274
+ }
275
+ setTxStatus((prevStatus) => prevStatus + 1);
276
+ }
277
+ };
278
+
279
+ newHandlers.onSuccess = (transactionHash) => {
280
+ closeSnackbar(transactionHash);
281
+
282
+ if (txKey.current !== currentKey) {
283
+ return;
284
+ }
285
+
286
+ if (handlers?.onSuccess) {
287
+ handlers?.onSuccess(transactionHash);
288
+ }
289
+ setTxHash(transactionHash || "");
290
+ setTxError("");
291
+ setLoading(false);
292
+
293
+ if (isTxModalOpenRef.current || !showDefaultSuccessMessage) {
294
+ return;
295
+ }
296
+
297
+ const snackbarKey = `${txHash}-success`;
298
+ enqueueSnackbar(
299
+ <SnackMessage
300
+ message={{
301
+ text: `Transaction mined successfully!`,
302
+ variant: "success",
303
+ action: () => (
304
+ <div className="flex items-center flex-nowrap">
305
+ <Button
306
+ type="button"
307
+ onClick={() => {
308
+ window.open(
309
+ getTxExplorerUrl(
310
+ txHash,
311
+ supportedChains.find(
312
+ (chain) =>
313
+ chain.chainId === solanaChainId?.toString() &&
314
+ chain.ecosystem === currentNetwork,
315
+ )?.transactionExplorer || "",
316
+ ),
317
+ );
318
+ }}
319
+ >
320
+ View
321
+ </Button>
322
+
323
+ <div
324
+ className="fill-t_text_primary w-[14px] h-[14px] cursor-pointer p-[14px] leading-[0px]"
325
+ onClick={() => closeSnackbar(snackbarKey)}
326
+ >
327
+ <CloseIcon />
328
+ </div>
329
+ </div>
330
+ ),
331
+ }}
332
+ />,
333
+ { persist: false, key: snackbarKey },
334
+ );
335
+ };
336
+
231
337
  newHandlers.onError = (err) => {
232
338
  if (txKey.current === currentKey) {
233
339
  setTxError(parseWeb3Error(err));
@@ -244,7 +350,7 @@ export const TxUIWrapper = ({ children }: Props) => {
244
350
  enqueueSnackbar,
245
351
  getTxExplorerUrl,
246
352
  supportedChains,
247
- chainId,
353
+ solanaChainId,
248
354
  currentNetwork,
249
355
  ],
250
356
  );
@@ -267,6 +373,12 @@ export const TxUIWrapper = ({ children }: Props) => {
267
373
  handlers as EvmHandlers,
268
374
  showDefaultSuccessMessage,
269
375
  );
376
+ } else if (network === Ecosystem.SOLANA) {
377
+ newHandlers = addUILogicToHandlersSolana(
378
+ currentKey,
379
+ handlers as SolanaHandlers,
380
+ showDefaultSuccessMessage,
381
+ );
270
382
  }
271
383
 
272
384
  // extracted tx logic
@@ -290,29 +402,33 @@ export const TxUIWrapper = ({ children }: Props) => {
290
402
 
291
403
  return currentKey;
292
404
  },
293
- [addUILogicToHandlersEVM, nativeEnqueueTransaction],
405
+ [
406
+ addUILogicToHandlersEVM,
407
+ addUILogicToHandlersSolana,
408
+ nativeEnqueueTransaction,
409
+ ],
294
410
  );
295
411
 
296
- const txExplorerUrl = useMemo(
297
- () =>
298
- getTxExplorerUrl(
299
- txHash,
300
- supportedChains.find(
301
- (chain) =>
302
- chain.chainId === chainId?.toString() &&
303
- chain.ecosystem === currentNetwork,
304
- )?.transactionExplorer || "",
305
- txReceipt,
306
- ),
307
- [
308
- chainId,
309
- currentNetwork,
310
- getTxExplorerUrl,
311
- supportedChains,
412
+ const txExplorerUrl = useMemo(() => {
413
+ return getTxExplorerUrl(
312
414
  txHash,
415
+ supportedChains.find(
416
+ (chain) =>
417
+ (chain.chainId === chainId?.toString() ||
418
+ chain.chainId === solanaChainId?.toString()) &&
419
+ chain.ecosystem === currentNetwork,
420
+ )?.transactionExplorer || "",
313
421
  txReceipt,
314
- ],
315
- );
422
+ );
423
+ }, [
424
+ chainId,
425
+ solanaChainId,
426
+ currentNetwork,
427
+ getTxExplorerUrl,
428
+ supportedChains,
429
+ txHash,
430
+ txReceipt,
431
+ ]);
316
432
 
317
433
  const setModalOpen = useCallback((flag: boolean) => {
318
434
  setTxModalOpen(flag);