@xswap-link/sdk 0.7.0 → 0.8.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.
- package/CHANGELOG.md +10 -0
- package/dist/index.d.mts +122 -70
- package/dist/index.d.ts +122 -70
- package/dist/index.global.js +686 -281
- package/dist/index.js +10 -10
- package/dist/index.mjs +10 -10
- package/package.json +1 -1
- package/src/assets/icons/ArrowsExchange.tsx +18 -0
- package/src/assets/icons/index.ts +1 -0
- package/src/components/Swap/Header/index.tsx +4 -2
- package/src/components/Swap/HistoryView/index.tsx +2 -2
- package/src/components/Swap/ReorderButton/ReorderButton.tsx +37 -0
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/ArrowIcon.tsx +1 -1
- package/src/components/Swap/SwapView/SwapButton/index.tsx +3 -1
- package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/ChainItem/index.tsx +55 -22
- package/src/components/Swap/SwapView/SwapPanel/ChainPanel/ChainPicker/index.tsx +71 -6
- package/src/components/Swap/SwapView/SwapPanel/ChainPanel/index.tsx +68 -15
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +10 -2
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +31 -11
- package/src/components/Swap/SwapView/SwapPanel/index.tsx +8 -3
- package/src/components/Swap/SwapView/index.tsx +3 -3
- package/src/components/Swap/index.tsx +1 -1
- package/src/components/TxConfigForm/index.tsx +57 -4
- package/src/components/TxWidgetWC/index.tsx +47 -2
- package/src/components/TxWidgetWCWrapped/index.tsx +33 -1
- package/src/config/index.ts +1 -1
- package/src/context/SwapProvider.tsx +177 -27
- package/src/models/BridgeTokensDictionary.ts +7 -0
- package/src/models/TokenData.ts +3 -1
- package/src/models/index.ts +6 -5
- package/src/models/payloads/ModalIntegrationPayload.ts +15 -1
- package/src/models/payloads/WidgetIntegrationPayload.ts +15 -1
- package/src/services/api.ts +6 -1
- package/src/services/integrations/transactions.ts +20 -0
- package/src/types/global.d.ts +15 -0
- package/src/utils/validation.ts +367 -0
- package/tsconfig.json +3 -2
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
|
|
3
|
+
export const ArrowsExchange: FC = () => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
width="14"
|
|
7
|
+
height="18"
|
|
8
|
+
viewBox="0 0 14 18"
|
|
9
|
+
fill="none"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
d="M3.66665 9.83335V3.85419L1.52081 6.00002L0.333313 4.83335L4.49998 0.666687L8.66665 4.83335L7.47915 6.00002L5.33331 3.85419V9.83335H3.66665ZM9.49998 17.3334L5.33331 13.1667L6.52081 12L8.66665 14.1459V8.16669H10.3333V14.1459L12.4791 12L13.6666 13.1667L9.49998 17.3334Z"
|
|
14
|
+
fill="white"
|
|
15
|
+
/>
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -2,6 +2,7 @@ export * from "./ArrowDownIcon";
|
|
|
2
2
|
export * from "./ArrowDownLongIcon";
|
|
3
3
|
export * from "./ArrowLeftIcon";
|
|
4
4
|
export * from "./ArrowRightIcon";
|
|
5
|
+
export * from "./ArrowsExchange";
|
|
5
6
|
export * from "./ArrowUpIcon";
|
|
6
7
|
export * from "./ArrowUpRightIcon";
|
|
7
8
|
export * from "./CashbacklIcon";
|
|
@@ -5,11 +5,13 @@ type Props = {
|
|
|
5
5
|
onClose: () => void;
|
|
6
6
|
};
|
|
7
7
|
export const Header = ({ onClose }: Props) => {
|
|
8
|
-
const { tab } = useSwapContext();
|
|
8
|
+
const { tab, bridgeUI } = useSwapContext();
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
11
|
<div className="flex justify-between">
|
|
12
|
-
<div className="text-xl">
|
|
12
|
+
<div className="text-xl">
|
|
13
|
+
{tab === "Swap" && bridgeUI ? "Bridge" : tab}
|
|
14
|
+
</div>
|
|
13
15
|
<Controls onClose={onClose} />
|
|
14
16
|
</div>
|
|
15
17
|
);
|
|
@@ -22,7 +22,7 @@ export const HistoryView = () => {
|
|
|
22
22
|
return (
|
|
23
23
|
<Virtuoso
|
|
24
24
|
data={history}
|
|
25
|
-
style={{ height: "
|
|
25
|
+
style={{ height: "532px" }}
|
|
26
26
|
itemContent={(_, transaction) => {
|
|
27
27
|
return <TxDataCard transaction={transaction} />;
|
|
28
28
|
}}
|
|
@@ -30,5 +30,5 @@ export const HistoryView = () => {
|
|
|
30
30
|
);
|
|
31
31
|
}, [history, historyLoadedOnce]);
|
|
32
32
|
|
|
33
|
-
return <div className="
|
|
33
|
+
return <div className="flex-1 overflow-hidden">{view}</div>;
|
|
34
34
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ArrowsExchange } from "@src/assets/icons";
|
|
2
|
+
import { useSwapContext } from "@src/context";
|
|
3
|
+
import { FC } from "react";
|
|
4
|
+
|
|
5
|
+
export const ReorderButton: FC = () => {
|
|
6
|
+
const {
|
|
7
|
+
dstChain,
|
|
8
|
+
dstToken,
|
|
9
|
+
srcChain,
|
|
10
|
+
srcToken,
|
|
11
|
+
setDstChain,
|
|
12
|
+
setDstToken,
|
|
13
|
+
setSrcChain,
|
|
14
|
+
setSrcToken,
|
|
15
|
+
} = useSwapContext();
|
|
16
|
+
|
|
17
|
+
const onReorderClick = () => {
|
|
18
|
+
setDstChain(srcChain);
|
|
19
|
+
setSrcChain(dstChain);
|
|
20
|
+
setDstToken(srcToken);
|
|
21
|
+
setSrcToken(dstToken);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<button
|
|
26
|
+
onClick={onReorderClick}
|
|
27
|
+
aria-label="Switch target token with the source token"
|
|
28
|
+
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-14 p-1 border border-solid border-t_text_primary border-opacity-10 rounded-xl bg-t_bg_secondary"
|
|
29
|
+
>
|
|
30
|
+
<div className="relative bg-gradient-to-r from-t_main_accent_light to-t_main_accent_dark w-8 h-8 rounded-lg">
|
|
31
|
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 fill-white">
|
|
32
|
+
<ArrowsExchange />
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</button>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -2,7 +2,7 @@ import { ArrowDownLongIcon } from "@src/assets/icons";
|
|
|
2
2
|
|
|
3
3
|
export const ArrowIcon = () => {
|
|
4
4
|
return (
|
|
5
|
-
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-
|
|
5
|
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-14 p-1 border border-solid border-t_text_primary border-opacity-10 rounded-xl bg-t_bg_secondary">
|
|
6
6
|
<div className="relative bg-gradient-to-r from-t_main_accent_light to-t_main_accent_dark w-8 h-8 rounded-xl">
|
|
7
7
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 fill-white">
|
|
8
8
|
<ArrowDownLongIcon />
|
|
@@ -24,6 +24,7 @@ export const SwapButton = () => {
|
|
|
24
24
|
const { integrationConfig } = useSwapContext();
|
|
25
25
|
|
|
26
26
|
const {
|
|
27
|
+
bridgeUI,
|
|
27
28
|
error,
|
|
28
29
|
srcChain,
|
|
29
30
|
dstChain,
|
|
@@ -199,13 +200,14 @@ export const SwapButton = () => {
|
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
return {
|
|
202
|
-
text: "Swap",
|
|
203
|
+
text: bridgeUI ? "Bridge" : "Swap",
|
|
203
204
|
fn: async () => {
|
|
204
205
|
showSwapConfirmationView();
|
|
205
206
|
},
|
|
206
207
|
disabled: false,
|
|
207
208
|
};
|
|
208
209
|
}, [
|
|
210
|
+
bridgeUI,
|
|
209
211
|
srcChainId,
|
|
210
212
|
dstChainId,
|
|
211
213
|
srcTokenAddress,
|
|
@@ -1,74 +1,107 @@
|
|
|
1
1
|
import { DotGreenIcon, HelpIcon } from "@src/assets/icons";
|
|
2
|
-
import {
|
|
2
|
+
import { SwapPanelType } from "@src/components/Swap/SwapView";
|
|
3
|
+
import { Tooltip } from "@src/components/Tooltip";
|
|
3
4
|
import { useSwapContext } from "@src/context";
|
|
5
|
+
import useNetworks from "@src/hooks/networkManagement/useNetworks";
|
|
4
6
|
import { Chain } from "@src/models";
|
|
5
7
|
import { useMemo, useRef } from "react";
|
|
6
|
-
import { SwapPanelType } from "../../../../../SwapView";
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
9
10
|
chain: Chain;
|
|
10
11
|
type: SwapPanelType;
|
|
11
|
-
|
|
12
|
+
disabledChains: string[];
|
|
12
13
|
onClick: (chainId: string) => void;
|
|
13
14
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
|
|
16
|
+
export const ChainItem = ({ chain, type, onClick, disabledChains }: Props) => {
|
|
17
|
+
const {
|
|
18
|
+
networks: { evm },
|
|
19
|
+
} = useNetworks();
|
|
17
20
|
|
|
18
21
|
const tooltipTriggerRef = useRef<SVGSVGElement>(null);
|
|
19
22
|
|
|
20
|
-
const { srcChain, dstChain } = useSwapContext();
|
|
23
|
+
const { srcChain, dstChain, bridgeUI } = useSwapContext();
|
|
24
|
+
|
|
21
25
|
const currentChainId = useMemo(
|
|
22
26
|
() => (type === "source" ? srcChain?.chainId : dstChain?.chainId),
|
|
23
27
|
[dstChain, srcChain, type],
|
|
24
28
|
);
|
|
25
29
|
|
|
26
|
-
const connected =
|
|
30
|
+
const connected = useMemo(
|
|
31
|
+
() => evm.signer && evm.chainId === chain.chainId,
|
|
32
|
+
[chain.chainId, evm.chainId, evm.signer],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const sameChainId = useMemo(() => {
|
|
36
|
+
if (!bridgeUI) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return type === "destination" ? srcChain?.chainId : dstChain?.chainId;
|
|
41
|
+
}, [bridgeUI, dstChain?.chainId, srcChain?.chainId, type]);
|
|
42
|
+
|
|
43
|
+
const isInDisabledChains = disabledChains.includes(chain.chainId);
|
|
27
44
|
|
|
28
45
|
return (
|
|
29
46
|
<div
|
|
30
47
|
onClick={() => {
|
|
31
|
-
if (chain.chainId ===
|
|
48
|
+
if (chain.chainId === sameChainId || isInDisabledChains) {
|
|
32
49
|
return;
|
|
33
50
|
}
|
|
34
51
|
onClick(chain.chainId);
|
|
35
52
|
}}
|
|
36
|
-
className={`flex justify-between gap-3 mt-2 mb-2 border border-solid border-
|
|
37
|
-
currentChainId === chain.chainId
|
|
38
|
-
? "selected-chain-item"
|
|
39
|
-
: "bg-t_bg_primary"
|
|
53
|
+
className={`flex justify-between items-center gap-3 mt-2 mb-2 cursor-pointer p-4 border border-solid border-white border-opacity-0 hover:selected-chain-item ${
|
|
54
|
+
currentChainId === chain.chainId ? "selected-chain-item" : ""
|
|
40
55
|
} ${
|
|
41
|
-
chain.chainId ===
|
|
56
|
+
chain.chainId === sameChainId || isInDisabledChains
|
|
42
57
|
? "disabled-chain-item cursor-default"
|
|
43
58
|
: ""
|
|
44
59
|
}`}
|
|
45
60
|
>
|
|
46
61
|
<div className="flex gap-3 items-center w-full">
|
|
47
62
|
<img src={chain.image} alt={chain.name} className="w-[34px] h-[34px]" />
|
|
48
|
-
<p
|
|
63
|
+
<p>{chain.displayName}</p>
|
|
49
64
|
</div>
|
|
50
65
|
|
|
51
66
|
{connected && (
|
|
52
|
-
<div className="flex items-
|
|
67
|
+
<div className="flex items-baseline gap-1">
|
|
53
68
|
<DotGreenIcon />
|
|
54
69
|
<div className="text-t_text_green">connected</div>
|
|
55
70
|
</div>
|
|
56
71
|
)}
|
|
57
|
-
|
|
72
|
+
|
|
73
|
+
{isInDisabledChains && chain.chainId !== sameChainId && (
|
|
58
74
|
<div className="flex items-center gap-1">
|
|
59
75
|
<Tooltip
|
|
60
|
-
text=
|
|
76
|
+
text={
|
|
77
|
+
bridgeUI
|
|
78
|
+
? "Token is not available on this chain"
|
|
79
|
+
: `Transfers from ${
|
|
80
|
+
srcChain?.name || "the source chain"
|
|
81
|
+
} to this chain are currently not supported.`
|
|
82
|
+
}
|
|
61
83
|
position="BOTTOM"
|
|
62
84
|
triggerRef={tooltipTriggerRef}
|
|
63
|
-
id="transfers-
|
|
85
|
+
id="transfers-from-chain-not-supported-tooltip"
|
|
64
86
|
/>
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
65
89
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
90
|
+
{chain.chainId === sameChainId && (
|
|
91
|
+
<div className="flex items-center gap-1">
|
|
92
|
+
<Tooltip
|
|
93
|
+
text="Transfers between the same chain are not available"
|
|
94
|
+
id="transfers-between-same-chains-tooltip"
|
|
95
|
+
position="BOTTOM"
|
|
96
|
+
triggerRef={tooltipTriggerRef}
|
|
69
97
|
/>
|
|
70
98
|
</div>
|
|
71
99
|
)}
|
|
100
|
+
|
|
101
|
+
<HelpIcon
|
|
102
|
+
ref={tooltipTriggerRef}
|
|
103
|
+
className="cursor-help fill-t_main_accent_light w-[18px] h-[18px] min-w-[18px] min-h-[18px] max-w-[18px] max-h-[18px]"
|
|
104
|
+
/>
|
|
72
105
|
</div>
|
|
73
106
|
);
|
|
74
107
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CloseIcon } from "@src/assets/icons";
|
|
2
|
+
import { useSwapContext } from "@src/context";
|
|
2
3
|
import { Chain, Web3Environment } from "@src/models";
|
|
3
4
|
import { useMemo } from "react";
|
|
4
5
|
import { Virtuoso } from "react-virtuoso";
|
|
@@ -7,24 +8,33 @@ import { ChainItem } from "./ChainItem";
|
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
9
10
|
type: SwapPanelType;
|
|
10
|
-
supportedChains: Chain[];
|
|
11
11
|
setSrcChain: (chain: Chain) => void;
|
|
12
12
|
setDstChain: (chain: Chain) => void;
|
|
13
13
|
onClose: () => void;
|
|
14
14
|
};
|
|
15
15
|
export const ChainPicker = ({
|
|
16
16
|
type,
|
|
17
|
-
supportedChains,
|
|
18
17
|
setSrcChain,
|
|
19
18
|
setDstChain,
|
|
20
19
|
onClose,
|
|
21
20
|
}: Props) => {
|
|
21
|
+
const {
|
|
22
|
+
srcChain,
|
|
23
|
+
srcToken,
|
|
24
|
+
dstChain,
|
|
25
|
+
dstToken,
|
|
26
|
+
bridgeUI,
|
|
27
|
+
bridgeTokensDictionary,
|
|
28
|
+
supportedChains,
|
|
29
|
+
} = useSwapContext();
|
|
30
|
+
|
|
22
31
|
const filteredChains = useMemo(() => {
|
|
23
32
|
return supportedChains.filter(
|
|
24
|
-
({ web3Environment, swapSupported }) =>
|
|
25
|
-
web3Environment === Web3Environment.MAINNET &&
|
|
33
|
+
({ web3Environment, swapSupported, bridgeSupported }) =>
|
|
34
|
+
web3Environment === Web3Environment.MAINNET &&
|
|
35
|
+
((bridgeUI && bridgeSupported) || (!bridgeUI && swapSupported)),
|
|
26
36
|
);
|
|
27
|
-
}, [supportedChains]);
|
|
37
|
+
}, [bridgeUI, supportedChains]);
|
|
28
38
|
|
|
29
39
|
const select = (selectedChainId: string) => {
|
|
30
40
|
const chain = supportedChains.find(
|
|
@@ -41,6 +51,56 @@ export const ChainPicker = ({
|
|
|
41
51
|
onClose();
|
|
42
52
|
};
|
|
43
53
|
|
|
54
|
+
const disabledChains = useMemo(() => {
|
|
55
|
+
if (!srcChain?.chainId || !srcToken?.address) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!bridgeUI) {
|
|
60
|
+
const disabledForDestination =
|
|
61
|
+
supportedChains.find((chain) => chain.chainId === srcChain.chainId)
|
|
62
|
+
?.disabledForDestination || [];
|
|
63
|
+
|
|
64
|
+
if (type === "destination") {
|
|
65
|
+
return filteredChains
|
|
66
|
+
?.filter((chain) => disabledForDestination.includes(chain.chainId))
|
|
67
|
+
.map(({ chainId }) => chainId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (type === "destination") {
|
|
74
|
+
const availableChains =
|
|
75
|
+
bridgeTokensDictionary?.[srcChain.chainId]?.[srcToken.address];
|
|
76
|
+
const disabledChains = filteredChains
|
|
77
|
+
?.filter((chain) => !availableChains?.[chain.chainId])
|
|
78
|
+
.map(({ chainId }) => chainId);
|
|
79
|
+
return disabledChains;
|
|
80
|
+
} else {
|
|
81
|
+
if (!dstChain || !dstToken) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const availableChains =
|
|
86
|
+
bridgeTokensDictionary?.[dstChain.chainId]?.[dstToken.address];
|
|
87
|
+
const disabledChains = filteredChains
|
|
88
|
+
?.filter((chain) => !availableChains?.[chain.chainId])
|
|
89
|
+
.map(({ chainId }) => chainId);
|
|
90
|
+
return disabledChains;
|
|
91
|
+
}
|
|
92
|
+
}, [
|
|
93
|
+
bridgeTokensDictionary,
|
|
94
|
+
bridgeUI,
|
|
95
|
+
filteredChains,
|
|
96
|
+
srcChain,
|
|
97
|
+
srcToken,
|
|
98
|
+
dstChain,
|
|
99
|
+
dstToken,
|
|
100
|
+
type,
|
|
101
|
+
supportedChains,
|
|
102
|
+
]);
|
|
103
|
+
|
|
44
104
|
return (
|
|
45
105
|
<div className="flex flex-col h-full">
|
|
46
106
|
<div className="flex justify-between">
|
|
@@ -59,7 +119,12 @@ export const ChainPicker = ({
|
|
|
59
119
|
style={{ height: "100%" }}
|
|
60
120
|
data={filteredChains}
|
|
61
121
|
itemContent={(_, chain) => (
|
|
62
|
-
<ChainItem
|
|
122
|
+
<ChainItem
|
|
123
|
+
chain={chain}
|
|
124
|
+
type={type}
|
|
125
|
+
disabledChains={disabledChains}
|
|
126
|
+
onClick={select}
|
|
127
|
+
/>
|
|
63
128
|
)}
|
|
64
129
|
/>
|
|
65
130
|
) : (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ArrowDownIcon } from "@src/assets/icons";
|
|
2
2
|
import { Modal } from "@src/components/Modal";
|
|
3
3
|
import { useSwapContext } from "@src/context";
|
|
4
|
-
import { Chain } from "@src/models";
|
|
4
|
+
import { Chain, Token } from "@src/models";
|
|
5
5
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
6
6
|
import { useAccount } from "wagmi";
|
|
7
7
|
import { SwapPanelType } from "../../../SwapView";
|
|
@@ -10,9 +10,10 @@ import { ChainPicker } from "./ChainPicker";
|
|
|
10
10
|
type Props = {
|
|
11
11
|
chain: Chain | undefined;
|
|
12
12
|
type: SwapPanelType;
|
|
13
|
+
className?: string;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
export const ChainPanel = ({ chain, type }: Props) => {
|
|
16
|
+
export const ChainPanel = ({ chain, type, className }: Props) => {
|
|
16
17
|
const {
|
|
17
18
|
supportedChains,
|
|
18
19
|
srcChain,
|
|
@@ -22,20 +23,28 @@ export const ChainPanel = ({ chain, type }: Props) => {
|
|
|
22
23
|
setSrcToken,
|
|
23
24
|
dstToken,
|
|
24
25
|
setDstToken,
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
srcChainLocked,
|
|
27
|
+
dstChainLocked,
|
|
28
|
+
integrationConfig,
|
|
29
|
+
bridgeUI,
|
|
30
|
+
bridgeTokensDictionary,
|
|
27
31
|
} = useSwapContext();
|
|
28
32
|
|
|
29
33
|
const { chainId } = useAccount();
|
|
30
34
|
|
|
31
|
-
const
|
|
32
|
-
() => type === "destination" &&
|
|
33
|
-
[type,
|
|
35
|
+
const projectLockedDestinationChain = useMemo(
|
|
36
|
+
() => type === "destination" && dstChainLocked,
|
|
37
|
+
[type, dstChainLocked],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const projectLockedSourceChain = useMemo(
|
|
41
|
+
() => type === "source" && srcChainLocked,
|
|
42
|
+
[type, srcChainLocked],
|
|
34
43
|
);
|
|
35
44
|
|
|
36
45
|
// select chain from user wallet
|
|
37
46
|
useEffect(() => {
|
|
38
|
-
if (!chainId ||
|
|
47
|
+
if (!chainId || integrationConfig.srcChainId) return;
|
|
39
48
|
|
|
40
49
|
const userChainFromWallet = supportedChains.find(
|
|
41
50
|
(chain) => chain.chainId === String(chainId),
|
|
@@ -54,13 +63,57 @@ export const ChainPanel = ({ chain, type }: Props) => {
|
|
|
54
63
|
|
|
55
64
|
const handleSrcChainChange = useCallback(
|
|
56
65
|
(chain: Chain) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
let newSrcToken: Token | undefined = undefined;
|
|
67
|
+
|
|
68
|
+
if (bridgeUI) {
|
|
69
|
+
const bridgeableTokensAddresses = Object.keys(
|
|
70
|
+
bridgeTokensDictionary?.[chain.chainId] || {},
|
|
71
|
+
).map((address) => address.toLowerCase());
|
|
72
|
+
|
|
73
|
+
// Attempt to use the same token on a new chain matching by symbol.
|
|
74
|
+
newSrcToken = chain.tokens.find(
|
|
75
|
+
(token) =>
|
|
76
|
+
token.symbol === srcToken?.symbol &&
|
|
77
|
+
bridgeableTokensAddresses.includes(token.address.toLowerCase()),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (!newSrcToken) {
|
|
81
|
+
// The token from the previous chain is not bridgeable on the new chain.
|
|
82
|
+
// We will use the first available bridgeable token on the new chain.
|
|
83
|
+
|
|
84
|
+
const firstAvailableBridgeableTokenAddress =
|
|
85
|
+
bridgeableTokensAddresses[0];
|
|
86
|
+
|
|
87
|
+
if (!firstAvailableBridgeableTokenAddress) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Couldn't find any available bridgeable token on the chain ${chain.chainId}, but bridging is available`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
newSrcToken = chain.tokens.find(
|
|
94
|
+
(token) =>
|
|
95
|
+
token.address.toLowerCase() ===
|
|
96
|
+
firstAvailableBridgeableTokenAddress.toLowerCase(),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (!newSrcToken) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Found bridgeable token (${firstAvailableBridgeableTokenAddress}) on the chain ${chain.chainId}, but token was not defined in chains collection`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
newSrcToken =
|
|
107
|
+
// Attempt to use the same token on a new chain.
|
|
108
|
+
chain.tokens.find((token) => token.symbol === srcToken?.symbol) ||
|
|
109
|
+
// Otherwise, use the default token on this chain.
|
|
110
|
+
chain.tokens.find((token) => token.symbol === chain.tokenSymbol);
|
|
111
|
+
}
|
|
112
|
+
|
|
60
113
|
setSrcChain(chain);
|
|
61
114
|
setSrcToken(newSrcToken);
|
|
62
115
|
},
|
|
63
|
-
[setSrcChain, setSrcToken, srcToken],
|
|
116
|
+
[setSrcChain, setSrcToken, srcToken, bridgeTokensDictionary, bridgeUI],
|
|
64
117
|
);
|
|
65
118
|
|
|
66
119
|
const handleDstChainChange = useCallback(
|
|
@@ -80,7 +133,6 @@ export const ChainPanel = ({ chain, type }: Props) => {
|
|
|
80
133
|
<Modal onClose={() => setChainPickerModalOpen(false)}>
|
|
81
134
|
<ChainPicker
|
|
82
135
|
type={type}
|
|
83
|
-
supportedChains={supportedChains}
|
|
84
136
|
setSrcChain={handleSrcChainChange}
|
|
85
137
|
setDstChain={handleDstChainChange}
|
|
86
138
|
onClose={() => setChainPickerModalOpen(false)}
|
|
@@ -90,8 +142,9 @@ export const ChainPanel = ({ chain, type }: Props) => {
|
|
|
90
142
|
|
|
91
143
|
<button
|
|
92
144
|
type="button"
|
|
93
|
-
disabled={
|
|
145
|
+
disabled={projectLockedDestinationChain || projectLockedSourceChain}
|
|
94
146
|
onClick={() => setChainPickerModalOpen(true)}
|
|
147
|
+
className={`${className || ""}`}
|
|
95
148
|
>
|
|
96
149
|
<div className="flex justify-between items-center p-4 gap-8">
|
|
97
150
|
<div className="flex flex-col font-medium items-start">
|
|
@@ -102,7 +155,7 @@ export const ChainPanel = ({ chain, type }: Props) => {
|
|
|
102
155
|
{chain ? chain.displayName : "Select"}
|
|
103
156
|
</p>
|
|
104
157
|
</div>
|
|
105
|
-
{!
|
|
158
|
+
{!projectLockedDestinationChain && !projectLockedSourceChain && (
|
|
106
159
|
<div className="w-4 h-2 fill-t_text_primary">
|
|
107
160
|
<ArrowDownIcon />
|
|
108
161
|
</div>
|
|
@@ -49,7 +49,8 @@ export const TokenPicker = ({
|
|
|
49
49
|
const currentRouteRequestNonce = useRef<number>(0);
|
|
50
50
|
const tokenSearchRef = useRef<HTMLInputElement | null>(null);
|
|
51
51
|
|
|
52
|
-
const { supportedChains, findTokenDataByAddressAndChain } =
|
|
52
|
+
const { bridgeUI, supportedChains, findTokenDataByAddressAndChain } =
|
|
53
|
+
useSwapContext();
|
|
53
54
|
const evmContractApi = useEvmContractApi();
|
|
54
55
|
|
|
55
56
|
useEffect(() => {
|
|
@@ -131,6 +132,7 @@ export const TokenPicker = ({
|
|
|
131
132
|
|
|
132
133
|
useEffect(() => {
|
|
133
134
|
if (
|
|
135
|
+
!bridgeUI &&
|
|
134
136
|
isETHAddressValid(searchValue) &&
|
|
135
137
|
!findTokenDataByAddressAndChain(searchValue.toLowerCase(), chain.chainId)
|
|
136
138
|
) {
|
|
@@ -139,7 +141,13 @@ export const TokenPicker = ({
|
|
|
139
141
|
setLoadingCustomTokenData(false);
|
|
140
142
|
setCustomTokenData(null);
|
|
141
143
|
}
|
|
142
|
-
}, [
|
|
144
|
+
}, [
|
|
145
|
+
bridgeUI,
|
|
146
|
+
searchValue,
|
|
147
|
+
getCustomTokenData,
|
|
148
|
+
findTokenDataByAddressAndChain,
|
|
149
|
+
chain,
|
|
150
|
+
]);
|
|
143
151
|
|
|
144
152
|
useEffect(() => {
|
|
145
153
|
setSearchValue("");
|
|
@@ -10,10 +10,12 @@ type Props = {
|
|
|
10
10
|
chain: Chain | undefined;
|
|
11
11
|
token: Token | undefined;
|
|
12
12
|
type: SwapPanelType;
|
|
13
|
+
className?: string;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
export const TokenPanel = ({ chain, token, type }: Props) => {
|
|
16
|
+
export const TokenPanel = ({ chain, token, type, className }: Props) => {
|
|
16
17
|
const {
|
|
18
|
+
bridgeUI,
|
|
17
19
|
srcChainTokensOptions,
|
|
18
20
|
srcChainOtherTokensOptions,
|
|
19
21
|
dstChainTokensOptions,
|
|
@@ -23,12 +25,18 @@ export const TokenPanel = ({ chain, token, type }: Props) => {
|
|
|
23
25
|
supportedChains,
|
|
24
26
|
setDstToken,
|
|
25
27
|
setDstChain,
|
|
26
|
-
|
|
28
|
+
dstTokenLocked,
|
|
29
|
+
srcTokenLocked,
|
|
27
30
|
} = useSwapContext();
|
|
28
31
|
|
|
29
|
-
const
|
|
30
|
-
() => type === "destination" &&
|
|
31
|
-
[type,
|
|
32
|
+
const integratorLockedDestinationToken = useMemo(
|
|
33
|
+
() => type === "destination" && dstTokenLocked,
|
|
34
|
+
[type, dstTokenLocked],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const integratorLockedSourceToken = useMemo(
|
|
38
|
+
() => type === "source" && srcTokenLocked,
|
|
39
|
+
[type, srcTokenLocked],
|
|
32
40
|
);
|
|
33
41
|
|
|
34
42
|
const [tokenPickerModalOpen, setTokenPickerModalOpen] = useState(false);
|
|
@@ -38,6 +46,11 @@ export const TokenPanel = ({ chain, token, type }: Props) => {
|
|
|
38
46
|
[dstChainTokensOptions, srcChainTokensOptions, type],
|
|
39
47
|
);
|
|
40
48
|
|
|
49
|
+
const pickerDisabled = useMemo(
|
|
50
|
+
() => type === "destination" && bridgeUI,
|
|
51
|
+
[bridgeUI, type],
|
|
52
|
+
);
|
|
53
|
+
|
|
41
54
|
const otherTokensWithBalances = useMemo(
|
|
42
55
|
() =>
|
|
43
56
|
type === "source"
|
|
@@ -80,10 +93,15 @@ export const TokenPanel = ({ chain, token, type }: Props) => {
|
|
|
80
93
|
|
|
81
94
|
<button
|
|
82
95
|
type="button"
|
|
83
|
-
disabled={
|
|
96
|
+
disabled={
|
|
97
|
+
pickerDisabled ||
|
|
98
|
+
integratorLockedDestinationToken ||
|
|
99
|
+
integratorLockedSourceToken
|
|
100
|
+
}
|
|
84
101
|
onClick={() => {
|
|
85
102
|
setTokenPickerModalOpen(true);
|
|
86
103
|
}}
|
|
104
|
+
className={`${className || ""}`}
|
|
87
105
|
>
|
|
88
106
|
<div className="flex justify-between items-center p-4 gap-8 border-r border-solid border-t_text_primary border-opacity-10">
|
|
89
107
|
<div className="flex gap-2 items-center">
|
|
@@ -99,11 +117,13 @@ export const TokenPanel = ({ chain, token, type }: Props) => {
|
|
|
99
117
|
</p>
|
|
100
118
|
</div>
|
|
101
119
|
</div>
|
|
102
|
-
{!
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
{!pickerDisabled &&
|
|
121
|
+
!integratorLockedDestinationToken &&
|
|
122
|
+
!integratorLockedSourceToken && (
|
|
123
|
+
<div className="w-4 h-2 fill-t_text_primary">
|
|
124
|
+
<ArrowDownIcon />
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
107
127
|
</div>
|
|
108
128
|
</button>
|
|
109
129
|
</>
|
|
@@ -24,9 +24,14 @@ export const SwapPanel = ({ type }: Props) => {
|
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
26
|
<div className="flex flex-col x-border rounded-xl bg-t_bg_tertiary bg-opacity-5">
|
|
27
|
-
<div className="
|
|
28
|
-
<TokenPanel
|
|
29
|
-
|
|
27
|
+
<div className="flex border-b border-solid border-t_text_primary border-opacity-10">
|
|
28
|
+
<TokenPanel
|
|
29
|
+
chain={chain}
|
|
30
|
+
token={token}
|
|
31
|
+
type={type}
|
|
32
|
+
className="w-full flex-grow"
|
|
33
|
+
/>
|
|
34
|
+
<ChainPanel chain={chain} type={type} className="w-full max-w-[36%]" />
|
|
30
35
|
</div>
|
|
31
36
|
<div
|
|
32
37
|
className={`flex justify-between ${
|