@xswap-link/sdk 0.2.5 → 0.3.0

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 +2 -2
  2. package/.github/workflows/publish.yml +2 -2
  3. package/CHANGELOG.md +12 -0
  4. package/dist/index.css +325 -257
  5. package/dist/index.d.mts +4 -2
  6. package/dist/index.d.ts +4 -2
  7. package/dist/index.js +1296 -516
  8. package/dist/index.mjs +1323 -537
  9. package/package.json +1 -1
  10. package/src/components/Alert/index.tsx +1 -1
  11. package/src/components/Skeleton/index.tsx +3 -1
  12. package/src/components/Spinner/index.tsx +28 -0
  13. package/src/components/TxConfigForm/BalanceComponent.tsx +1 -1
  14. package/src/components/TxConfigForm/Button.tsx +1 -1
  15. package/src/components/TxConfigForm/ChainListElement.tsx +2 -2
  16. package/src/components/TxConfigForm/ConfirmationAmount.tsx +91 -0
  17. package/src/components/TxConfigForm/Description.tsx +5 -3
  18. package/src/components/TxConfigForm/ErrorField.tsx +4 -2
  19. package/src/components/TxConfigForm/FeesDetails.tsx +25 -31
  20. package/src/components/TxConfigForm/Form.tsx +763 -118
  21. package/src/components/TxConfigForm/History.tsx +5 -5
  22. package/src/components/TxConfigForm/HistoryCard.tsx +33 -40
  23. package/src/components/TxConfigForm/PoweredBy.tsx +2 -2
  24. package/src/components/TxConfigForm/Settings.tsx +33 -27
  25. package/src/components/TxConfigForm/Summary.tsx +33 -31
  26. package/src/components/TxConfigForm/SwapPanel.tsx +12 -15
  27. package/src/components/TxConfigForm/TokenPicker.tsx +36 -34
  28. package/src/components/TxConfigForm/TopBar.tsx +8 -8
  29. package/src/components/TxConfigForm/UsdPrice.tsx +4 -14
  30. package/src/components/TxConfigForm/index.tsx +37 -10
  31. package/src/components/TxStatusButton/index.tsx +42 -28
  32. package/src/components/UnknownTokenLogo/UnknownTokenLogo.tsx +1 -1
  33. package/src/components/global.css +7 -5
  34. package/src/components/icons/CircularProgressIcon.tsx +1 -1
  35. package/src/components/icons/ErrorIcon.tsx +32 -0
  36. package/src/components/icons/SignIcon.tsx +17 -0
  37. package/src/components/icons/SuccessIcon.tsx +29 -0
  38. package/src/components/icons/index.ts +3 -0
  39. package/src/config/init.tsx +42 -24
  40. package/src/constants/index.ts +2 -2
  41. package/src/models/payloads/GetSwapTxPayload.ts +2 -0
  42. package/src/services/integrations/monitoring.ts +10 -3
  43. package/src/services/integrations/transactions.ts +9 -3
  44. package/src/utils/contracts.ts +18 -0
  45. package/src/utils/index.ts +1 -0
  46. package/src/utils/parseWeb3Error.ts +93 -0
  47. package/tailwind.config.js +40 -0
@@ -20,3 +20,6 @@ export * from "./SettingsIcon";
20
20
  export * from "./XSwapBadgeIcon";
21
21
  export * from "./ChainlinkCCIPIcon";
22
22
  export * from "./XSwapLogo";
23
+ export * from "./SignIcon";
24
+ export * from "./SuccessIcon";
25
+ export * from "./ErrorIcon";
@@ -3,31 +3,33 @@ import xswapConfig from "../../xswap.config";
3
3
  import { version } from "../../package.json";
4
4
  import { WaitingForInit } from "@src/components/WaitingForInit";
5
5
 
6
- export let initIndicatorRoot: Root;
7
- export let xswapRoot: Root;
8
- export let txStatusRoot: Root;
6
+ export let xpayRoot: Root | null = null;
7
+ export let xpayInitIndicatorRoot: Root | null = null;
8
+ export let xpayTxStatusRoot: Root | null = null;
9
9
 
10
- let initCompleted = false;
10
+ let xPayInitCompleted = false;
11
+
12
+ const isServer = typeof window === "undefined";
11
13
 
12
14
  export const initDocument = () => {
13
- if (typeof document === "undefined") {
14
- throw new Error("Can't render XPay components from server side.");
15
+ if (isServer) {
16
+ return;
15
17
  }
16
- createInitIndicatorRoot();
18
+ createXPayInitIndicatorRoot();
17
19
 
18
20
  Promise.all([
19
21
  createStyleElement(),
20
- createXSwapRoot(),
21
- createTxStatusRoot(),
22
+ createXPayRoot(),
23
+ createXPayTxStatusRoot(),
22
24
  ]).then(() => {
23
- initCompleted = true;
25
+ xPayInitCompleted = true;
24
26
  });
25
27
  };
26
28
 
27
29
  export const waitForInitialization = async () => {
28
- if (!initCompleted) {
30
+ if (!xPayInitCompleted) {
29
31
  displayInitIndicator(true);
30
- while (!initCompleted) {
32
+ while (!xPayInitCompleted) {
31
33
  await new Promise((resolve) => setTimeout(resolve, 10));
32
34
  }
33
35
  displayInitIndicator(false);
@@ -35,6 +37,9 @@ export const waitForInitialization = async () => {
35
37
  };
36
38
 
37
39
  const createStyleElement = async () => {
40
+ if (isServer) {
41
+ return;
42
+ }
38
43
  const css = await fetch(
39
44
  `${xswapConfig.apiUrl}/sdk/css?${new URLSearchParams({
40
45
  data: JSON.stringify({ version }),
@@ -46,30 +51,43 @@ const createStyleElement = async () => {
46
51
  document.body.appendChild(style);
47
52
  };
48
53
 
49
- const createXSwapRoot = async () => {
54
+ const createXPayRoot = async () => {
55
+ if (isServer) {
56
+ return;
57
+ }
50
58
  const xswapElement = document.createElement("div");
51
- xswapElement.setAttribute("id", "xswap-modal");
59
+ xswapElement.setAttribute("id", "xpay-root");
60
+ xswapElement.setAttribute("class", "xpay");
52
61
  document.body.appendChild(xswapElement);
53
- xswapRoot = createRoot(xswapElement);
62
+ xpayRoot = createRoot(xswapElement);
54
63
  };
55
64
 
56
- const createTxStatusRoot = async () => {
65
+ const createXPayTxStatusRoot = async () => {
66
+ if (isServer) {
67
+ return;
68
+ }
57
69
  const txStatusElement = document.createElement("div");
58
- txStatusElement.setAttribute("id", "xswap-tx-status");
59
- txStatusElement.setAttribute("class", "xswap-tx-status");
70
+ txStatusElement.setAttribute("id", "xpay-tx-status");
71
+ txStatusElement.setAttribute("class", "xpay-tx-status");
60
72
  document.body.appendChild(txStatusElement);
61
- txStatusRoot = createRoot(txStatusElement);
73
+ xpayTxStatusRoot = createRoot(txStatusElement);
62
74
  };
63
75
 
64
- const createInitIndicatorRoot = () => {
76
+ const createXPayInitIndicatorRoot = () => {
77
+ if (isServer) {
78
+ return;
79
+ }
65
80
  const initIndicatorElement = document.createElement("div");
66
- initIndicatorElement.setAttribute("id", "xswap-init-indicator");
81
+ initIndicatorElement.setAttribute("id", "xpay-init-indicator");
67
82
  document.body.appendChild(initIndicatorElement);
68
- initIndicatorRoot = createRoot(initIndicatorElement);
83
+ xpayInitIndicatorRoot = createRoot(initIndicatorElement);
69
84
  };
70
85
 
71
86
  const displayInitIndicator = (display: boolean) => {
87
+ if (isServer || !xpayInitIndicatorRoot) {
88
+ return;
89
+ }
72
90
  display
73
- ? initIndicatorRoot.render(<WaitingForInit />)
74
- : initIndicatorRoot.render("");
91
+ ? xpayInitIndicatorRoot.render(<WaitingForInit />)
92
+ : xpayInitIndicatorRoot.render(null);
75
93
  };
@@ -7,11 +7,11 @@ export const DELIVERY_TIME = 30 * 1000 * 60;
7
7
  export const EXPRESS_DELIVERY_TIME = 30 * 1000;
8
8
  export const EXPRESS_DELIVERY_LIMIT = 1000; // USD
9
9
  export const BALANCES_CHUNK_SIZE = 500;
10
- export const ROUTE_TIMEOUT_MS = 5000;
10
+ export const ROUTE_TIMEOUT_MS = 10000;
11
11
  export const MINIMUM_DISPLAYED_TOKEN_AMOUNT = 0.0001;
12
12
  export const DEFAULT_SOURCE_CHAIN_ID = "1";
13
13
  export const CCIP_EXPLORER = "https://ccip.chain.link/";
14
- export const TX_RECEIPT_STATUS_LIFETIME = 10000;
14
+ export const TX_RECEIPT_STATUS_LIFETIME = 60000;
15
15
 
16
16
  export const MSG_SENT_EVENT_SIG =
17
17
  "MessageSent(bytes32,uint64,address,bytes,address,uint256,uint256,address,uint256)";
@@ -6,4 +6,6 @@ export type GetSwapTxPayload = {
6
6
  dstToken: string;
7
7
  customContractCalls?: ContractCall[];
8
8
  desc?: string;
9
+ retrunTransactions?: boolean;
10
+ dstDisplayToken?: string;
9
11
  };
@@ -101,9 +101,6 @@ export const renderTxStatus = async (
101
101
  addTransactionToRenderedTransactions(transaction);
102
102
  renderTxStatusButtons();
103
103
 
104
- // start listening
105
- // TODO if someone will provide tx which already is done then we will never get the event. Will need to add status endpoint on backend for transactions to check if they are done or not.
106
-
107
104
  const xSwapRouterOnDestinationAddress = ADDRESSES[dstChainId]?.XSwapRouter;
108
105
  if (!xSwapRouterOnDestinationAddress) {
109
106
  throw new Error(`Unknown destination XSwapRouter!`);
@@ -140,6 +137,16 @@ export const renderTxStatus = async (
140
137
  }
141
138
  });
142
139
 
140
+ setTimeout(
141
+ () =>
142
+ getTxStatus({ transferId: messageId }).then((response) => {
143
+ if (response.status === "DONE") {
144
+ onSuccess();
145
+ }
146
+ }),
147
+ 30 * 60 * 1000,
148
+ ); // additional check after 30 min
149
+
143
150
  xSwapRouterOnDestination.once(msgReceivedEventFilter, () => onSuccess());
144
151
  };
145
152
 
@@ -14,7 +14,9 @@ export const openTransactionModal = async ({
14
14
  dstToken,
15
15
  customContractCalls = [],
16
16
  desc,
17
- }: GetSwapTxPayload): Promise<Transactions> => {
17
+ retrunTransactions,
18
+ dstDisplayToken,
19
+ }: GetSwapTxPayload): Promise<Transactions | undefined> => {
18
20
  const supportedChains: Chain[] = (await getChains()).filter(
19
21
  ({ web3Environment, swapSupported }) =>
20
22
  web3Environment === Web3Environment.MAINNET && swapSupported,
@@ -29,9 +31,13 @@ export const openTransactionModal = async ({
29
31
  customContractCalls,
30
32
  desc,
31
33
  supportedChains,
34
+ retrunTransactions,
35
+ dstDisplayTokenAddr: dstDisplayToken,
32
36
  });
33
37
 
34
- return route.transactions;
38
+ if (retrunTransactions) {
39
+ return route.transactions;
40
+ }
35
41
  };
36
42
 
37
43
  const validateSwapTxData = (
@@ -48,7 +54,7 @@ const validateSwapTxData = (
48
54
 
49
55
  if (!isETHAddressValid(dstToken)) {
50
56
  throw new Error(
51
- `Provided token address ${dstToken} on chain ${dstChain} is not correct`,
57
+ `Provided token address ${dstToken} on chain ${dstChain} is invalid`,
52
58
  );
53
59
  }
54
60
  };
@@ -23,6 +23,24 @@ export const getBalanceOf = async (
23
23
  await contract.decimals(),
24
24
  );
25
25
  };
26
+ export const getAllowanceOf = async (
27
+ token: string,
28
+ wallet: string,
29
+ spender: string,
30
+ rpcUrl: string,
31
+ ): Promise<string> => {
32
+ const provider = ethers.getDefaultProvider(rpcUrl);
33
+
34
+ if (token === ethers.constants.AddressZero) {
35
+ return ethers.constants.MaxUint256.toString();
36
+ }
37
+
38
+ const contract = new ethers.Contract(token, ERC20Abi, provider);
39
+ return ethers.utils.formatUnits(
40
+ await contract.allowance(wallet, spender),
41
+ await contract.decimals(),
42
+ );
43
+ };
26
44
 
27
45
  export const getBalances = async (
28
46
  chain: Chain,
@@ -1,6 +1,7 @@
1
1
  export * from "./contracts";
2
2
  export * from "./numbers";
3
3
  export * from "./strings";
4
+ export * from "./parseWeb3Error";
4
5
 
5
6
  import { format } from "date-fns";
6
7
 
@@ -0,0 +1,93 @@
1
+ import { ethers } from "ethers";
2
+
3
+ /**
4
+ * Parses a Web3 error and returns a user-friendly error message.
5
+ * @param error - The error object or message to parse.
6
+ * @returns A user-friendly error message.
7
+ */
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ export function parseWeb3Error(error: any): string {
10
+ const errorMessage =
11
+ typeof error === "string"
12
+ ? error
13
+ : error?.message || "An unknown error occurred";
14
+
15
+ // User rejected the transaction
16
+ if (
17
+ errorMessage.includes("User denied transaction signature") ||
18
+ errorMessage.includes("User rejected the request")
19
+ ) {
20
+ return "Transaction was cancelled.";
21
+ }
22
+
23
+ // Insufficient funds
24
+ if (errorMessage.includes("insufficient funds")) {
25
+ return "Insufficient funds to complete the transaction.";
26
+ }
27
+
28
+ // Gas price too low
29
+ if (errorMessage.includes("gas price too low")) {
30
+ return "Gas price is too low. Please increase the gas price and try again.";
31
+ }
32
+
33
+ // Nonce too low
34
+ if (errorMessage.includes("nonce too low")) {
35
+ return "Transaction nonce is too low. Please reset your account in MetaMask and try again.";
36
+ }
37
+
38
+ // Contract execution error
39
+ if (errorMessage.includes("execution reverted")) {
40
+ const revertReason = errorMessage.match(/reason: (.+?)(?:, method|$)/)?.[1];
41
+ return revertReason
42
+ ? `Transaction failed: ${revertReason}`
43
+ : "Transaction failed during contract execution.";
44
+ }
45
+
46
+ // Gas limit exceeded
47
+ if (errorMessage.includes("exceeds block gas limit")) {
48
+ return "Transaction exceeded gas limit. Please try again with a higher gas limit.";
49
+ }
50
+
51
+ // Network error
52
+ if (
53
+ errorMessage.includes("network error") ||
54
+ errorMessage.includes("connection error")
55
+ ) {
56
+ return "Network error. Please check your internet connection and try again.";
57
+ }
58
+
59
+ // Metamask specific errors
60
+ if (errorMessage.includes("MetaMask Tx Signature:")) {
61
+ return (
62
+ "MetaMask error: " +
63
+ errorMessage.split("MetaMask Tx Signature:")[1].trim()
64
+ );
65
+ }
66
+
67
+ // Ethers.js specific errors
68
+ if (error.code) {
69
+ switch (error.code) {
70
+ case ethers.errors.INVALID_ARGUMENT:
71
+ return "Invalid argument provided to the transaction.";
72
+ case ethers.errors.MISSING_ARGUMENT:
73
+ return "Missing required argument for the transaction.";
74
+ case ethers.errors.UNEXPECTED_ARGUMENT:
75
+ return "Unexpected argument provided to the transaction.";
76
+ case ethers.errors.CALL_EXCEPTION:
77
+ return "Contract call failed. The transaction might have been reverted.";
78
+ case ethers.errors.INSUFFICIENT_FUNDS:
79
+ return "Insufficient funds to complete the transaction.";
80
+ case ethers.errors.NONCE_EXPIRED:
81
+ return "Transaction nonce has expired. Please try again.";
82
+ case ethers.errors.REPLACEMENT_UNDERPRICED:
83
+ return "Replacement transaction underpriced. Please increase the gas price.";
84
+ case ethers.errors.UNPREDICTABLE_GAS_LIMIT:
85
+ return "Unable to estimate gas limit. The transaction might fail.";
86
+ default:
87
+ return "An error occurred: " + error.message;
88
+ }
89
+ }
90
+
91
+ // If no specific error is caught, return a generic message
92
+ return "An error occurred while processing the transaction. Please try again.";
93
+ }
@@ -15,6 +15,45 @@ export default {
15
15
  x_blue_dark: "rgba(43,74,157,1)",
16
16
  x_green_light: "rgba(43,74,157,1)",
17
17
  x_green_dark: "rgba(46,125,50,1)",
18
+
19
+ x_gray_100: "#D1D3D4",
20
+ x_gray_200: "#90A4AE",
21
+ x_gray_300: "#3C3C3B",
22
+ x_gray_400: "#272727",
23
+ x_gray_500: "#0F0F0F",
24
+ x_gray_600: "#020202",
25
+ x_green_100: "#11EFAA",
26
+ x_green_200: "#07C166",
27
+ x_green_300: "#4CAF50",
28
+ x_green_400: "#2E7D32",
29
+ x_green_500: "#08493C",
30
+ x_green_600: "#032921",
31
+ x_red_100: "#FFB7B2",
32
+ x_red_200: "#FE796F",
33
+ x_red_300: "#F44336",
34
+ x_red_400: "#C62828",
35
+ x_red_500: "#890000",
36
+ x_red_600: "#540000",
37
+ x_blue_100: "#A0F2FE",
38
+ x_blue_200: "#12AAFF",
39
+ x_blue_300: "#3681C6",
40
+ x_blue_300_20: "#3681C633",
41
+ x_blue_400: "#2B4A9D",
42
+ x_blue_400_20: "#2B4A9D33",
43
+ x_blue_500: "#1C227B",
44
+ x_blue_600: "#0B173A",
45
+ x_purple_100: "#EFA9FF",
46
+ x_purple_200: "#D427FC",
47
+ x_purple_300: "#AC8FFF",
48
+ x_purple_400: "#8247E5",
49
+ x_purple_500: "#46139D",
50
+ x_purple_600: "#2E075F",
51
+ x_orange_100: "#FEECA8",
52
+ x_orange_200: "#FDD835",
53
+ x_orange_300: "#FFCA28",
54
+ x_orange_400: "#F7A80D",
55
+ x_orange_500: "#FF6F00",
56
+ x_orange_600: "#795548",
18
57
  },
19
58
  width: {
20
59
  x_desktop: "600px",
@@ -26,4 +65,5 @@ export default {
26
65
  },
27
66
  },
28
67
  plugins: [],
68
+ prefix: "xpay-",
29
69
  };