@xswap-link/sdk 0.1.0 → 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 +1 -0
- package/.github/workflows/publish.yml +1 -0
- package/CHANGELOG.md +15 -0
- package/dist/index.css +125 -29
- package/dist/index.d.mts +21 -18
- package/dist/index.d.ts +21 -18
- package/dist/index.js +1140 -32916
- package/dist/index.mjs +1112 -32913
- package/nodemon.json +5 -0
- package/package.json +17 -6
- 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 +130 -447
- package/src/components/TxConfigForm/History.tsx +28 -32
- 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 +8 -2
- package/src/components/TxConfigForm/TopBar.tsx +53 -0
- package/src/components/TxConfigForm/index.tsx +121 -0
- package/src/components/icons/ArrowDownIcon.tsx +16 -0
- package/src/components/icons/ChainlinkCCIPIcon.tsx +23 -0
- package/src/components/icons/ChevronDownIcon.tsx +2 -2
- package/src/components/icons/ChevronUpIcon.tsx +2 -2
- package/src/components/icons/CloseIcon.tsx +2 -2
- package/src/components/icons/CoinsIcon.tsx +8 -6
- package/src/components/icons/HistoryIcon.tsx +8 -5
- package/src/components/icons/InfoIcon.tsx +13 -0
- package/src/components/icons/SettingsIcon.tsx +16 -0
- package/src/components/icons/TimerIcon.tsx +15 -0
- package/src/components/icons/XSwapBadgeIcon.tsx +23 -0
- package/src/components/icons/index.ts +6 -0
- package/src/config/index.ts +1 -0
- package/src/config/wagmiConfig.ts +45 -0
- package/src/constants/index.ts +5 -2
- package/tailwind.config.js +7 -2
- package/src/components/TxConfigForm/index.jsx +0 -100
- package/src/components/global.d.ts +0 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Transaction, TransactionStatus, Chain } from "@src/models";
|
|
2
2
|
import React, { FC, useCallback, useEffect, useState } from "react";
|
|
3
3
|
import { getHistory } from "@src/services";
|
|
4
|
-
import {
|
|
4
|
+
import { DELIVERY_TIME, EXPRESS_DELIVERY_TIME } from "@src/constants";
|
|
5
5
|
import BigNumberJS from "bignumber.js";
|
|
6
6
|
import { HistoryCard } from "./HistoryCard";
|
|
7
7
|
import { CircularProgressIcon } from "../icons";
|
|
@@ -45,23 +45,17 @@ export const History: FC<Props> = ({ signer, supportedChains }) => {
|
|
|
45
45
|
} else {
|
|
46
46
|
status = "IN_PROGRESS";
|
|
47
47
|
}
|
|
48
|
-
const timestampMs = new Date(entry.source?.blockTime).getTime();
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (blockchainEstimate) {
|
|
58
|
-
estimatedDeliveryTimestamp = blockchainEstimate;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
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;
|
|
61
55
|
|
|
62
56
|
fetchedTransactions.push({
|
|
63
57
|
hash: entry.source.transactionHash,
|
|
64
|
-
timestamp:
|
|
58
|
+
timestamp: blockTimestampMs / 1000,
|
|
65
59
|
sourceChainId: entry.source.blockchainId,
|
|
66
60
|
targetChainId: entry.source.targetBlockchainId,
|
|
67
61
|
amountWei: entry.source.tokenAmount,
|
|
@@ -136,24 +130,26 @@ export const History: FC<Props> = ({ signer, supportedChains }) => {
|
|
|
136
130
|
}, [signer, fetchHistory, getHistory, historyLoadedOnce]);
|
|
137
131
|
|
|
138
132
|
return (
|
|
139
|
-
<div className="
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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>
|
|
157
153
|
</div>
|
|
158
154
|
);
|
|
159
155
|
};
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { FC, useState, useEffect, useRef, useMemo } from "react";
|
|
2
|
+
import { NUMBER_INPUT_REGEX } from "@src/constants";
|
|
3
|
+
import { DownArrowIcon } from "../icons";
|
|
4
|
+
import { Token, Chain, TokenBalances, TokenPrices } from "@src/models";
|
|
5
|
+
import { TokenPicker } from "./TokenPicker";
|
|
6
|
+
import { weiToHumanReadable } from "@src/utils";
|
|
7
|
+
import { Alert } from "../Alert";
|
|
8
|
+
import { ChainListElement } from "./ChainListElement";
|
|
9
|
+
import { BalanceComponent } from "./BalanceComponent";
|
|
10
|
+
import BigNumber from "bignumber.js";
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
amount: string;
|
|
14
|
+
setAmount: React.Dispatch<React.SetStateAction<string>>;
|
|
15
|
+
srcToken: Token | undefined;
|
|
16
|
+
setSrcToken: React.Dispatch<React.SetStateAction<Token | undefined>>;
|
|
17
|
+
setSrcChain: React.Dispatch<React.SetStateAction<Chain | undefined>>;
|
|
18
|
+
srcChain: Chain | undefined;
|
|
19
|
+
signer: string | undefined;
|
|
20
|
+
balances: TokenBalances | undefined;
|
|
21
|
+
prices: TokenPrices | undefined;
|
|
22
|
+
supportedChains: Chain[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const SwapPanel: FC<Props> = ({
|
|
26
|
+
amount,
|
|
27
|
+
setAmount,
|
|
28
|
+
srcToken,
|
|
29
|
+
setSrcToken,
|
|
30
|
+
srcChain,
|
|
31
|
+
setSrcChain,
|
|
32
|
+
signer,
|
|
33
|
+
balances,
|
|
34
|
+
prices,
|
|
35
|
+
supportedChains,
|
|
36
|
+
}) => {
|
|
37
|
+
const [chainListShown, setChainListShown] = useState(false);
|
|
38
|
+
const [tokenListShown, setTokenListShown] = useState(false);
|
|
39
|
+
const listRef = useRef(null);
|
|
40
|
+
const buttonRef = useRef(null);
|
|
41
|
+
|
|
42
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
43
|
+
if (listRef.current && buttonRef.current) {
|
|
44
|
+
const listElement = listRef.current as HTMLElement;
|
|
45
|
+
const buttonElement = buttonRef.current as HTMLElement;
|
|
46
|
+
if (
|
|
47
|
+
!listElement.contains(event.target as Node) &&
|
|
48
|
+
!buttonElement.contains(event.target as Node)
|
|
49
|
+
)
|
|
50
|
+
setChainListShown(false);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleMaxClick = () => {
|
|
55
|
+
if (
|
|
56
|
+
balances &&
|
|
57
|
+
srcToken &&
|
|
58
|
+
balances[srcToken.address] &&
|
|
59
|
+
balances[srcToken.address]?.toString() !== "0"
|
|
60
|
+
)
|
|
61
|
+
setAmount(
|
|
62
|
+
weiToHumanReadable({
|
|
63
|
+
amount: balances[srcToken.address]?.toString() || "0",
|
|
64
|
+
decimals: srcToken.decimals,
|
|
65
|
+
precisionFractionalPlaces: 4,
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (chainListShown) {
|
|
72
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
73
|
+
return () =>
|
|
74
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
75
|
+
}
|
|
76
|
+
}, [chainListShown]);
|
|
77
|
+
|
|
78
|
+
const chainListOptions = useMemo(
|
|
79
|
+
() =>
|
|
80
|
+
supportedChains.filter((chain) => chain.chainId !== srcChain?.chainId),
|
|
81
|
+
[supportedChains, srcChain],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="flex flex-col items-start globalBorder rounded-lg bg-[rgba(15,15,15,1)] p-4">
|
|
86
|
+
<div className="w-full flex gap-4 items-start justify-between mb-4">
|
|
87
|
+
<div className="flex flex-col gap-2 overflow-hidden w-1/2">
|
|
88
|
+
<p className="text-white opacity-60 text-sm font-medium">You pay</p>
|
|
89
|
+
<input
|
|
90
|
+
className="p-0 border-none outline-none bg-transparent text-2xl overflow-ellipsis"
|
|
91
|
+
value={amount}
|
|
92
|
+
onChange={(e) => {
|
|
93
|
+
if (e.target.value === ".") return;
|
|
94
|
+
if (NUMBER_INPUT_REGEX.test(e.target.value)) {
|
|
95
|
+
setAmount(e.target.value.replace(/,/g, "."));
|
|
96
|
+
}
|
|
97
|
+
}}
|
|
98
|
+
inputMode="decimal"
|
|
99
|
+
spellCheck="false"
|
|
100
|
+
autoComplete="off"
|
|
101
|
+
autoCorrect="off"
|
|
102
|
+
placeholder="0"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
<div className="flex flex-col relative items-end gap-2.5 w-1/2 text-xs">
|
|
106
|
+
<div
|
|
107
|
+
ref={buttonRef}
|
|
108
|
+
className="bg-black globalBorder rounded-2xl whitespace-nowrap flex items-center p-2 cursor-pointer gap-2"
|
|
109
|
+
onClick={() => setChainListShown((prev) => !prev)}
|
|
110
|
+
>
|
|
111
|
+
<img
|
|
112
|
+
width="16"
|
|
113
|
+
height="16"
|
|
114
|
+
src={srcChain?.image}
|
|
115
|
+
alt={srcChain?.name}
|
|
116
|
+
/>
|
|
117
|
+
<div>{srcChain?.displayName}</div>
|
|
118
|
+
<DownArrowIcon />
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{chainListShown && (
|
|
122
|
+
<ul
|
|
123
|
+
ref={listRef}
|
|
124
|
+
className="bg-black globalBorder rounded-lg whitespace-nowrap z-1 right-0 top-10 px-2 max-w-full max-h-[40vh] overflow-auto absolute text-sm z-20"
|
|
125
|
+
>
|
|
126
|
+
{chainListOptions.map((chain, index) => (
|
|
127
|
+
<ChainListElement
|
|
128
|
+
index={index}
|
|
129
|
+
length={supportedChains.length}
|
|
130
|
+
setSrcChain={setSrcChain}
|
|
131
|
+
setChainListShown={setChainListShown}
|
|
132
|
+
chain={chain}
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
</ul>
|
|
136
|
+
)}
|
|
137
|
+
<div
|
|
138
|
+
className="flex gap-2 items-center p-2 max-w-full cursor-pointer bg-black text-white globalBorder rounded-3xl whitespace-nowrap"
|
|
139
|
+
onClick={() => {
|
|
140
|
+
setTokenListShown((state) => !state);
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
<img
|
|
144
|
+
height={16}
|
|
145
|
+
width={16}
|
|
146
|
+
src={srcToken?.image}
|
|
147
|
+
alt={srcToken?.name}
|
|
148
|
+
/>
|
|
149
|
+
<div>{srcToken?.symbol}</div>
|
|
150
|
+
<DownArrowIcon />
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{tokenListShown && (
|
|
154
|
+
<TokenPicker
|
|
155
|
+
onCloseClick={() => setTokenListShown(false)}
|
|
156
|
+
tokens={srcChain?.tokens}
|
|
157
|
+
signer={signer}
|
|
158
|
+
selectedToken={srcToken}
|
|
159
|
+
setSelectedToken={(token) => setSrcToken(token)}
|
|
160
|
+
balances={balances}
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<div className="flex justify-between items-center w-full text-sm text-[rgba(255,255,255,0.6)] relative">
|
|
167
|
+
{srcToken ? (
|
|
168
|
+
prices && prices[srcToken.address] ? (
|
|
169
|
+
<div>
|
|
170
|
+
{`$${(Number(amount) * Number(prices[srcToken.address])).toFixed(
|
|
171
|
+
2,
|
|
172
|
+
)}`}
|
|
173
|
+
</div>
|
|
174
|
+
) : (
|
|
175
|
+
<Alert desc="Unknown price" />
|
|
176
|
+
)
|
|
177
|
+
) : (
|
|
178
|
+
<>$0.00</>
|
|
179
|
+
)}
|
|
180
|
+
<div
|
|
181
|
+
onClick={handleMaxClick}
|
|
182
|
+
className="flex gap-1 items-center font-medium text-white opacity-60 py-0.5 cursor-pointer"
|
|
183
|
+
>
|
|
184
|
+
{signer && (
|
|
185
|
+
<BalanceComponent balances={balances} srcToken={srcToken} />
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { MouseEvent, FC, useState, useMemo } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
|
-
import { SearchIcon } from "../icons";
|
|
3
|
+
import { CloseIcon, SearchIcon } from "../icons";
|
|
4
4
|
import { Token, TokenBalances } from "@src/models";
|
|
5
5
|
import { weiToHumanReadable } from "@src/utils";
|
|
6
6
|
import { Skeleton } from "@src/components";
|
|
@@ -10,7 +10,7 @@ interface Props {
|
|
|
10
10
|
tokens: Token[] | undefined;
|
|
11
11
|
selectedToken: Token | undefined;
|
|
12
12
|
setSelectedToken: (token: Token) => void;
|
|
13
|
-
signer: string;
|
|
13
|
+
signer: string | undefined;
|
|
14
14
|
balances: TokenBalances | undefined;
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -52,6 +52,12 @@ export const TokenPicker: FC<Props> = ({
|
|
|
52
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
53
|
>
|
|
54
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
|
+
<div
|
|
56
|
+
onClick={onCloseClick}
|
|
57
|
+
className="absolute top-4 right-4 cursor-pointer"
|
|
58
|
+
>
|
|
59
|
+
<CloseIcon />
|
|
60
|
+
</div>
|
|
55
61
|
<p className="text-base mb-4">Pick a token</p>
|
|
56
62
|
<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
63
|
<div className="w-6 h-6">
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { shortAddress } from "@src/utils";
|
|
3
|
+
import { SettingsIcon, HistoryIcon } from "../icons";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
signer: string | undefined;
|
|
7
|
+
setSettingsShown: React.Dispatch<React.SetStateAction<boolean>>;
|
|
8
|
+
historyTabShown: boolean;
|
|
9
|
+
setHistoryTabShown: React.Dispatch<React.SetStateAction<boolean>>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TopBar: FC<Props> = ({
|
|
13
|
+
signer,
|
|
14
|
+
setSettingsShown,
|
|
15
|
+
setHistoryTabShown,
|
|
16
|
+
historyTabShown,
|
|
17
|
+
}) => {
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex w-full justify-between mx-auto pl-4 pr-8 pt-2">
|
|
20
|
+
<div className="rounded-lg text-[rgba(255,255,255,0.6)] text-base whitespace-nowrap">
|
|
21
|
+
{signer ? `Connected wallet: ${shortAddress(signer)}` : `Disconnected`}
|
|
22
|
+
</div>
|
|
23
|
+
<div className="flex gap-2">
|
|
24
|
+
{!historyTabShown && (
|
|
25
|
+
<div
|
|
26
|
+
onClick={() => setSettingsShown(true)}
|
|
27
|
+
className="flex items-center text-xs gap-1 cursor-pointer"
|
|
28
|
+
>
|
|
29
|
+
<div className="w-7 h-7 rounded-full bg-gradient-to-r from-x_blue_light to-x_blue_dark flex items-center justify-center">
|
|
30
|
+
<SettingsIcon />
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
)}
|
|
34
|
+
{signer && (
|
|
35
|
+
<div
|
|
36
|
+
onClick={() => setHistoryTabShown((x) => !x)}
|
|
37
|
+
className="flex items-center text-sm gap-1 cursor-pointer"
|
|
38
|
+
>
|
|
39
|
+
{!historyTabShown ? (
|
|
40
|
+
<>
|
|
41
|
+
<div className="w-7 h-7 rounded-full bg-gradient-to-r from-x_blue_light to-x_blue_dark flex items-center justify-center">
|
|
42
|
+
<HistoryIcon />
|
|
43
|
+
</div>
|
|
44
|
+
</>
|
|
45
|
+
) : (
|
|
46
|
+
<div>Back</div>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import "@src/global.css";
|
|
2
|
+
import { MouseEvent, useMemo } from "react";
|
|
3
|
+
import { createRoot, Root } from "react-dom/client";
|
|
4
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
import { WagmiProvider } from "wagmi";
|
|
6
|
+
import { getWagmiConfig } from "@src/config";
|
|
7
|
+
import { Chain, ContractCall, Route } from "@src/models";
|
|
8
|
+
import { CloseIcon } from "@src/components/icons/CloseIcon";
|
|
9
|
+
import { Form } from "./Form";
|
|
10
|
+
import { ChainlinkCCIPIcon, XSwapBadgeIcon } from "../icons";
|
|
11
|
+
|
|
12
|
+
export type TxConfigFormProps = {
|
|
13
|
+
dstChainId: string;
|
|
14
|
+
dstTokenAddr: string;
|
|
15
|
+
customContractCalls: ContractCall[];
|
|
16
|
+
desc?: string;
|
|
17
|
+
supportedChains: Chain[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const TxConfigForm = ({
|
|
21
|
+
dstChainId,
|
|
22
|
+
dstTokenAddr,
|
|
23
|
+
customContractCalls,
|
|
24
|
+
desc,
|
|
25
|
+
supportedChains,
|
|
26
|
+
onSubmit,
|
|
27
|
+
onClose,
|
|
28
|
+
}: TxConfigFormProps & {
|
|
29
|
+
onSubmit: (route: Route) => void;
|
|
30
|
+
onClose: () => void;
|
|
31
|
+
}) => {
|
|
32
|
+
const onBackdropClick = (e: MouseEvent) => {
|
|
33
|
+
e.stopPropagation();
|
|
34
|
+
e.nativeEvent.stopImmediatePropagation();
|
|
35
|
+
if (e.target === e.currentTarget) {
|
|
36
|
+
onClose();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const queryClient = new QueryClient();
|
|
41
|
+
const wagmiConfig = useMemo(
|
|
42
|
+
() => getWagmiConfig(supportedChains),
|
|
43
|
+
supportedChains,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<WagmiProvider config={wagmiConfig}>
|
|
48
|
+
<QueryClientProvider client={queryClient}>
|
|
49
|
+
<div
|
|
50
|
+
className="top-0 left-0 right-0 bottom-0 bg-[rgba(0,0,0,0.8)] fixed flex items-center justify-center z-10"
|
|
51
|
+
onClick={onBackdropClick}
|
|
52
|
+
>
|
|
53
|
+
<div className="relative bg-black rounded-3xl w-lg overflow-auto text-white border-2 border-solid border-[rgba(255,255,255,0.1)]">
|
|
54
|
+
<Form
|
|
55
|
+
dstChainId={dstChainId}
|
|
56
|
+
dstTokenAddr={dstTokenAddr}
|
|
57
|
+
customContractCalls={customContractCalls}
|
|
58
|
+
desc={desc}
|
|
59
|
+
supportedChains={supportedChains}
|
|
60
|
+
onSubmit={onSubmit}
|
|
61
|
+
/>
|
|
62
|
+
<div
|
|
63
|
+
className="absolute top-7 right-4 cursor-pointer text-white"
|
|
64
|
+
onClick={onClose}
|
|
65
|
+
>
|
|
66
|
+
<CloseIcon />
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="swappage__poweredby">
|
|
70
|
+
<span>Powered by</span>
|
|
71
|
+
<XSwapBadgeIcon />
|
|
72
|
+
<span>✕</span>
|
|
73
|
+
<ChainlinkCCIPIcon />
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</QueryClientProvider>
|
|
78
|
+
</WagmiProvider>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const openTxConfigForm = async ({
|
|
83
|
+
dstChainId,
|
|
84
|
+
dstTokenAddr,
|
|
85
|
+
customContractCalls,
|
|
86
|
+
desc,
|
|
87
|
+
supportedChains,
|
|
88
|
+
}: TxConfigFormProps): Promise<Route> => {
|
|
89
|
+
try {
|
|
90
|
+
return await new Promise((resolve) => {
|
|
91
|
+
txConfigFormRoot.render(
|
|
92
|
+
<TxConfigForm
|
|
93
|
+
dstChainId={dstChainId}
|
|
94
|
+
dstTokenAddr={dstTokenAddr}
|
|
95
|
+
customContractCalls={customContractCalls}
|
|
96
|
+
desc={desc}
|
|
97
|
+
supportedChains={supportedChains}
|
|
98
|
+
onSubmit={(route) => {
|
|
99
|
+
resolve(route);
|
|
100
|
+
txConfigFormRoot.render("");
|
|
101
|
+
}}
|
|
102
|
+
onClose={() => {
|
|
103
|
+
txConfigFormRoot.render("");
|
|
104
|
+
}}
|
|
105
|
+
/>,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
} catch (err) {
|
|
109
|
+
throw new Error(`XSwap component error, ${err}`);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// skip on server-side integrations
|
|
114
|
+
let txConfigFormRoot: Root;
|
|
115
|
+
if (typeof document !== "undefined") {
|
|
116
|
+
const xswapElement = document.createElement("div");
|
|
117
|
+
xswapElement.setAttribute("id", "txConfigFormRoot");
|
|
118
|
+
xswapElement.classList.add("xswap");
|
|
119
|
+
document.body.appendChild(xswapElement);
|
|
120
|
+
txConfigFormRoot = createRoot(xswapElement);
|
|
121
|
+
}
|