@xswap-link/sdk 0.0.12 → 0.1.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 (81) hide show
  1. package/.github/workflows/main.yml +1 -1
  2. package/.github/workflows/publish.yml +1 -1
  3. package/.prettierrc +7 -0
  4. package/CHANGELOG.md +17 -0
  5. package/README.md +23 -14
  6. package/dist/index.css +1012 -0
  7. package/dist/index.d.mts +209 -52
  8. package/dist/index.d.ts +209 -52
  9. package/dist/index.js +33963 -88
  10. package/dist/index.mjs +33949 -76
  11. package/package.json +8 -2
  12. package/postcss.config.js +3 -0
  13. package/src/components/Alert/index.tsx +9 -0
  14. package/src/components/Skeleton/index.tsx +10 -0
  15. package/src/components/TxConfigForm/Form.tsx +567 -0
  16. package/src/components/TxConfigForm/History.tsx +159 -0
  17. package/src/components/TxConfigForm/HistoryCard.tsx +209 -0
  18. package/src/components/TxConfigForm/TokenPicker.tsx +165 -0
  19. package/src/components/TxConfigForm/index.jsx +100 -0
  20. package/src/components/global.css +34 -0
  21. package/src/components/global.d.ts +6 -0
  22. package/src/components/icons/ArrowRightIcon.tsx +11 -0
  23. package/src/components/icons/ArrowUpRightIcon.tsx +11 -0
  24. package/src/components/icons/CheckIcon.tsx +11 -0
  25. package/src/components/icons/ChevronDownIcon.tsx +15 -0
  26. package/src/components/icons/ChevronUpIcon.tsx +15 -0
  27. package/src/components/icons/CircularProgressIcon.tsx +24 -0
  28. package/src/components/icons/CloseIcon.tsx +15 -0
  29. package/src/components/icons/CoinsIcon.tsx +15 -0
  30. package/src/components/icons/DownArrorIcon.tsx +17 -0
  31. package/src/components/icons/HistoryIcon.tsx +13 -0
  32. package/src/components/icons/HourGlassIcon.tsx +11 -0
  33. package/src/components/icons/PercentageIcon.tsx +29 -0
  34. package/src/components/icons/SearchIcon.tsx +15 -0
  35. package/src/components/icons/XMarkIcon.tsx +11 -0
  36. package/src/components/icons/index.ts +14 -0
  37. package/src/components/index.ts +3 -0
  38. package/src/constants/index.ts +6 -5
  39. package/src/contracts/abi/BatchQuery.json +52 -0
  40. package/src/contracts/abi/index.ts +2 -0
  41. package/src/contracts/addresses.ts +34 -0
  42. package/src/contracts/index.ts +1 -0
  43. package/src/hooks/index.ts +1 -0
  44. package/src/hooks/useDebounce.tsx +21 -0
  45. package/src/index.ts +6 -0
  46. package/src/models/Addresses.ts +12 -0
  47. package/src/models/Route.ts +17 -5
  48. package/src/models/TokenData.ts +45 -0
  49. package/src/models/TransactionHistory.ts +48 -0
  50. package/src/models/XSwapConfig.ts +3 -0
  51. package/src/models/forms/TxConfigFormData.ts +8 -0
  52. package/src/models/forms/index.ts +1 -0
  53. package/src/models/index.ts +6 -6
  54. package/src/models/integrations/GenerateStakingCallsParams.ts +8 -0
  55. package/src/models/integrations/index.ts +1 -0
  56. package/src/models/payloads/GetPricesPayload.ts +4 -0
  57. package/src/models/payloads/GetRoutePayload.ts +1 -3
  58. package/src/models/payloads/GetSwapTxPayload.ts +8 -0
  59. package/src/models/payloads/index.ts +2 -0
  60. package/src/services/api.ts +46 -64
  61. package/src/services/index.ts +1 -0
  62. package/src/services/integrations/customCalls/index.ts +1 -0
  63. package/src/services/integrations/customCalls/staking.ts +83 -0
  64. package/src/services/integrations/index.ts +2 -0
  65. package/src/services/integrations/transactions.ts +30 -0
  66. package/src/utils/contracts.ts +153 -11
  67. package/src/utils/index.ts +49 -1
  68. package/src/utils/numbers.ts +47 -0
  69. package/src/utils/strings.ts +6 -0
  70. package/tailwind.config.js +14 -0
  71. package/test/api.test.ts +1 -1
  72. package/tsconfig.json +10 -1
  73. package/xswap.config.ts +18 -0
  74. package/index.ts +0 -5
  75. package/src/models/ApiOverrides.ts +0 -3
  76. package/src/models/Chain.ts +0 -20
  77. package/src/models/Prices.ts +0 -9
  78. package/src/models/Token.ts +0 -10
  79. package/src/models/XSwapFee.ts +0 -4
  80. package/src/models/XSwapFees.ts +0 -7
  81. package/src/utils/bigNumbers.ts +0 -7
@@ -0,0 +1,159 @@
1
+ import { Transaction, TransactionStatus, Chain } from "@src/models";
2
+ import React, { FC, useCallback, useEffect, useState } from "react";
3
+ import { getHistory } from "@src/services";
4
+ import { FALLBACK_CROSSCHAIN_ESTIMATION_TIME } from "@src/constants";
5
+ import BigNumberJS from "bignumber.js";
6
+ import { HistoryCard } from "./HistoryCard";
7
+ import { CircularProgressIcon } from "../icons";
8
+
9
+ const PENDING_TXS_LOCAL_STORAGE_KEY = "pending_transactions";
10
+
11
+ type LocalHistory = { [walletAddress: string]: Transaction[] };
12
+
13
+ interface Props {
14
+ signer: string;
15
+ supportedChains: Chain[];
16
+ }
17
+
18
+ export const History: FC<Props> = ({ signer, supportedChains }) => {
19
+ const [mappedHistory, setMappedHistory] = useState<Transaction[]>();
20
+ // const [latestHistory, setLatestHistory] = useState<Transaction[]>();
21
+ const [historyLoadedOnce, setHistoryLoadedOnce] = useState(false);
22
+
23
+ const getLocalHistory = () => {
24
+ return JSON.parse(
25
+ localStorage.getItem(PENDING_TXS_LOCAL_STORAGE_KEY) || "{}",
26
+ ) as LocalHistory;
27
+ };
28
+
29
+ const setLocalHistory = (data: LocalHistory) => {
30
+ localStorage.setItem(PENDING_TXS_LOCAL_STORAGE_KEY, JSON.stringify(data));
31
+ };
32
+
33
+ const fetchHistory = useCallback(async () => {
34
+ const downloadedHistory = await getHistory({ walletAddress: signer });
35
+ const fetchedTransactions: Transaction[] = [];
36
+ if (downloadedHistory) {
37
+ for (const entry of downloadedHistory.history) {
38
+ if (!entry.source) {
39
+ continue;
40
+ }
41
+ let status: TransactionStatus;
42
+ if (entry.failed) status = "REVERTED";
43
+ if (!!entry.target) {
44
+ status = "DONE";
45
+ } else {
46
+ status = "IN_PROGRESS";
47
+ }
48
+ const timestampMs = new Date(entry.source?.blockTime).getTime();
49
+
50
+ let estimatedDeliveryTimestamp = FALLBACK_CROSSCHAIN_ESTIMATION_TIME;
51
+
52
+ if (new BigNumberJS(entry.source.valueForInstantCcipRecieve).gt(0)) {
53
+ estimatedDeliveryTimestamp = timestampMs + 30 * 1000;
54
+ } else {
55
+ const blockchainEstimate = timestampMs + 30 * 1000 * 60;
56
+
57
+ if (blockchainEstimate) {
58
+ estimatedDeliveryTimestamp = blockchainEstimate;
59
+ }
60
+ }
61
+
62
+ fetchedTransactions.push({
63
+ hash: entry.source.transactionHash,
64
+ timestamp: timestampMs / 1000,
65
+ sourceChainId: entry.source.blockchainId,
66
+ targetChainId: entry.source.targetBlockchainId,
67
+ amountWei: entry.source.tokenAmount,
68
+ tokenAddress: entry.source.tokenAddress,
69
+ tokenOutAddress:
70
+ entry.target?.tokenAddress || entry.source.tokenOutAddress,
71
+ tokenOutAmount:
72
+ entry.target?.tokenAmount || entry.source.estimatedAmountOut,
73
+ estimatedDeliveryTimestamp,
74
+ status,
75
+ });
76
+ }
77
+
78
+ const txHashToTxFromAPI: Record<string, Transaction> = {};
79
+ for (const transaction of fetchedTransactions) {
80
+ txHashToTxFromAPI[transaction.hash] = transaction;
81
+ }
82
+
83
+ const localHistory = getLocalHistory();
84
+ const newLocalHistory: Record<string, Transaction[]> = {};
85
+
86
+ for (const [walletAddress, transactions] of Object.entries(
87
+ localHistory,
88
+ )) {
89
+ if (walletAddress.toLowerCase() !== signer.toLowerCase()) {
90
+ continue;
91
+ }
92
+ const newLocalHistoryForWallet: Transaction[] = [];
93
+
94
+ for (const transaction of transactions) {
95
+ if (!txHashToTxFromAPI[transaction.hash]) {
96
+ // localStorage has fresher data.
97
+ fetchedTransactions.push(transaction);
98
+ newLocalHistoryForWallet.push(transaction);
99
+ continue;
100
+ }
101
+
102
+ if (
103
+ txHashToTxFromAPI[transaction.hash] &&
104
+ transaction.status === "DONE" &&
105
+ txHashToTxFromAPI[transaction.hash]?.status === "IN_PROGRESS"
106
+ ) {
107
+ // If we know on the frontend that the tx is done we should update backend result
108
+ txHashToTxFromAPI[transaction.hash]!.status = "DONE";
109
+ }
110
+
111
+ if (
112
+ txHashToTxFromAPI[transaction.hash]?.status === "DONE" ||
113
+ txHashToTxFromAPI[transaction.hash]?.status === "REVERTED"
114
+ ) {
115
+ // API has the same or fresher data.
116
+ continue;
117
+ }
118
+ }
119
+
120
+ newLocalHistory[walletAddress.toLowerCase()] = newLocalHistoryForWallet;
121
+ }
122
+ setLocalHistory(newLocalHistory);
123
+ setMappedHistory(
124
+ fetchedTransactions.sort((a, b) => b.timestamp - a.timestamp),
125
+ );
126
+ setHistoryLoadedOnce(true);
127
+ }
128
+ }, [signer, getHistory]);
129
+
130
+ useEffect(() => {
131
+ if (!historyLoadedOnce) fetchHistory();
132
+ const timer = setInterval(() => {
133
+ fetchHistory();
134
+ }, 60000);
135
+ return () => clearInterval(timer);
136
+ }, [signer, fetchHistory, getHistory, historyLoadedOnce]);
137
+
138
+ return (
139
+ <div className="w-full h-[50vh] overflow-scroll overflow-x-hidden">
140
+ {mappedHistory?.length === 0 && historyLoadedOnce && (
141
+ <div className="w-full text-center py-4 px-0 text-[rgb(158,158,158)]">
142
+ Your history is empty...
143
+ </div>
144
+ )}
145
+ {!mappedHistory && !historyLoadedOnce && (
146
+ <div className="flex w-full h-full items-center justify-center">
147
+ <CircularProgressIcon />
148
+ </div>
149
+ )}
150
+ {mappedHistory?.map((transaction, index) => (
151
+ <HistoryCard
152
+ key={index}
153
+ transaction={transaction}
154
+ supportedChains={supportedChains}
155
+ />
156
+ ))}
157
+ </div>
158
+ );
159
+ };
@@ -0,0 +1,209 @@
1
+ import { FC, useCallback, useMemo } from "react";
2
+ import { format } from "date-fns";
3
+ import { Transaction, Chain, Token } from "@src/models";
4
+ import { deepMergeObjects, weiToHumanReadable } from "@src/utils";
5
+ import {
6
+ ArrowRightIcon,
7
+ ArrowUpRightIcon,
8
+ CheckIcon,
9
+ HourGlassIcon,
10
+ XMarkIcon,
11
+ } from "@src/components/icons";
12
+
13
+ interface Props {
14
+ transaction: Transaction;
15
+ supportedChains: Chain[];
16
+ }
17
+
18
+ export const HistoryCard: FC<Props> = ({ transaction, supportedChains }) => {
19
+ const getDate = useCallback((blockTimestamp: number) => {
20
+ const date = new Date(blockTimestamp * 1000);
21
+ return format(date, "d MMM yyyy HH:mm:ss");
22
+ }, []);
23
+
24
+ const date = getDate(transaction.timestamp);
25
+
26
+ const supportedTokens: Record<string, Record<string, Token>> = useMemo(() => {
27
+ const customTokens = JSON.parse(
28
+ localStorage.getItem("custom-tokens") || "{}",
29
+ );
30
+
31
+ const chainIdToTokenAddressToData: Record<
32
+ string,
33
+ Record<string, Token>
34
+ > = {};
35
+
36
+ supportedChains.forEach((chain) => {
37
+ chainIdToTokenAddressToData[chain.chainId] = {};
38
+ chain.tokens.forEach((token) => {
39
+ chainIdToTokenAddressToData[chain.chainId]![token.address] = token;
40
+ });
41
+ });
42
+ return deepMergeObjects(chainIdToTokenAddressToData, customTokens);
43
+ }, [supportedChains]);
44
+
45
+ const findTokenDataByAddressAndChain = useCallback(
46
+ (tokenAddress: string, chainId: string): Token | undefined => {
47
+ return supportedTokens[chainId]?.[tokenAddress];
48
+ },
49
+ [supportedTokens],
50
+ );
51
+
52
+ const tokenData = findTokenDataByAddressAndChain(
53
+ transaction.tokenAddress.toLowerCase(),
54
+ transaction.sourceChainId,
55
+ );
56
+ const tokenOutData = transaction.tokenOutAddress
57
+ ? findTokenDataByAddressAndChain(
58
+ transaction.tokenOutAddress?.toLowerCase(),
59
+ transaction.targetChainId,
60
+ )
61
+ : undefined;
62
+ const transferredAmount = weiToHumanReadable({
63
+ amount: transaction.amountWei,
64
+ decimals: tokenData?.decimals || 18,
65
+ precisionFractionalPlaces: 4,
66
+ });
67
+ const receivedAmount = weiToHumanReadable({
68
+ amount: transaction?.tokenOutAmount || "0",
69
+ decimals: tokenOutData?.decimals || 18,
70
+ precisionFractionalPlaces: 4,
71
+ });
72
+
73
+ const sourceChainData = supportedChains.find(
74
+ (chain) => chain.chainId === transaction.sourceChainId,
75
+ );
76
+
77
+ const targetChainData = supportedChains.find(
78
+ (chain) => chain.chainId === transaction.targetChainId,
79
+ );
80
+
81
+ return (
82
+ <div className="flex flex-col border border-solid border-[rgba(255,255,255,0.1)] rounded-xl bg-[rgb(15,15,15)] p-2 gap-4 mb-2">
83
+ <div className="flex items-center">
84
+ <div className="text-[rgba(255,255,255,0.6)] text-xs">{date}</div>
85
+ <div className="flex flex-grow justify-end items-center">
86
+ {transaction.status === "IN_PROGRESS" && (
87
+ <div className="flex items-center">
88
+ <div className="w-3.5 h-3.5 mr-1 text-[rgb(250,200,100)]">
89
+ <HourGlassIcon />
90
+ </div>
91
+ <div className="text-xs text-[rgb(250,200,100)]">In progress</div>
92
+ </div>
93
+ )}
94
+
95
+ {transaction.status === "DONE" && (
96
+ <div className="flex items-center">
97
+ <div className="w-3.5 h-3.5 mr-1 text-[rgb(100,200,100)]">
98
+ <CheckIcon />
99
+ </div>
100
+ <div className="text-xs text-[rgb(100,200,100)]">Done</div>
101
+ </div>
102
+ )}
103
+
104
+ {transaction.status === "REVERTED" && (
105
+ <div className="flex items-center">
106
+ <div className="w-4 h-4 mr-1 text-[rgb(255,100,100)]">
107
+ <XMarkIcon />
108
+ </div>
109
+ <div className="text-xs text-[rgb(255,100,100)]">Reverted</div>
110
+ </div>
111
+ )}
112
+
113
+ <a
114
+ href={`${
115
+ supportedChains.find(
116
+ (chain) => chain.chainId === transaction.sourceChainId,
117
+ )?.transactionExplorer
118
+ }/${transaction.hash}`}
119
+ target="_blank"
120
+ rel="noreferrer"
121
+ className="ml-2 no-underline"
122
+ aria-label="Show the transaction in the chain explorer"
123
+ >
124
+ <div className="w-3.5 h-3.5 text-[rgba(255,255,255,0.6)]">
125
+ <ArrowUpRightIcon />
126
+ </div>
127
+ </a>
128
+ </div>
129
+ </div>
130
+ <div className="flex flex-wrap gap-8">
131
+ <div className="flex flex-grow items-center">
132
+ <div className="flex items-center">
133
+ <img
134
+ src={sourceChainData?.image}
135
+ alt={`${sourceChainData?.name} logo`}
136
+ className="w-5 h-5 mr-1"
137
+ />
138
+ <div className="text-xs text-[rgba(255,255,255,0.6)]">
139
+ {sourceChainData?.displayName}
140
+ </div>
141
+ </div>
142
+
143
+ <div className="w-3.5 h-3.5 text-[rgba(255,255,255,0.6)] my-0 mx-1.5">
144
+ <ArrowRightIcon />
145
+ </div>
146
+
147
+ <div className="flex items-center">
148
+ <img
149
+ src={targetChainData?.image}
150
+ alt={`${targetChainData?.name} logo`}
151
+ className="w-5 h-5 mr-1"
152
+ />
153
+ <div className="text-xs text-[rgba(255,255,255,0.6)]">
154
+ {targetChainData?.displayName}
155
+ </div>
156
+ </div>
157
+ </div>
158
+ <div className="flex items-center mb-2 last:mb-0">
159
+ <div className="flex flex-1 items-center flex-wrap text-xs text-[rgba(255,255,255,0.6)]">
160
+ <div className="flex items-center">
161
+ {Number(transferredAmount) < 0.0001
162
+ ? "<0.0001"
163
+ : transferredAmount}
164
+ <div className="text-xs ml-1 text-rgba(255,255,255,0.6)">
165
+ {tokenData?.symbol}
166
+ </div>
167
+ {tokenData?.image ? (
168
+ <img
169
+ src={tokenData?.image}
170
+ alt={tokenData?.name || ""}
171
+ className="w-5 h-5 ml-1"
172
+ />
173
+ ) : (
174
+ <div className="flex items-center justify-center w-5 h-5 text-[10px] ml-1 rounded-full bg-[#272e40] text-[#e0e7fa]">
175
+ {tokenData?.symbol.substring(0, 1)}
176
+ </div>
177
+ )}
178
+ </div>
179
+ {transaction.tokenOutAddress && (
180
+ <>
181
+ <div className="w-3.5 h-3.5 text-[rgba(255,255,255,0.6)] my-0 mx-1.5">
182
+ <ArrowRightIcon />
183
+ </div>
184
+ <div className="flex items-center">
185
+ {Number(receivedAmount) < 0.0001 ? "<0.0001" : receivedAmount}
186
+ <div className="text-xs ml-1 text-[rgba(255,255,255,0.6)]">
187
+ {tokenOutData?.symbol}
188
+ </div>
189
+
190
+ {tokenOutData?.image ? (
191
+ <img
192
+ src={tokenOutData?.image}
193
+ alt={tokenOutData?.name || ""}
194
+ className="w-5 h-5 ml-1"
195
+ />
196
+ ) : (
197
+ <div className="flex items-center justify-center w-5 h-5 text-[10px] ml-1 rounded-full bg-[#272e40] text-[#e0e7fa]">
198
+ {tokenData?.symbol.substring(0, 1)}
199
+ </div>
200
+ )}
201
+ </div>
202
+ </>
203
+ )}
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ );
209
+ };
@@ -0,0 +1,165 @@
1
+ import React, { MouseEvent, FC, useState, useMemo } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { SearchIcon } from "../icons";
4
+ import { Token, TokenBalances } from "@src/models";
5
+ import { weiToHumanReadable } from "@src/utils";
6
+ import { Skeleton } from "@src/components";
7
+
8
+ interface Props {
9
+ onCloseClick: () => void;
10
+ tokens: Token[] | undefined;
11
+ selectedToken: Token | undefined;
12
+ setSelectedToken: (token: Token) => void;
13
+ signer: string;
14
+ balances: TokenBalances | undefined;
15
+ }
16
+
17
+ export const TokenPicker: FC<Props> = ({
18
+ onCloseClick,
19
+ tokens,
20
+ selectedToken,
21
+ setSelectedToken,
22
+ signer,
23
+ balances,
24
+ }) => {
25
+ const [searchValue, setSearchValue] = useState("");
26
+
27
+ const modalRoot = document.querySelector(".xswap");
28
+
29
+ const onBackdropClick = (e: MouseEvent<HTMLDivElement>) => {
30
+ e.stopPropagation();
31
+ e.nativeEvent.stopImmediatePropagation();
32
+ if (e.target === e.currentTarget) {
33
+ onCloseClick();
34
+ }
35
+ };
36
+
37
+ const filteredTokens = useMemo(() => {
38
+ const isMatch = (value: string, searchValue: string) =>
39
+ value.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
40
+ return tokens?.filter(
41
+ (token) =>
42
+ isMatch(token.symbol, searchValue) ||
43
+ isMatch(token.name, searchValue) ||
44
+ token.address === searchValue.toLowerCase(),
45
+ );
46
+ }, [searchValue, tokens]);
47
+
48
+ return modalRoot
49
+ ? createPortal(
50
+ <div
51
+ onClick={onBackdropClick}
52
+ className="box-border fixed h-full w-full z-[999] top-0 left-0 bg-[rgba(0,0,0,0.8)] flex items-center justify-center p-5"
53
+ >
54
+ <div className="relative bg-black rounded-3xl p-6 max-h-[70%] flex flex-col text-base text-white w-[452px] h-[70%] border border-solid border-[rgba(255,255,255,0.2)]">
55
+ <p className="text-base mb-4">Pick a token</p>
56
+ <div className="flex items-center border border-solid border-[rgba(255,255,255,0.1)] rounded-lg py-0 px-3 gap-2 bg-[rgba(15,15,15,1)]">
57
+ <div className="w-6 h-6">
58
+ <SearchIcon />
59
+ </div>
60
+ <input
61
+ placeholder="Search name or paste address"
62
+ value={searchValue}
63
+ onChange={(e) => setSearchValue(e.target.value)}
64
+ className="relative h-[41px] leading-[41px] font-normal z-[1] w-full text-white bg-transparent border-none outline-none placeholder:text-sm"
65
+ />
66
+ </div>
67
+ <div className="my-4 mx-0 flex flex-wrap gap-3">
68
+ {tokens
69
+ ?.filter((token) => token?.quickPick)
70
+ .map((token) => (
71
+ <div
72
+ key={token.address}
73
+ className={`flex gap-2 py-1 px-2 items-center bg-[rgb(15,15,15)] rounded-2xl border border-solid border-[rgba(255,255,255,0.1)] hover:bg-[rgb(25,25,25)] hover:cursor-pointer ${
74
+ token.address === selectedToken?.address
75
+ ? "bg-[rgb(35, 35, 35)]"
76
+ : ""
77
+ }`}
78
+ onClick={() => {
79
+ setSelectedToken(token);
80
+ onCloseClick();
81
+ }}
82
+ >
83
+ <img src={token.image} alt={token.name} className="w-5" />
84
+ <div className="text-sm">{token.symbol}</div>
85
+ </div>
86
+ ))}
87
+ </div>
88
+ <div className="h-[2px] my-0 mx-[-24px] w-[100%+48px] px-12 bg-[rgba(255,255,255,0.1)]" />
89
+ <div className="overflow-y-scroll h-full mx-[-24px] w-[100%+48px] flex flex-col">
90
+ {filteredTokens && filteredTokens.length > 0 ? (
91
+ filteredTokens
92
+ .sort((a, b) =>
93
+ balances &&
94
+ balances[a.address] &&
95
+ balances[b.address] &&
96
+ balances[a.address]?.lte(balances[b.address] || "0")
97
+ ? 1
98
+ : -1,
99
+ )
100
+ .map((token, index) => (
101
+ <div
102
+ key={`${index}_${token.address}`}
103
+ className={`flex gap-3 py-2 px-[24px] w-full items-center hover:bg-[rgb(25,25,25)] hover:cursor-pointer ${
104
+ token.address === selectedToken?.address
105
+ ? "bg-[rgb(35,35,35)]"
106
+ : ""
107
+ }`}
108
+ onClick={() => {
109
+ setSelectedToken(token);
110
+ onCloseClick();
111
+ }}
112
+ >
113
+ {token.image ? (
114
+ <img
115
+ src={token.image}
116
+ alt={token.name}
117
+ className="token-picker__all__logo"
118
+ />
119
+ ) : (
120
+ <div className="flex items-center justify-center w-9 h-9 text-[15px] ml-1 rounded-full bg-[#272e40] text-[#e0e7fa]">
121
+ {token?.symbol.substring(0, 1)}
122
+ </div>
123
+ )}
124
+ <div className="flex justify-between items-center gap-1 overflow-hidden grow">
125
+ <div className="flex flex-col leading-[16px]">
126
+ <div className="overflow-hidden whitespace-nowrap text-ellipsis text-[15px]">
127
+ {token.name}
128
+ </div>
129
+ <div className="text-[#888] text-[11px]">
130
+ {token.symbol}
131
+ </div>
132
+ </div>
133
+ {signer && (
134
+ <>
135
+ {balances &&
136
+ balances[token.address] &&
137
+ token.decimals ? (
138
+ <div className="text-xs">
139
+ {weiToHumanReadable({
140
+ amount:
141
+ balances[token.address]?.toString() || "0",
142
+ decimals: token.decimals,
143
+ precisionFractionalPlaces: 4,
144
+ })}
145
+ </div>
146
+ ) : (
147
+ <Skeleton w="w-12" h="h-3" />
148
+ )}
149
+ </>
150
+ )}
151
+ </div>
152
+ </div>
153
+ ))
154
+ ) : (
155
+ <div className="mt-4 flex justify-center">
156
+ <p className="text-sm text-white">No tokens found.</p>
157
+ </div>
158
+ )}
159
+ </div>
160
+ </div>
161
+ </div>,
162
+ modalRoot,
163
+ )
164
+ : null;
165
+ };
@@ -0,0 +1,100 @@
1
+ import "@src/global.css";
2
+ import { Form } from "./Form";
3
+ import { CloseIcon } from "@src/components/icons/CloseIcon";
4
+ import React from "react";
5
+ import ReactDOM from "react-dom";
6
+
7
+ const TxConfigForm = ({ isOpen, onClose, content }) => {
8
+ const onBackdropClick = (e) => {
9
+ e.stopPropagation();
10
+ e.nativeEvent.stopImmediatePropagation();
11
+ if (e.target === e.currentTarget) {
12
+ onClose();
13
+ }
14
+ };
15
+
16
+ if (!isOpen) return null;
17
+
18
+ return (
19
+ <div
20
+ className="top-0 left-0 right-0 bottom-0 bg-[rgba(0,0,0,0.8)] fixed flex items-center justify-center z-10"
21
+ onClick={onBackdropClick}
22
+ >
23
+ <div className="relative bg-black rounded-3xl w-lg overflow-auto text-white border-2 border-solid border-[rgba(255,255,255,0.1)]">
24
+ {content}
25
+ <div
26
+ className="absolute top-3.5 right-3 cursor-pointer text-white"
27
+ onClick={onClose}
28
+ >
29
+ <CloseIcon />
30
+ </div>
31
+ </div>
32
+ </div>
33
+ );
34
+ };
35
+
36
+ class TxConfigModalWrapper extends React.Component {
37
+ constructor(props) {
38
+ super(props);
39
+ this.state = { isOpen: false, content: "No wallet connected" };
40
+ this.closeModal = this.closeModal.bind(this);
41
+ }
42
+
43
+ openModal(content) {
44
+ this.setState({ isOpen: true, content });
45
+ }
46
+
47
+ closeModal() {
48
+ this.setState({ isOpen: false });
49
+ }
50
+
51
+ render() {
52
+ return this.state.isOpen ? (
53
+ <TxConfigForm
54
+ isOpen={this.state.isOpen}
55
+ onClose={this.closeModal}
56
+ content={this.state.content}
57
+ />
58
+ ) : null;
59
+ }
60
+ }
61
+
62
+ // skip on server-side integrations
63
+ let modalWrapperInstance;
64
+ if (typeof document !== "undefined") {
65
+ // Create a div element that will host the modal, and append it to the body
66
+ const modalDiv = document.createElement("div");
67
+ modalDiv.classList.add("xswap");
68
+ document.body.appendChild(modalDiv);
69
+
70
+ // // Render the ModalWrapper component into the modalDiv
71
+ modalWrapperInstance = ReactDOM.render(<TxConfigModalWrapper />, modalDiv);
72
+ }
73
+
74
+ export const openTxConfigForm = async ({
75
+ dstChainId,
76
+ dstTokenAddr,
77
+ customContractCalls,
78
+ desc,
79
+ supportedChains,
80
+ }) => {
81
+ try {
82
+ return await new Promise((resolve) => {
83
+ modalWrapperInstance.openModal(
84
+ <Form
85
+ dstChainId={dstChainId}
86
+ dstTokenAddr={dstTokenAddr}
87
+ customContractCalls={customContractCalls}
88
+ desc={desc}
89
+ supportedChains={supportedChains}
90
+ onSubmit={(route) => {
91
+ resolve(route);
92
+ modalWrapperInstance.closeModal();
93
+ }}
94
+ />,
95
+ );
96
+ });
97
+ } catch (err) {
98
+ throw new Error(`User input failed, ${err}`);
99
+ }
100
+ };
@@ -0,0 +1,34 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ .xswap {
6
+ /* Works on Firefox */
7
+ * {
8
+ scrollbar-width: thin;
9
+ scrollbar-color: #252525 #000;
10
+ }
11
+
12
+ /* Works on Chrome, Edge, and Safari */
13
+ *::-webkit-scrollbar {
14
+ width: 4px;
15
+ }
16
+
17
+ *::-webkit-scrollbar-track {
18
+ background: #000;
19
+ border-radius: 4px;
20
+ }
21
+
22
+ *::-webkit-scrollbar-thumb {
23
+ background-color: #252525;
24
+ border-radius: 4px;
25
+ }
26
+
27
+ button {
28
+ @apply text-white border-none rounded-2xl text-xl py-4 cursor-pointer w-full bg-gradient-to-r from-[#3681c6] to-[#2b4a9d] disabled:opacity-25;
29
+ }
30
+
31
+ .globalBorder {
32
+ border: 1px solid rgba(255, 255, 255, 0.1);
33
+ }
34
+ }
@@ -0,0 +1,6 @@
1
+ /// <reference types="react-scripts" />
2
+
3
+ interface Window {
4
+ // eslint-disable-next-line
5
+ ethereum: any;
6
+ }
@@ -0,0 +1,11 @@
1
+ export const ArrowRightIcon = () => {
2
+ return (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ viewBox="0 0 448 512"
6
+ fill="currentColor"
7
+ >
8
+ <path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z" />
9
+ </svg>
10
+ );
11
+ };