@xswap-link/sdk 0.0.13 → 0.1.1
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 +2 -1
- package/.github/workflows/publish.yml +2 -1
- package/.prettierrc +7 -0
- package/CHANGELOG.md +27 -1
- package/README.md +23 -14
- package/dist/index.css +1108 -0
- package/dist/index.d.mts +212 -52
- package/dist/index.d.ts +212 -52
- package/dist/index.js +2184 -85
- package/dist/index.mjs +2148 -76
- package/nodemon.json +5 -0
- package/package.json +20 -3
- package/postcss.config.js +3 -0
- package/src/components/Alert/index.tsx +9 -0
- package/src/components/Skeleton/index.tsx +10 -0
- package/src/components/TxConfigForm/BalanceComponent.tsx +51 -0
- package/src/components/TxConfigForm/ChainListElement.tsx +36 -0
- package/src/components/TxConfigForm/Description.tsx +15 -0
- package/src/components/TxConfigForm/ErrorField.tsx +17 -0
- package/src/components/TxConfigForm/FeesDetails.tsx +105 -0
- package/src/components/TxConfigForm/Form.tsx +250 -0
- package/src/components/TxConfigForm/History.tsx +155 -0
- package/src/components/TxConfigForm/HistoryCard.tsx +209 -0
- package/src/components/TxConfigForm/Settings.tsx +137 -0
- package/src/components/TxConfigForm/Summary.tsx +57 -0
- package/src/components/TxConfigForm/SwapPanel.tsx +191 -0
- package/src/components/TxConfigForm/TokenPicker.tsx +171 -0
- package/src/components/TxConfigForm/TopBar.tsx +53 -0
- package/src/components/TxConfigForm/index.tsx +121 -0
- package/src/components/global.css +34 -0
- package/src/components/icons/ArrowDownIcon.tsx +16 -0
- package/src/components/icons/ArrowRightIcon.tsx +11 -0
- package/src/components/icons/ArrowUpRightIcon.tsx +11 -0
- package/src/components/icons/ChainlinkCCIPIcon.tsx +23 -0
- package/src/components/icons/CheckIcon.tsx +11 -0
- package/src/components/icons/ChevronDownIcon.tsx +15 -0
- package/src/components/icons/ChevronUpIcon.tsx +15 -0
- package/src/components/icons/CircularProgressIcon.tsx +24 -0
- package/src/components/icons/CloseIcon.tsx +15 -0
- package/src/components/icons/CoinsIcon.tsx +17 -0
- package/src/components/icons/DownArrorIcon.tsx +17 -0
- package/src/components/icons/HistoryIcon.tsx +16 -0
- package/src/components/icons/HourGlassIcon.tsx +11 -0
- package/src/components/icons/InfoIcon.tsx +13 -0
- package/src/components/icons/PercentageIcon.tsx +29 -0
- package/src/components/icons/SearchIcon.tsx +15 -0
- package/src/components/icons/SettingsIcon.tsx +16 -0
- package/src/components/icons/TimerIcon.tsx +15 -0
- package/src/components/icons/XMarkIcon.tsx +11 -0
- package/src/components/icons/XSwapBadgeIcon.tsx +23 -0
- package/src/components/icons/index.ts +20 -0
- package/src/components/index.ts +3 -0
- package/src/config/index.ts +1 -0
- package/src/config/wagmiConfig.ts +45 -0
- package/src/constants/index.ts +9 -5
- package/src/contracts/abi/BatchQuery.json +52 -0
- package/src/contracts/abi/index.ts +2 -0
- package/src/contracts/addresses.ts +34 -0
- package/src/contracts/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useDebounce.tsx +21 -0
- package/src/index.ts +6 -0
- package/src/models/Addresses.ts +12 -0
- package/src/models/Route.ts +17 -5
- package/src/models/TokenData.ts +45 -0
- package/src/models/TransactionHistory.ts +48 -0
- package/src/models/XSwapConfig.ts +3 -0
- package/src/models/forms/TxConfigFormData.ts +8 -0
- package/src/models/forms/index.ts +1 -0
- package/src/models/index.ts +6 -6
- package/src/models/integrations/GenerateStakingCallsParams.ts +8 -0
- package/src/models/integrations/index.ts +1 -0
- package/src/models/payloads/GetPricesPayload.ts +4 -0
- package/src/models/payloads/GetRoutePayload.ts +1 -3
- package/src/models/payloads/GetSwapTxPayload.ts +8 -0
- package/src/models/payloads/index.ts +2 -0
- package/src/services/api.ts +46 -64
- package/src/services/index.ts +1 -0
- package/src/services/integrations/customCalls/index.ts +1 -0
- package/src/services/integrations/customCalls/staking.ts +83 -0
- package/src/services/integrations/index.ts +2 -0
- package/src/services/integrations/transactions.ts +30 -0
- package/src/utils/contracts.ts +153 -11
- package/src/utils/index.ts +49 -1
- package/src/utils/numbers.ts +47 -0
- package/src/utils/strings.ts +6 -0
- package/tailwind.config.js +19 -0
- package/test/api.test.ts +1 -1
- package/tsconfig.json +10 -1
- package/xswap.config.ts +18 -0
- package/index.ts +0 -5
- package/src/models/ApiOverrides.ts +0 -3
- package/src/models/Chain.ts +0 -20
- package/src/models/Prices.ts +0 -9
- package/src/models/Token.ts +0 -10
- package/src/models/XSwapFee.ts +0 -4
- package/src/models/XSwapFees.ts +0 -7
- package/src/utils/bigNumbers.ts +0 -7
|
@@ -0,0 +1,155 @@
|
|
|
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 { DELIVERY_TIME, EXPRESS_DELIVERY_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
|
+
|
|
49
|
+
const blockTimestampMs = new Date(entry.source.blockTime).getTime();
|
|
50
|
+
const estimatedDeliveryTimestamp = new BigNumberJS(
|
|
51
|
+
entry.source.valueForInstantCcipRecieve,
|
|
52
|
+
).gt(0)
|
|
53
|
+
? blockTimestampMs + EXPRESS_DELIVERY_TIME
|
|
54
|
+
: blockTimestampMs + DELIVERY_TIME;
|
|
55
|
+
|
|
56
|
+
fetchedTransactions.push({
|
|
57
|
+
hash: entry.source.transactionHash,
|
|
58
|
+
timestamp: blockTimestampMs / 1000,
|
|
59
|
+
sourceChainId: entry.source.blockchainId,
|
|
60
|
+
targetChainId: entry.source.targetBlockchainId,
|
|
61
|
+
amountWei: entry.source.tokenAmount,
|
|
62
|
+
tokenAddress: entry.source.tokenAddress,
|
|
63
|
+
tokenOutAddress:
|
|
64
|
+
entry.target?.tokenAddress || entry.source.tokenOutAddress,
|
|
65
|
+
tokenOutAmount:
|
|
66
|
+
entry.target?.tokenAmount || entry.source.estimatedAmountOut,
|
|
67
|
+
estimatedDeliveryTimestamp,
|
|
68
|
+
status,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const txHashToTxFromAPI: Record<string, Transaction> = {};
|
|
73
|
+
for (const transaction of fetchedTransactions) {
|
|
74
|
+
txHashToTxFromAPI[transaction.hash] = transaction;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const localHistory = getLocalHistory();
|
|
78
|
+
const newLocalHistory: Record<string, Transaction[]> = {};
|
|
79
|
+
|
|
80
|
+
for (const [walletAddress, transactions] of Object.entries(
|
|
81
|
+
localHistory,
|
|
82
|
+
)) {
|
|
83
|
+
if (walletAddress.toLowerCase() !== signer.toLowerCase()) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const newLocalHistoryForWallet: Transaction[] = [];
|
|
87
|
+
|
|
88
|
+
for (const transaction of transactions) {
|
|
89
|
+
if (!txHashToTxFromAPI[transaction.hash]) {
|
|
90
|
+
// localStorage has fresher data.
|
|
91
|
+
fetchedTransactions.push(transaction);
|
|
92
|
+
newLocalHistoryForWallet.push(transaction);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
txHashToTxFromAPI[transaction.hash] &&
|
|
98
|
+
transaction.status === "DONE" &&
|
|
99
|
+
txHashToTxFromAPI[transaction.hash]?.status === "IN_PROGRESS"
|
|
100
|
+
) {
|
|
101
|
+
// If we know on the frontend that the tx is done we should update backend result
|
|
102
|
+
txHashToTxFromAPI[transaction.hash]!.status = "DONE";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
txHashToTxFromAPI[transaction.hash]?.status === "DONE" ||
|
|
107
|
+
txHashToTxFromAPI[transaction.hash]?.status === "REVERTED"
|
|
108
|
+
) {
|
|
109
|
+
// API has the same or fresher data.
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
newLocalHistory[walletAddress.toLowerCase()] = newLocalHistoryForWallet;
|
|
115
|
+
}
|
|
116
|
+
setLocalHistory(newLocalHistory);
|
|
117
|
+
setMappedHistory(
|
|
118
|
+
fetchedTransactions.sort((a, b) => b.timestamp - a.timestamp),
|
|
119
|
+
);
|
|
120
|
+
setHistoryLoadedOnce(true);
|
|
121
|
+
}
|
|
122
|
+
}, [signer, getHistory]);
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!historyLoadedOnce) fetchHistory();
|
|
126
|
+
const timer = setInterval(() => {
|
|
127
|
+
fetchHistory();
|
|
128
|
+
}, 60000);
|
|
129
|
+
return () => clearInterval(timer);
|
|
130
|
+
}, [signer, fetchHistory, getHistory, historyLoadedOnce]);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className="flex items-center justify-center w-full">
|
|
134
|
+
<div className="w-full h-[50vh] overflow-scroll overflow-x-hidden">
|
|
135
|
+
{mappedHistory?.length === 0 && historyLoadedOnce && (
|
|
136
|
+
<div className="w-full text-center py-4 px-0 text-[rgb(158,158,158)]">
|
|
137
|
+
Your history is empty...
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
{!mappedHistory && !historyLoadedOnce && (
|
|
141
|
+
<div className="flex w-full h-full items-center justify-center">
|
|
142
|
+
<CircularProgressIcon />
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
{mappedHistory?.map((transaction, index) => (
|
|
146
|
+
<HistoryCard
|
|
147
|
+
key={index}
|
|
148
|
+
transaction={transaction}
|
|
149
|
+
supportedChains={supportedChains}
|
|
150
|
+
/>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
@@ -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,137 @@
|
|
|
1
|
+
import { CloseIcon, InfoIcon, PercentageIcon } from "../icons";
|
|
2
|
+
import { SLIPPAGE_PRESETS } from "@src/constants";
|
|
3
|
+
import { FC, useState, useEffect } from "react";
|
|
4
|
+
import { NUMBER_INPUT_REGEX } from "@src/constants";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
slippage: string;
|
|
8
|
+
setSlippage: React.Dispatch<React.SetStateAction<string>>;
|
|
9
|
+
setSettingsShown: React.Dispatch<React.SetStateAction<boolean>>;
|
|
10
|
+
expressChecked: boolean;
|
|
11
|
+
setExpressChecked: React.Dispatch<React.SetStateAction<boolean>>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const Settings: FC<Props> = ({
|
|
15
|
+
setSlippage,
|
|
16
|
+
setSettingsShown,
|
|
17
|
+
slippage,
|
|
18
|
+
expressChecked,
|
|
19
|
+
setExpressChecked,
|
|
20
|
+
}) => {
|
|
21
|
+
const [slippageActivePresetIndex, setSlippageActivePresetIndex] = useState(1);
|
|
22
|
+
const [usingSlippageInput, setUsingSlippageInput] = useState(false);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!usingSlippageInput) {
|
|
26
|
+
setSlippage(
|
|
27
|
+
SLIPPAGE_PRESETS[slippageActivePresetIndex]?.toString() || "1.5",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}, [slippageActivePresetIndex, usingSlippageInput]);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="absolute w-[310px] right-0 top-[46px] z-10 bg-black border border-solid border-[rgba(54,129,198,1)] p-4 rounded-xl">
|
|
34
|
+
<div className="flex flex-col gap-4 justify-between">
|
|
35
|
+
<div className="flex justify-between">
|
|
36
|
+
<div className="text-base">Settings</div>
|
|
37
|
+
<div
|
|
38
|
+
className="cursor-pointer"
|
|
39
|
+
onClick={() => setSettingsShown(false)}
|
|
40
|
+
>
|
|
41
|
+
<CloseIcon />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div>
|
|
45
|
+
<div className="flex gap-2 items-center">
|
|
46
|
+
<div className="text-sm text-[rgba(255,255,255,0.6)]">
|
|
47
|
+
Express delivery
|
|
48
|
+
</div>
|
|
49
|
+
<div>
|
|
50
|
+
<span
|
|
51
|
+
className="inline-flex w-14 h-9 p-3 relative align-middle box-border overflow-hidden cursor-pointer"
|
|
52
|
+
onClick={() => {
|
|
53
|
+
setExpressChecked((x) => !x);
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
<span className="h-full, w-full rounded-lg bg-[rgba(255,255,255,0.3)]"></span>
|
|
57
|
+
<span
|
|
58
|
+
className={`transition-all w-5 h-5 rounded-full absolute translate-y-[-4px]
|
|
59
|
+
${expressChecked ? "translate-x-[12px] bg-x_blue" : "bg-white"} `}
|
|
60
|
+
></span>
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex gap-4">
|
|
65
|
+
<div className="min-w-[26px] min-h-[26px] text-[#ffa726]">
|
|
66
|
+
<InfoIcon />
|
|
67
|
+
</div>
|
|
68
|
+
<div className="text-xs text-left">
|
|
69
|
+
Express delivery is a special feature of XSwap that reduces
|
|
70
|
+
transaction time across chains to around 30 seconds. It is
|
|
71
|
+
currently available for swaps below a value of $ 1000 USD.
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div className="flex flex-col gap-2 ">
|
|
77
|
+
<div className=" text-[rgba(255,255,255,0.6)] text-sm">Slippage</div>
|
|
78
|
+
<div className="flex gap-2">
|
|
79
|
+
<div className="flex items-center bg-[rgba(15,15,15,1)] p-1 globalBorder rounded-xl">
|
|
80
|
+
{SLIPPAGE_PRESETS.map((preset, index) => (
|
|
81
|
+
<div
|
|
82
|
+
key={index}
|
|
83
|
+
className={`transition-all cursor-pointer block rounded-lg p-2 border-none text-sm leading-[10px] ${
|
|
84
|
+
index === slippageActivePresetIndex && !usingSlippageInput
|
|
85
|
+
? "text-white"
|
|
86
|
+
: "text-[rgba(255,255,255,0.2)]"
|
|
87
|
+
} ${
|
|
88
|
+
index === slippageActivePresetIndex && !usingSlippageInput
|
|
89
|
+
? "bg-gradient-to-r from-[#3681c6] to-[#2b4a9d]"
|
|
90
|
+
: ""
|
|
91
|
+
}`}
|
|
92
|
+
onClick={() => {
|
|
93
|
+
setUsingSlippageInput(false);
|
|
94
|
+
setSlippageActivePresetIndex(index);
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{`${preset.toFixed(1)}%`}
|
|
98
|
+
</div>
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
<div className="bg-[rgba(39,39,39,1)] p-2 border border-solid border-[rgba(82,82,82,1)] rounded-xl text-white flex items-center">
|
|
102
|
+
<input
|
|
103
|
+
className="text-white border-none bg-[rgba(39,39,39,1)] w-10 placeholder:text-[rgba(255,255,255,0.2)] focus:outline-none text-sm leading-none"
|
|
104
|
+
placeholder="1.5"
|
|
105
|
+
value={usingSlippageInput ? slippage : ""}
|
|
106
|
+
onChange={(e) => {
|
|
107
|
+
if (e.target.value.length > 0) {
|
|
108
|
+
if (NUMBER_INPUT_REGEX.test(e.target.value)) {
|
|
109
|
+
setUsingSlippageInput(true);
|
|
110
|
+
setSlippage(e.target.value.replace(/,/g, "."));
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
setUsingSlippageInput(false);
|
|
114
|
+
setSlippageActivePresetIndex(1);
|
|
115
|
+
}
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
<PercentageIcon />
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
<div className="flex gap-4">
|
|
122
|
+
<div className="min-w-[26px] min-h-[26px] text-[#ffa726]">
|
|
123
|
+
<InfoIcon />
|
|
124
|
+
</div>
|
|
125
|
+
<div className="text-xs text-left">
|
|
126
|
+
Slippage is the price variation you are willing to accept in the
|
|
127
|
+
event that the price of the trade changes while it is processing.
|
|
128
|
+
<br /> <br />
|
|
129
|
+
If the trade fails due to too-low slippage, you will receive USDC
|
|
130
|
+
on the destination chain.
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { weiToHumanReadable } from "@src/utils";
|
|
3
|
+
import { Chain, Route, Token } from "@src/models";
|
|
4
|
+
import { ArrowDownIcon } from "../icons";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
isGettingRoute: boolean;
|
|
8
|
+
amount: string;
|
|
9
|
+
route: Route | undefined;
|
|
10
|
+
dstToken: Token | undefined;
|
|
11
|
+
dstChain: Chain | undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const Summary: FC<Props> = ({
|
|
15
|
+
isGettingRoute,
|
|
16
|
+
amount,
|
|
17
|
+
route,
|
|
18
|
+
dstToken,
|
|
19
|
+
dstChain,
|
|
20
|
+
}) => {
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex justify-between globalBorder rounded-lg bg-[rgba(15,15,15,1)] p-4 relative">
|
|
23
|
+
<div className="flex flex-col gap-2">
|
|
24
|
+
<p className="text-[rgba(255,255,255,0.6)] text-sm">You receive</p>
|
|
25
|
+
{isGettingRoute && amount.length > 0 ? (
|
|
26
|
+
<div className="bg-current rounded animate-pulse w-[190px] h-7" />
|
|
27
|
+
) : (
|
|
28
|
+
<div className="flex gap-2 items-center text-white text-2xl">
|
|
29
|
+
{weiToHumanReadable({
|
|
30
|
+
amount: route?.estAmountOut || "0",
|
|
31
|
+
decimals: dstToken?.decimals || 18,
|
|
32
|
+
precisionFractionalPlaces: 5,
|
|
33
|
+
})}
|
|
34
|
+
<div className="w-6 h-6">
|
|
35
|
+
<img src={dstToken?.image} alt={dstToken?.name} />
|
|
36
|
+
</div>
|
|
37
|
+
<p>{dstToken?.symbol}</p>
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
<div className="flex flex-col gap-1 text-sm">
|
|
42
|
+
<p className="text-[rgba(255,255,255,0.6)] text-right">on chain:</p>
|
|
43
|
+
<div className="flex gap-1 text-white">
|
|
44
|
+
<div className="w-4 h-4">
|
|
45
|
+
<img src={dstChain?.image} alt={dstChain?.name} />
|
|
46
|
+
</div>
|
|
47
|
+
<div>{dstChain?.displayName}</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div className="absolute right-[50%] top-0 translate-x-1/2 translate-y-[-60%] globalBorder rounded-xl p-[5px] bg-[rgba(15,15,15,1)]">
|
|
51
|
+
<div className=" flex items-center justify-center rounded-lg w-8 h-8 bg-gradient-to-l from-[rgba(54,129,198,1)] to-[rgba(43,74,157,1)] ">
|
|
52
|
+
<ArrowDownIcon />
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
};
|