@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
@@ -0,0 +1,71 @@
1
+ import { useWallet } from "@src/context/WalletProvider";
2
+ import { SwapPanelType } from "../..";
3
+ import { Chain } from "@src/models";
4
+ import { useCallback } from "react";
5
+ import { shortAddress } from "@src/utils";
6
+
7
+ type Props = {
8
+ chain: Chain | undefined;
9
+ type: SwapPanelType;
10
+ className?: string;
11
+ };
12
+
13
+ export const WalletPanel = ({ chain, type, className }: Props) => {
14
+ const { setOpenEVMWalletModal, setOpenSolanaWalletModal, evm, solana } =
15
+ useWallet();
16
+ const openWalletModal = useCallback(() => {
17
+ if (!chain) {
18
+ return;
19
+ }
20
+ if (chain.ecosystem === "evm") {
21
+ setOpenEVMWalletModal(true);
22
+ evm.connectWallet();
23
+ } else if (chain.ecosystem === "solana") {
24
+ setOpenSolanaWalletModal(true);
25
+ solana.connectWallet();
26
+ }
27
+ }, [chain, evm, solana, setOpenEVMWalletModal, setOpenSolanaWalletModal]);
28
+ const renderAddress = useCallback(
29
+ (chainEcosystem: string) => {
30
+ if (chainEcosystem === "evm") {
31
+ return shortAddress(evm.address || "");
32
+ } else if (chainEcosystem === "solana") {
33
+ return shortAddress(solana.address || "");
34
+ }
35
+ },
36
+ [evm, solana],
37
+ );
38
+ return (
39
+ <>
40
+ <button
41
+ type="button"
42
+ onClick={() => openWalletModal()}
43
+ className={`${className || ""}`}
44
+ >
45
+ <div className="flex justify-between items-center p-4 gap-8">
46
+ <div className="flex flex-col items-start">
47
+ {type === "source" ? (
48
+ <div className="flex items-center gap-2">
49
+ <p className="text-xs font-medium opacity-60">Sender:</p>
50
+ {chain && (
51
+ <p className="text-sm font-medium">
52
+ {renderAddress(chain.ecosystem)}
53
+ </p>
54
+ )}
55
+ </div>
56
+ ) : (
57
+ <div className="flex items-center gap-2">
58
+ <p className="text-xs font-medium opacity-60">Receiver:</p>
59
+ {chain && (
60
+ <p className="text-sm font-medium">
61
+ {renderAddress(chain.ecosystem)}
62
+ </p>
63
+ )}
64
+ </div>
65
+ )}
66
+ </div>
67
+ </div>
68
+ </button>
69
+ </>
70
+ );
71
+ };
@@ -5,6 +5,7 @@ import { AmountPanel } from "./AmountPanel";
5
5
  import { ChainPanel } from "./ChainPanel";
6
6
  import { MaxPanel } from "./MaxPanel";
7
7
  import { TokenPanel } from "./TokenPanel";
8
+ import { WalletPanel } from "./WalletPanel";
8
9
 
9
10
  type Props = {
10
11
  type: SwapPanelType;
@@ -24,6 +25,9 @@ export const SwapPanel = ({ type }: Props) => {
24
25
 
25
26
  return (
26
27
  <div className="flex flex-col x-border rounded-xl bg-t_bg_tertiary bg-opacity-5">
28
+ <div className="flex border-b border-solid border-t_text_primary border-opacity-10">
29
+ <WalletPanel chain={chain} type={type} className="w-full flex-grow" />
30
+ </div>
27
31
  <div className="flex border-b border-solid border-t_text_primary border-opacity-10">
28
32
  <TokenPanel
29
33
  chain={chain}
@@ -1,6 +1,8 @@
1
1
  import { CloseIcon, WalletConnectIcon } from "@src/assets/icons";
2
- import { FC, useCallback, useMemo } from "react";
2
+ import { FC, useCallback } from "react";
3
3
  import { Connector, useConnect } from "wagmi";
4
+ import { useWallet as useSolanaWallet } from "@solana/wallet-adapter-react";
5
+ import { Ecosystem } from "@src/models";
4
6
 
5
7
  const ConnectorIcon: FC<{ connector: Connector; className?: string }> = ({
6
8
  connector,
@@ -26,30 +28,41 @@ const ConnectorIcon: FC<{ connector: Connector; className?: string }> = ({
26
28
  }
27
29
  };
28
30
 
29
- export const WalletPicker: FC<{ className?: string; onClose: () => void }> = ({
30
- className,
31
- onClose,
32
- }) => {
31
+ export const WalletPicker: FC<{
32
+ ecosystem: Ecosystem;
33
+ className?: string;
34
+ onClose: () => void;
35
+ }> = ({ ecosystem, className, onClose }) => {
33
36
  const { connectors, connectAsync } = useConnect();
37
+ const { wallets, select, connect } = useSolanaWallet();
34
38
 
35
- const allowedConnectors = useMemo(() => {
36
- return connectors.filter((connector) => {
37
- const isInjected = connector.type === "injected";
38
- const isInjectedGeneric = connector.name === "Injected";
39
- const isWalletConnect = connector.type === "walletConnect";
40
-
41
- return isWalletConnect || (isInjected && !isInjectedGeneric);
42
- });
43
- }, [connectors]);
44
-
45
- const handleWalletClick = useCallback(
39
+ const handleEvmWalletClick = useCallback(
46
40
  async (connector: Connector) => {
47
- await connectAsync({ connector });
48
- onClose();
41
+ try {
42
+ await connectAsync({ connector });
43
+ } catch (error) {
44
+ console.error("Failed to connect EVM wallet:", error);
45
+ } finally {
46
+ onClose();
47
+ }
49
48
  },
50
49
  [connectAsync, onClose],
51
50
  );
52
51
 
52
+ const handleConnectSolana = useCallback(
53
+ async (walletName) => {
54
+ try {
55
+ select(walletName);
56
+ await connect();
57
+ } catch (error) {
58
+ console.error("Failed to connect Solana wallet:", error);
59
+ } finally {
60
+ onClose();
61
+ }
62
+ },
63
+ [connect, select, onClose],
64
+ );
65
+
53
66
  return (
54
67
  <div className={`flex flex-col h-full ${className || ""}`}>
55
68
  <div className="flex justify-between">
@@ -63,16 +76,38 @@ export const WalletPicker: FC<{ className?: string; onClose: () => void }> = ({
63
76
  </div>
64
77
  <div className="horizontal-separator" />
65
78
  <div className="h-full pt-4">
66
- {allowedConnectors.map((connector) => (
67
- <button
68
- key={connector.id}
69
- onClick={() => handleWalletClick(connector)}
70
- className="flex items-center w-full gap-3 mt-2 mb-2 cursor-pointer p-4 border border-solid border-white border-opacity-0 hover:selected-wallet-item"
71
- >
72
- <ConnectorIcon connector={connector} />
73
- <div className="text-t_text_primary">{connector.name}</div>
74
- </button>
75
- ))}
79
+ {ecosystem === Ecosystem.EVM && (
80
+ <>
81
+ {connectors.map((connector) => (
82
+ <button
83
+ key={connector.id}
84
+ onClick={() => handleEvmWalletClick(connector)}
85
+ className="flex items-center w-full gap-3 mt-2 mb-2 cursor-pointer p-4 border border-solid border-white border-opacity-0 hover:selected-wallet-item"
86
+ >
87
+ <ConnectorIcon connector={connector} />
88
+ <div className="text-t_text_primary">{connector.name}</div>
89
+ </button>
90
+ ))}
91
+ </>
92
+ )}
93
+ {ecosystem === Ecosystem.SOLANA && (
94
+ <>
95
+ {wallets.map((wallet) => (
96
+ <button
97
+ key={wallet.adapter.name}
98
+ onClick={() => handleConnectSolana(wallet.adapter.name)}
99
+ className="flex items-center w-full gap-3 mt-2 mb-2 cursor-pointer p-4 border border-solid border-white border-opacity-0 hover:selected-wallet-item"
100
+ >
101
+ <img
102
+ src={wallet.adapter.icon}
103
+ alt={wallet.adapter.name}
104
+ className={`w-[34px] h-[34px] rounded-md ${className || ""}`}
105
+ />
106
+ <div className="text-t_text_primary">{wallet.adapter.name}</div>
107
+ </button>
108
+ ))}
109
+ </>
110
+ )}
76
111
  </div>
77
112
  </div>
78
113
  );
@@ -1,13 +1,13 @@
1
1
  import { useSwapContext } from "@src/context";
2
- import { Route } from "@src/models";
3
2
  import { useMemo } from "react";
4
3
  import { Header } from "./Header";
5
4
  import { HistoryView } from "./HistoryView";
6
5
  import { SettingsView } from "./SettingsView";
7
6
  import { SwapView } from "./SwapView";
7
+ import { UnifiedRoute } from "@src/models";
8
8
 
9
9
  type Props = {
10
- onSubmit: (route: Route) => void;
10
+ onSubmit: (route: UnifiedRoute) => void;
11
11
  onClose: () => void;
12
12
  };
13
13
  export const Swap = ({ onClose }: Props) => {
@@ -7,13 +7,13 @@ import {
7
7
  TxUIWrapper,
8
8
  useHistory,
9
9
  } from "@src/context";
10
- import { Chain, ModalIntegrationPayload, Route } from "@src/models";
11
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
10
+ import { Chain, ModalIntegrationPayload, UnifiedRoute } from "@src/models";
12
11
  import { FC, MouseEvent, useEffect, useMemo } from "react";
13
12
  import { ErrorBoundary } from "react-error-boundary";
14
- import { useReconnect, WagmiProvider } from "wagmi";
15
13
  import { changeHostVariableColor } from "../../../localTheme";
16
14
  import { setApiUrl } from "@src/services/api";
15
+ import { WalletProvider } from "@src/context/WalletProvider";
16
+ import { AllWalletsConfig } from "../AllWalletsConfig";
17
17
 
18
18
  export type TxConfigFormPayload = Omit<
19
19
  ModalIntegrationPayload,
@@ -27,12 +27,13 @@ export type TxConfigFormPayload = Omit<
27
27
  supportedChains: Chain[];
28
28
  integratorFee?: number;
29
29
  integratorFeeReceiverAddress?: string;
30
+ highlightedDstTokens?: string[];
30
31
  };
31
32
 
32
33
  type TxConfigFormProps = TxConfigFormPayload &
33
34
  (
34
35
  | {
35
- onSubmit: (route: Route) => void;
36
+ onSubmit: (route: UnifiedRoute) => void;
36
37
  onClose: () => void;
37
38
  overlay: true;
38
39
  }
@@ -43,29 +44,6 @@ type TxConfigFormProps = TxConfigFormPayload &
43
44
  }
44
45
  );
45
46
 
46
- const queryClient = new QueryClient();
47
-
48
- const WagmiReload: FC = () => {
49
- const { reconnect } = useReconnect();
50
-
51
- useEffect(() => {
52
- let count = 0;
53
- const interval = setInterval(() => {
54
- reconnect();
55
- count++;
56
- if (count >= 5) {
57
- clearInterval(interval);
58
- }
59
- }, 1000);
60
-
61
- return () => {
62
- clearInterval(interval);
63
- };
64
- }, [reconnect]);
65
-
66
- return <></>;
67
- };
68
-
69
47
  const normalizeTxConfigFormProps = (props: TxConfigFormProps) => {
70
48
  return {
71
49
  ...props,
@@ -131,6 +109,7 @@ export const TxConfigForm = (props: TxConfigFormProps) => {
131
109
  override,
132
110
  integratorFee,
133
111
  integratorFeeReceiverAddress,
112
+ highlightedDstTokens,
134
113
  } = normalizedProps;
135
114
 
136
115
  const onBackdropClick = (e: MouseEvent) => {
@@ -163,9 +142,8 @@ export const TxConfigForm = (props: TxConfigFormProps) => {
163
142
  <div>Something went wrong, please contact XSwap.</div>
164
143
  )}
165
144
  >
166
- <WagmiProvider config={wagmiConfig}>
167
- <QueryClientProvider client={queryClient}>
168
- <WagmiReload />
145
+ <AllWalletsConfig wagmiConfig={wagmiConfig}>
146
+ <WalletProvider>
169
147
  <HistoryProvider>
170
148
  <PendingTransactionsEmitter
171
149
  onPendingTransactionsChange={onPendingTransactionsChange}
@@ -193,9 +171,9 @@ export const TxConfigForm = (props: TxConfigFormProps) => {
193
171
  onConnectWallet,
194
172
  integratorFee,
195
173
  integratorFeeReceiverAddress,
174
+ highlightedDstTokens,
196
175
  }}
197
176
  supportedChains={supportedChains}
198
- wagmiConfig={wagmiConfig}
199
177
  overlay={overlay}
200
178
  >
201
179
  <TransactionProvider>
@@ -220,8 +198,8 @@ export const TxConfigForm = (props: TxConfigFormProps) => {
220
198
  </TransactionProvider>
221
199
  </SwapProvider>
222
200
  </HistoryProvider>
223
- </QueryClientProvider>
224
- </WagmiProvider>
201
+ </WalletProvider>
202
+ </AllWalletsConfig>
225
203
  </ErrorBoundary>
226
204
  );
227
205
  };
@@ -245,7 +223,7 @@ export const openTxConfigForm = async ({
245
223
  srcTokenLocked,
246
224
  srcChainLocked,
247
225
  override,
248
- }: TxConfigFormPayload): Promise<Route> => {
226
+ }: TxConfigFormPayload): Promise<UnifiedRoute> => {
249
227
  try {
250
228
  if (xpayRoot === null) {
251
229
  throw new Error("XPay was incorrectly initialized");
@@ -32,16 +32,22 @@ export const TxDataCardUI = ({
32
32
 
33
33
  const date = getDate(transaction.timestamp);
34
34
 
35
- const transferredAmount = weiToHumanReadable({
36
- amount: transaction.amountWei,
37
- decimals: srcToken?.decimals || 18,
38
- precisionFractionalPlaces: 4,
39
- });
40
- const receivedAmount = weiToHumanReadable({
41
- amount: transaction?.tokenOutAmount || "0",
42
- decimals: dstToken?.decimals || 18,
43
- precisionFractionalPlaces: 4,
44
- });
35
+ const transferredAmount =
36
+ transaction.sourceChainId === "mainnet-beta"
37
+ ? transaction.amountWei
38
+ : weiToHumanReadable({
39
+ amount: transaction.amountWei,
40
+ decimals: srcToken?.decimals || 18,
41
+ precisionFractionalPlaces: 4,
42
+ });
43
+ const receivedAmount =
44
+ transaction.sourceChainId === "mainnet-beta"
45
+ ? (transaction.tokenOutAmount as string)
46
+ : weiToHumanReadable({
47
+ amount: transaction?.tokenOutAmount || "0",
48
+ decimals: dstToken?.decimals || 18,
49
+ precisionFractionalPlaces: 4,
50
+ });
45
51
 
46
52
  const srcChain = supportedChains.find(
47
53
  (chain) => chain.chainId === transaction.sourceChainId,
@@ -19,6 +19,7 @@ export const TxDataCard = ({ transaction }: Props) => {
19
19
  transaction.targetChainId,
20
20
  )
21
21
  : undefined;
22
+
22
23
  return (
23
24
  <TxDataCardUI
24
25
  transaction={transaction}
@@ -39,6 +39,7 @@ const TxWidget = ({
39
39
  override,
40
40
  integratorFee,
41
41
  integratorFeeReceiverAddress,
42
+ highlightedDstTokens,
42
43
  }: WidgetIntegrationPayload) => {
43
44
  const [loading, setLoading] = useState(true);
44
45
  const [supportedChains, setSupportedChains] = useState<Chain[]>([]);
@@ -112,6 +113,7 @@ const TxWidget = ({
112
113
  override={override}
113
114
  integratorFee={integratorFee}
114
115
  integratorFeeReceiverAddress={integratorFeeReceiverAddress}
116
+ highlightedDstTokens={highlightedDstTokens}
115
117
  />
116
118
  )}
117
119
  </>
@@ -158,6 +160,7 @@ export const createTxWidgetWC = () => {
158
160
  override: "json",
159
161
  integratorFee: "number",
160
162
  integratorFeeReceiverAddress: "string",
163
+ highlightedDstTokens: "json",
161
164
  },
162
165
  });
163
166
 
@@ -198,4 +201,5 @@ export type TxWidgetWCAttributes = {
198
201
  "integrator-fee-receiver-address":
199
202
  | WidgetIntegrationPayload["integratorFeeReceiverAddress"]
200
203
  | undefined;
204
+ "highlighted-dst-tokens": string | undefined;
201
205
  };
@@ -50,6 +50,7 @@ export const TxWidgetWCWrapped = ({
50
50
  override,
51
51
  integratorFee,
52
52
  integratorFeeReceiverAddress,
53
+ highlightedDstTokens,
53
54
  }: WidgetIntegrationPayload) => {
54
55
  useEffect(() => {
55
56
  // We couldn't find a way to pass a function to the web component created by @r2wc/core.
@@ -90,6 +91,7 @@ export const TxWidgetWCWrapped = ({
90
91
  override={JSON.stringify(override)}
91
92
  integrator-fee={integratorFee}
92
93
  integrator-fee-receiver-address={integratorFeeReceiverAddress}
94
+ highlighted-dst-tokens={JSON.stringify(highlightedDstTokens)}
93
95
  ></xpay-widget>
94
96
  );
95
97
  };
@@ -1,4 +1,5 @@
1
1
  export * from "./Alert";
2
+ export * from "./AllWalletsConfig";
2
3
  export * from "./Button";
3
4
  export * from "./MessageBar";
4
5
  export * from "./MessageBar/SnackMessage";
@@ -4,7 +4,7 @@ import { Transport } from "viem";
4
4
  import * as WagmiChains from "viem/chains";
5
5
  import { mainnet } from "viem/chains";
6
6
  import { Config, createConfig, createStorage, http } from "@wagmi/core";
7
- import { injected, walletConnect, coinbaseWallet } from "@wagmi/connectors";
7
+ import { walletConnect } from "@wagmi/connectors";
8
8
 
9
9
  const storage = createStorage({
10
10
  key: "xpay.wagmi.store",
@@ -18,11 +18,6 @@ export const getWagmiConfig = (supportedChains: Chain[]): Config => {
18
18
  chains,
19
19
  transports,
20
20
  connectors: [
21
- injected(),
22
- coinbaseWallet({
23
- appLogoUrl: "https://xswap.link/favicon.ico",
24
- appName: "XSwap",
25
- }),
26
21
  walletConnect({
27
22
  projectId: "c392898b45ac587a280b5eb33e92aeb0",
28
23
  showQrModal: true,
@@ -1,6 +1,20 @@
1
+ import { PublicKey } from "@solana/web3.js";
1
2
  import { Ecosystem } from "@src/models";
2
3
  import { constants } from "ethers";
3
4
 
5
+ export const SOLANA_NATIVE_TOKEN_ADDRESS = new PublicKey(
6
+ "So11111111111111111111111111111111111111112",
7
+ );
8
+ export const SOLANA_TOKEN_PROGRAM_ID = new PublicKey(
9
+ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
10
+ );
11
+ export const METADATA_PROGRAM_ID =
12
+ "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
13
+ export const SOLANA_MAINNET_RPC_URL =
14
+ "https://orbital-thrilling-water.solana-mainnet.quiknode.pro/6b4a6d73d2cbe0590ce45bad3b375b183acef83f/";
15
+ export const JUPITER_V6_PROGRAM_ID = new PublicKey(
16
+ "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
17
+ );
4
18
  export const DEFAULT_ECOSYSTEM = Ecosystem.EVM;
5
19
  export const NUMBER_INPUT_REGEX = /^[0-9]*[.,]?[0-9]*$/;
6
20
  export const SLIPPAGE_PRESETS: number[] = [0.5, 1.5, 3.0];