@xswap-link/sdk 0.2.3 → 0.2.5

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 (36) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/index.css +20 -21
  3. package/dist/index.d.mts +24 -13
  4. package/dist/index.d.ts +24 -13
  5. package/dist/index.js +747 -527
  6. package/dist/index.mjs +645 -425
  7. package/package.json +1 -1
  8. package/src/components/TxConfigForm/FeesDetails.tsx +55 -37
  9. package/src/components/TxConfigForm/Form.tsx +30 -7
  10. package/src/components/TxConfigForm/History.tsx +31 -4
  11. package/src/components/TxConfigForm/Summary.tsx +28 -11
  12. package/src/components/TxConfigForm/SwapPanel.tsx +5 -1
  13. package/src/components/TxConfigForm/TopBar.tsx +8 -3
  14. package/src/components/TxConfigForm/UsdPrice.tsx +14 -1
  15. package/src/components/TxConfigForm/index.tsx +10 -10
  16. package/src/components/TxStatusButton/index.tsx +21 -13
  17. package/src/components/UnknownTokenLogo/UnknownTokenLogo.tsx +14 -0
  18. package/src/components/WaitingForInit/index.tsx +20 -0
  19. package/src/components/global.css +1 -1
  20. package/src/config/init.tsx +75 -0
  21. package/src/constants/index.ts +1 -0
  22. package/src/index.ts +9 -1
  23. package/src/models/Route.ts +6 -2
  24. package/src/models/TokenData.ts +1 -1
  25. package/src/models/TransactionHistory.ts +12 -8
  26. package/src/models/payloads/GetRoutePayload.ts +1 -0
  27. package/src/models/payloads/GetSwapTxPayload.ts +1 -0
  28. package/src/services/api.ts +9 -0
  29. package/src/services/blockchain.ts +49 -0
  30. package/src/services/index.ts +1 -0
  31. package/src/services/integrations/monitoring.ts +34 -14
  32. package/src/services/integrations/transactions.ts +8 -3
  33. package/src/utils/contracts.ts +2 -4
  34. package/src/utils/strings.ts +4 -0
  35. package/tailwind.config.js +2 -2
  36. package/src/config/init.ts +0 -35
@@ -0,0 +1,75 @@
1
+ import { createRoot, Root } from "react-dom/client";
2
+ import xswapConfig from "../../xswap.config";
3
+ import { version } from "../../package.json";
4
+ import { WaitingForInit } from "@src/components/WaitingForInit";
5
+
6
+ export let initIndicatorRoot: Root;
7
+ export let xswapRoot: Root;
8
+ export let txStatusRoot: Root;
9
+
10
+ let initCompleted = false;
11
+
12
+ export const initDocument = () => {
13
+ if (typeof document === "undefined") {
14
+ throw new Error("Can't render XPay components from server side.");
15
+ }
16
+ createInitIndicatorRoot();
17
+
18
+ Promise.all([
19
+ createStyleElement(),
20
+ createXSwapRoot(),
21
+ createTxStatusRoot(),
22
+ ]).then(() => {
23
+ initCompleted = true;
24
+ });
25
+ };
26
+
27
+ export const waitForInitialization = async () => {
28
+ if (!initCompleted) {
29
+ displayInitIndicator(true);
30
+ while (!initCompleted) {
31
+ await new Promise((resolve) => setTimeout(resolve, 10));
32
+ }
33
+ displayInitIndicator(false);
34
+ }
35
+ };
36
+
37
+ const createStyleElement = async () => {
38
+ const css = await fetch(
39
+ `${xswapConfig.apiUrl}/sdk/css?${new URLSearchParams({
40
+ data: JSON.stringify({ version }),
41
+ })}`,
42
+ );
43
+ const text = await css.text();
44
+ const style = document.createElement("style");
45
+ style.textContent = text;
46
+ document.body.appendChild(style);
47
+ };
48
+
49
+ const createXSwapRoot = async () => {
50
+ const xswapElement = document.createElement("div");
51
+ xswapElement.setAttribute("id", "xswap-modal");
52
+ document.body.appendChild(xswapElement);
53
+ xswapRoot = createRoot(xswapElement);
54
+ };
55
+
56
+ const createTxStatusRoot = async () => {
57
+ const txStatusElement = document.createElement("div");
58
+ txStatusElement.setAttribute("id", "xswap-tx-status");
59
+ txStatusElement.setAttribute("class", "xswap-tx-status");
60
+ document.body.appendChild(txStatusElement);
61
+ txStatusRoot = createRoot(txStatusElement);
62
+ };
63
+
64
+ const createInitIndicatorRoot = () => {
65
+ const initIndicatorElement = document.createElement("div");
66
+ initIndicatorElement.setAttribute("id", "xswap-init-indicator");
67
+ document.body.appendChild(initIndicatorElement);
68
+ initIndicatorRoot = createRoot(initIndicatorElement);
69
+ };
70
+
71
+ const displayInitIndicator = (display: boolean) => {
72
+ display
73
+ ? initIndicatorRoot.render(<WaitingForInit />)
74
+ : initIndicatorRoot.render("");
75
+ };
@@ -5,6 +5,7 @@ export const NUMBER_INPUT_REGEX = /^[0-9]*[.,]?[0-9]*$/;
5
5
  export const SLIPPAGE_PRESETS: number[] = [0.5, 1.5, 3.0];
6
6
  export const DELIVERY_TIME = 30 * 1000 * 60;
7
7
  export const EXPRESS_DELIVERY_TIME = 30 * 1000;
8
+ export const EXPRESS_DELIVERY_LIMIT = 1000; // USD
8
9
  export const BALANCES_CHUNK_SIZE = 500;
9
10
  export const ROUTE_TIMEOUT_MS = 5000;
10
11
  export const MINIMUM_DISPLAYED_TOKEN_AMOUNT = 0.0001;
package/src/index.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import { initDocument } from "@src/config/init";
2
+ import { openTransactionModal, renderTxStatus } from "@src/services";
2
3
 
3
4
  initDocument();
4
5
 
5
6
  export * from "@src/models";
6
- export { getSwapTx, monitorTransactionStatus } from "@src/services";
7
+ export { openTransactionModal, renderTxStatus } from "@src/services";
8
+
9
+ const XPay = {
10
+ openTransactionModal,
11
+ renderTxStatus,
12
+ };
13
+
14
+ export default XPay;
@@ -1,5 +1,3 @@
1
- import { TransactionRequest } from "@ethersproject/providers";
2
-
3
1
  export type Route = {
4
2
  estAmountOut: string;
5
3
  minAmountOut: string;
@@ -13,6 +11,12 @@ export type Transactions = {
13
11
  swap: TransactionRequest;
14
12
  };
15
13
 
14
+ export type TransactionRequest = {
15
+ to: string;
16
+ data: string;
17
+ value: string;
18
+ };
19
+
16
20
  export type XSwapFees = {
17
21
  ccipFee?: string;
18
22
  xSwapFee: XSwapFee;
@@ -24,7 +24,7 @@ export type Token = {
24
24
  name: string;
25
25
  symbol: string;
26
26
  decimals: number;
27
- image: string;
27
+ image?: string;
28
28
  priority: number;
29
29
  quickPick?: boolean;
30
30
  supported?: boolean;
@@ -34,7 +34,11 @@ export type HistoryTransaction = {
34
34
  } | null;
35
35
  };
36
36
 
37
- export type TransactionStatus = "IN_PROGRESS" | "DONE" | "REVERTED";
37
+ export type TransactionStatus =
38
+ | "IN_PROGRESS"
39
+ | "DONE"
40
+ | "REVERTED"
41
+ | "NOT_FOUND";
38
42
 
39
43
  export type Transaction = {
40
44
  hash: string;
@@ -50,13 +54,13 @@ export type Transaction = {
50
54
  };
51
55
 
52
56
  export type MonitoredTransaction = {
53
- fromChain: string | undefined;
54
- fromChainImage: string | undefined;
55
- fromToken: string | undefined;
56
- fromTokenImage: string | undefined;
57
- fromAmount: string | undefined;
58
- toChain: string | undefined;
59
- toChainImage: string | undefined;
57
+ srcChain: string | undefined;
58
+ srcChainImage: string | undefined;
59
+ srcToken: string | undefined;
60
+ srcTokenImage: string | undefined;
61
+ srcAmount: string | undefined;
62
+ dstChain: string | undefined;
63
+ dstChainImage: string | undefined;
60
64
  isDone: boolean;
61
65
  txHash: string;
62
66
  explorer: string | undefined;
@@ -1,6 +1,7 @@
1
1
  import { CoinTypeAddress, CollectFees, ContractCall } from "@src/models";
2
2
 
3
3
  export type GetRoutePayload = {
4
+ integratorId: string;
4
5
  fromChain: string;
5
6
  toChain: string;
6
7
  fromToken: string;
@@ -1,6 +1,7 @@
1
1
  import { ContractCall } from "@src/models";
2
2
 
3
3
  export type GetSwapTxPayload = {
4
+ integratorId: string;
4
5
  dstChain: string;
5
6
  dstToken: string;
6
7
  customContractCalls?: ContractCall[];
@@ -9,6 +9,7 @@ import {
9
9
  Token,
10
10
  TokenPrices,
11
11
  TransactionHistory,
12
+ TransactionStatus,
12
13
  } from "@src/models";
13
14
 
14
15
  async function _sendRequest<T>(
@@ -110,3 +111,11 @@ export async function getPrices(payload: GetPricesPayload) {
110
111
  })}`,
111
112
  );
112
113
  }
114
+
115
+ export async function getTxStatus(payload: { transferId: string }) {
116
+ return await _sendRequest<{ status: TransactionStatus }>(
117
+ `/getTxStatus?${new URLSearchParams({
118
+ data: JSON.stringify(payload), // todo remove once gcp starts working
119
+ })}`,
120
+ );
121
+ }
@@ -0,0 +1,49 @@
1
+ import { ERC20Abi } from "@src/contracts";
2
+ import { ethers } from "ethers";
3
+ import { Chain, Token } from "@src/models";
4
+
5
+ export const getCustomTokenData = async (
6
+ chain: Chain,
7
+ tokenAddress: string,
8
+ ): Promise<Token> => {
9
+ const rpcUrl = chain.publicRpcUrls[0];
10
+
11
+ const customTokenContract = new ethers.Contract(
12
+ tokenAddress.toLowerCase(),
13
+ ERC20Abi,
14
+ ethers.getDefaultProvider(rpcUrl),
15
+ );
16
+ try {
17
+ const query = async (
18
+ contract: ethers.Contract | null,
19
+ method: string,
20
+ args: unknown[] = [],
21
+ ) => {
22
+ if (contract?.provider) {
23
+ const response = await contract[method](...args);
24
+ return response;
25
+ }
26
+ return null;
27
+ };
28
+ const responses = await Promise.all([
29
+ query(customTokenContract, "decimals"),
30
+ query(customTokenContract, "name"),
31
+ query(customTokenContract, "symbol"),
32
+ ]);
33
+ const [decimals, name, symbol] = responses;
34
+
35
+ return {
36
+ address: tokenAddress,
37
+ symbol: symbol,
38
+ name: name,
39
+ decimals: decimals,
40
+ quickPick: false,
41
+ supported: true,
42
+ priority: 0,
43
+ };
44
+ } catch (_) {
45
+ throw new Error(
46
+ `Provided token ${tokenAddress} does not exists on chain ${chain.displayName}`,
47
+ );
48
+ }
49
+ };
@@ -1,2 +1,3 @@
1
1
  export * from "./api";
2
2
  export * from "./integrations";
3
+ export * from "./blockchain";
@@ -1,5 +1,5 @@
1
1
  import { Chain, MonitoredTransaction, Web3Environment } from "@src/models";
2
- import { getChains } from "@src/services";
2
+ import { getChains, getTxStatus } from "@src/services";
3
3
  import { ethers } from "ethers";
4
4
  import { weiToHumanReadable } from "@src/utils";
5
5
  import {
@@ -18,8 +18,9 @@ import {
18
18
  } from "@src/components";
19
19
  import { ADDRESSES } from "@src/contracts";
20
20
  import retry from "async-retry";
21
+ import { getCustomTokenData } from "../blockchain";
21
22
 
22
- export const monitorTransactionStatus = async (
23
+ export const renderTxStatus = async (
23
24
  txChainId: string,
24
25
  txHash: string,
25
26
  ): Promise<void> => {
@@ -52,11 +53,20 @@ export const monitorTransactionStatus = async (
52
53
  const srcChain = supportedChains.find(
53
54
  (chain) => chain.chainId === txChainId.toString(),
54
55
  );
56
+ if (!srcChain) {
57
+ throw new Error(`Unknown src chain ${txChainId}`);
58
+ }
59
+
60
+ const tokenAddress = decodedMessageSentEvent.args?.token.toString();
55
61
 
56
- const srcToken = srcChain?.tokens.find(
62
+ let srcToken = srcChain.tokens.find(
57
63
  (token) => token.address === decodedMessageSentEvent.args?.token.toString(),
58
64
  );
59
65
 
66
+ if (!srcToken) {
67
+ srcToken = await getCustomTokenData(srcChain, tokenAddress);
68
+ }
69
+
60
70
  const dstChain = supportedChains.find(
61
71
  (chain) =>
62
72
  chain.ccipChainId ===
@@ -69,19 +79,21 @@ export const monitorTransactionStatus = async (
69
79
  throw new Error(`Unknown destination chain!`);
70
80
  }
71
81
 
82
+ const tokenAmount = weiToHumanReadable({
83
+ amount: decodedMessageSentEvent.args?.tokenAmount.toString(),
84
+ decimals: srcToken?.decimals || 18,
85
+ precisionFractionalPlaces: 4,
86
+ });
87
+
72
88
  const transaction: MonitoredTransaction = {
73
89
  txHash: txHash,
74
- fromChain: srcChain?.displayName,
75
- fromChainImage: srcChain?.image,
76
- toChain: dstChain.displayName,
77
- toChainImage: dstChain.image,
78
- fromToken: srcToken?.symbol,
79
- fromTokenImage: srcToken?.image,
80
- fromAmount: weiToHumanReadable({
81
- amount: decodedMessageSentEvent.args?.tokenAmount.toString(),
82
- decimals: srcToken?.decimals || 18,
83
- precisionFractionalPlaces: 4,
84
- }),
90
+ srcChain: srcChain.displayName,
91
+ srcChainImage: srcChain.image,
92
+ dstChain: dstChain.displayName,
93
+ dstChainImage: dstChain.image,
94
+ srcToken: srcToken?.symbol,
95
+ srcTokenImage: srcToken?.image,
96
+ srcAmount: tokenAmount === "0" ? "<0.0001" : tokenAmount,
85
97
  isDone: false,
86
98
  explorer: `${CCIP_EXPLORER}/tx/${txHash}`,
87
99
  };
@@ -120,6 +132,14 @@ export const monitorTransactionStatus = async (
120
132
  removeTransactionFromRenderedTransactions(txHash);
121
133
  renderTxStatusButtons();
122
134
  };
135
+
136
+ // check tx status on backend
137
+ getTxStatus({ transferId: messageId }).then((response) => {
138
+ if (response.status === "DONE") {
139
+ onSuccess();
140
+ }
141
+ });
142
+
123
143
  xSwapRouterOnDestination.once(msgReceivedEventFilter, () => onSuccess());
124
144
  };
125
145
 
@@ -6,8 +6,10 @@ import {
6
6
  } from "@src/models";
7
7
  import { getChains } from "@src/services";
8
8
  import { openTxConfigForm } from "@src/components";
9
+ import { isETHAddressValid } from "@src/utils";
9
10
 
10
- export const getSwapTx = async ({
11
+ export const openTransactionModal = async ({
12
+ integratorId,
11
13
  dstChain,
12
14
  dstToken,
13
15
  customContractCalls = [],
@@ -21,6 +23,7 @@ export const getSwapTx = async ({
21
23
  validateSwapTxData(supportedChains, dstChain, dstToken);
22
24
 
23
25
  const route = await openTxConfigForm({
26
+ integratorId: integratorId,
24
27
  dstChainId: dstChain,
25
28
  dstTokenAddr: dstToken,
26
29
  customContractCalls,
@@ -43,7 +46,9 @@ const validateSwapTxData = (
43
46
  throw new Error(`Provided chain '${dstChain}' is not supported`);
44
47
  }
45
48
 
46
- if (!supportedDstChain.tokens.some(({ address }) => address === dstToken)) {
47
- throw new Error(`Provided token '${dstToken}' is not supported`);
49
+ if (!isETHAddressValid(dstToken)) {
50
+ throw new Error(
51
+ `Provided token address ${dstToken} on chain ${dstChain} is not correct`,
52
+ );
48
53
  }
49
54
  };
@@ -1,8 +1,6 @@
1
1
  import { BigNumber, BigNumberish, ethers } from "ethers";
2
- import { TransactionRequest } from "@ethersproject/providers";
3
- import { safeBigNumberFrom } from "./numbers";
4
2
  import { ADDRESSES, BatchQueryAbi, ERC20Abi } from "@src/contracts";
5
- import { Chain, TokenBalances } from "@src/models";
3
+ import { Chain, TokenBalances, TransactionRequest } from "@src/models";
6
4
  import { chunkArray } from "@src/utils";
7
5
  import { BALANCES_CHUNK_SIZE } from "@src/constants";
8
6
 
@@ -104,7 +102,7 @@ export const generateApproveTxData = (
104
102
  return tokenAddress !== ethers.constants.AddressZero
105
103
  ? {
106
104
  to: tokenAddress,
107
- value: safeBigNumberFrom("0"),
105
+ value: "0",
108
106
  data: IERC20.encodeFunctionData("approve", [spender, amount]),
109
107
  }
110
108
  : undefined;
@@ -4,3 +4,7 @@ export const shortAddress = (address: string): string => {
4
4
  address.length,
5
5
  )}`;
6
6
  };
7
+
8
+ export const isETHAddressValid = (address: string): boolean => {
9
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
10
+ };
@@ -1,5 +1,5 @@
1
1
  /** @type {import("tailwindcss").Config} */
2
- module.exports = {
2
+ export default {
3
3
  content: ["./src/components/**/*.{js,ts,jsx,tsx,mdx}"],
4
4
  theme: {
5
5
  extend: {
@@ -18,7 +18,7 @@ module.exports = {
18
18
  },
19
19
  width: {
20
20
  x_desktop: "600px",
21
- x_mobile: "320px",
21
+ x_mobile: "360px",
22
22
  },
23
23
  animation: {
24
24
  "spin-slow": "spin 3s linear infinite",
@@ -1,35 +0,0 @@
1
- import { createRoot, Root } from "react-dom/client";
2
- import xswapConfig from "../../xswap.config";
3
- import { version } from "../../package.json";
4
-
5
- export let xswapRoot: Root;
6
- export let txStatusRoot: Root;
7
-
8
- export const initDocument = () => {
9
- if (typeof document === "undefined") {
10
- throw new Error("Can't render XPay components from server side.");
11
- }
12
-
13
- const cssRequestParams = new URLSearchParams({
14
- data: JSON.stringify({ version }),
15
- });
16
-
17
- fetch(`${xswapConfig.apiUrl}/sdk/css?${cssRequestParams}`).then(
18
- async (css) => {
19
- const style = document.createElement("style");
20
- style.textContent = await css.text();
21
- document.body.appendChild(style);
22
- },
23
- );
24
-
25
- const xswapElement = document.createElement("div");
26
- xswapElement.setAttribute("id", "xswap-modal");
27
- document.body.appendChild(xswapElement);
28
- xswapRoot = createRoot(xswapElement);
29
-
30
- const txStatusElement = document.createElement("div");
31
- txStatusElement.setAttribute("id", "xswap-tx-status");
32
- txStatusElement.setAttribute("class", "xswap-tx-status");
33
- document.body.appendChild(txStatusElement);
34
- txStatusRoot = createRoot(txStatusElement);
35
- };