@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.
- package/.github/workflows/main.yml +1 -1
- package/.github/workflows/publish.yml +10 -10
- package/CHANGELOG.md +13 -0
- package/dist/index.d.mts +39 -8
- package/dist/index.d.ts +39 -8
- package/dist/index.global.js +827 -440
- package/dist/index.js +9 -9
- package/dist/index.mjs +9 -9
- package/package.json +12 -1
- package/src/components/AllWalletsConfig/index.tsx +40 -0
- package/src/components/Swap/HistoryView/index.tsx +28 -5
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +73 -47
- package/src/components/Swap/SwapView/SwapButton/index.tsx +45 -10
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +123 -4
- package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +71 -0
- package/src/components/Swap/SwapView/SwapPanel/index.tsx +4 -0
- package/src/components/Swap/SwapView/WalletPicker/index.tsx +63 -28
- package/src/components/Swap/index.tsx +2 -2
- package/src/components/TxConfigForm/index.tsx +9 -34
- package/src/components/TxDataCard/TxDataCardUI/index.tsx +2 -2
- package/src/components/TxDataCard/index.tsx +7 -2
- package/src/components/index.ts +1 -0
- package/src/config/wagmiConfig.ts +1 -6
- package/src/constants/index.ts +14 -0
- package/src/context/HistoryProvider.tsx +121 -25
- package/src/context/SwapProvider.tsx +104 -48
- package/src/context/TransactionProvider.tsx +60 -1
- package/src/context/TxUIWrapper.tsx +187 -71
- package/src/context/WalletProvider.tsx +108 -0
- package/src/contracts/addresses.ts +4 -0
- package/src/contracts/idl/jupiter.ts +2879 -0
- package/src/models/Ecosystem.ts +1 -0
- package/src/models/Route.ts +14 -3
- package/src/models/SolanaTransaction.ts +38 -0
- package/src/models/SolanaWeb3Network.ts +5 -0
- package/src/models/TxUIWrapper.ts +8 -4
- package/src/models/index.ts +1 -0
- package/src/models/payloads/GetBalancesPayload.ts +2 -1
- package/src/models/payloads/GetSolanaRoutePayload.ts +8 -0
- package/src/models/payloads/index.ts +1 -0
- package/src/services/api.ts +21 -3
- package/src/services/solana/jupiter/extract-swap-data.ts +182 -0
- package/src/services/solana/jupiter/get-events.ts +37 -0
- package/src/services/solana/jupiter/instruction-parser.ts +217 -0
- package/src/services/solana/jupiter/utils.ts +37 -0
- package/src/utils/strings.ts +16 -0
- 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
|
-
|
|
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:
|
|
102
|
-
setRoute: Dispatch<SetStateAction<
|
|
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 {
|
|
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 || !
|
|
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
|
-
|
|
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
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
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
|
-
!
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
}, [
|
|
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 (
|
|
781
|
+
if ((evmAddress || solanaAddress) && supportedChains.length > 0) {
|
|
728
782
|
setIsFetchingBalances(true);
|
|
729
|
-
getBalances({
|
|
783
|
+
getBalances({
|
|
784
|
+
walletAddress: evmAddress,
|
|
785
|
+
solanaWalletAddress: solanaAddress,
|
|
786
|
+
}).then((balances) => {
|
|
730
787
|
setTokenBalances(balances);
|
|
731
788
|
setIsFetchingBalances(false);
|
|
732
789
|
});
|
|
733
790
|
}
|
|
734
|
-
}, [
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
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);
|