@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.
- package/.github/workflows/main.yml +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/index.d.mts +46 -12
- package/dist/index.d.ts +46 -12
- package/dist/index.global.js +5290 -261
- package/dist/index.js +9 -9
- package/dist/index.mjs +9 -9
- package/package.json +13 -2
- package/src/assets/icons/StarsIcon.tsx +18 -0
- package/src/assets/icons/index.ts +1 -0
- 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/TokenItem/index.tsx +20 -2
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +124 -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 +12 -34
- package/src/components/TxDataCard/TxDataCardUI/index.tsx +16 -10
- package/src/components/TxDataCard/index.tsx +1 -0
- package/src/components/TxWidgetWC/index.tsx +4 -0
- package/src/components/TxWidgetWCWrapped/index.tsx +2 -0
- 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/ModalIntegrationPayload.ts +1 -0
- package/src/models/payloads/WidgetIntegrationPayload.ts +1 -0
- package/src/models/payloads/index.ts +1 -0
- package/src/services/api.ts +21 -3
- package/src/services/integrations/transactions.ts +2 -0
- package/src/services/solana/jupiter/extract-swap-data.ts +183 -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
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
276
|
+
apiTransaction &&
|
|
178
277
|
transaction.status === "DONE" &&
|
|
179
|
-
|
|
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
|
-
|
|
183
|
-
txHashToTxFromAPI[transaction.hash].status = "DONE";
|
|
281
|
+
apiTransaction.status = "DONE";
|
|
184
282
|
}
|
|
185
283
|
|
|
186
284
|
if (
|
|
187
|
-
|
|
188
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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>(
|