Skip to content

Commit 6fb753e

Browse files
authored
Merge pull request #132 from skip-mev/staging
chore: sync staging -> main
2 parents 7b0f3d4 + 2189ad2 commit 6fb753e

File tree

9 files changed

+280
-41
lines changed

9 files changed

+280
-41
lines changed

Diff for: chain-registry

Submodule chain-registry updated 82 files

Diff for: src/components/AssetInput.tsx

+23-38
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,25 @@ import { PencilSquareIcon } from "@heroicons/react/20/solid";
22
import { BigNumber } from "bignumber.js";
33
import { clsx } from "clsx";
44
import { ethers } from "ethers";
5-
import { FC, Fragment, useEffect, useMemo, useState } from "react";
5+
import { FC, Fragment, useMemo, useState } from "react";
66

77
import { Chain } from "@/api/queries";
88
import { AssetWithMetadata, useAssets } from "@/context/assets";
99
import { disclosure } from "@/context/disclosures";
1010
import { useSettingsStore } from "@/context/settings";
1111
import Toast from "@/elements/Toast";
1212
import { useAccount } from "@/hooks/useAccount";
13-
import { getFee, useBalancesByChain } from "@/utils/utils";
13+
import { formatUSD, getFee, useBalancesByChain } from "@/utils/utils";
1414

1515
import AssetSelect from "./AssetSelect";
1616
import ChainSelect from "./ChainSelect";
1717
import { ClientOnly } from "./ClientOnly";
1818
import { SimpleTooltip } from "./SimpleTooltip";
19-
import { UsdDiff, UsdValue, useUsdDiffReset } from "./UsdValue";
2019

2120
interface Props {
2221
amount: string;
22+
amountUSD?: string;
23+
diffPercentage?: number;
2324
onAmountChange?: (amount: string) => void;
2425
asset?: AssetWithMetadata;
2526
onAssetChange?: (asset: AssetWithMetadata) => void;
@@ -33,6 +34,8 @@ interface Props {
3334

3435
const AssetInput: FC<Props> = ({
3536
amount,
37+
amountUSD,
38+
diffPercentage = 0,
3639
onAmountChange,
3740
asset,
3841
onAssetChange,
@@ -93,15 +96,6 @@ const AssetInput: FC<Props> = ({
9396

9497
const { slippage } = useSettingsStore();
9598

96-
const reset = useUsdDiffReset();
97-
useEffect(() => {
98-
const parsed = parseFloat(amount);
99-
100-
// hotfix side effect to prevent negative amounts
101-
if (parsed < 0) onAmountChange?.("0.0");
102-
if (parsed == 0) reset();
103-
}, [amount, onAmountChange, reset]);
104-
10599
return (
106100
<Fragment>
107101
<div className="space-y-4 border border-neutral-200 p-4 rounded-lg">
@@ -188,33 +182,24 @@ const AssetInput: FC<Props> = ({
188182
}}
189183
/>
190184
<div className="flex items-center space-x-2 tabular-nums h-8">
191-
{asset && parseFloat(amount) > 0 && (
192-
<div className="text-neutral-400 text-sm">
193-
<UsdValue
194-
error={null}
195-
chainId={asset.originChainID}
196-
denom={asset.originDenom}
197-
coingeckoID={asset.coingeckoID}
198-
value={amount}
199-
context={context}
200-
/>
201-
</div>
202-
)}
203-
{context === "dest" && (
204-
<UsdDiff.Value>
205-
{({ isLoading, percentage }) => (
206-
<div
207-
className={clsx(
208-
"text-sm",
209-
isLoading && "hidden",
210-
percentage > 0 ? "text-green-500" : "text-red-500",
211-
)}
212-
>
213-
({percentage.toFixed(2)}%)
214-
</div>
185+
<p className="text-neutral-400 text-sm">
186+
{amountUSD ? formatUSD(amountUSD) : null}
187+
</p>
188+
{amountUSD !== undefined &&
189+
diffPercentage !== 0 &&
190+
context === "dest" ? (
191+
<p
192+
className={clsx(
193+
"text-sm",
194+
diffPercentage >= 0 ? "text-green-500" : "text-red-500",
215195
)}
216-
</UsdDiff.Value>
217-
)}
196+
>
197+
{new Intl.NumberFormat("en-US", {
198+
style: "percent",
199+
maximumFractionDigits: 2,
200+
}).format(diffPercentage)}
201+
</p>
202+
) : null}
218203
<div className="flex-grow" />
219204
{showBalance && address && selectedAssetBalance && asset && (
220205
<div className="text-neutral-400 text-sm flex items-center">

Diff for: src/components/PriceImpactWarning.tsx

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { useDisclosureKey } from "@/context/disclosures";
2+
3+
interface Props {
4+
onGoBack: () => void;
5+
message?: string;
6+
title?: string;
7+
}
8+
9+
export const PriceImpactWarning = ({
10+
onGoBack,
11+
message = "",
12+
title = "",
13+
}: Props) => {
14+
const [isOpen, control] = useDisclosureKey("priceImpactWarning");
15+
16+
if (!isOpen || title === "") return null;
17+
18+
return (
19+
<div className="absolute inset-0 bg-white rounded-3xl z-[999]">
20+
<div className="h-full px-4 py-6 overflow-y-auto scrollbar-hide">
21+
<div className="h-full flex flex-col">
22+
<div className="flex-1 pt-8">
23+
<div className="text-red-400 flex justify-center py-16">
24+
<svg
25+
xmlns="http://www.w3.org/2000/svg"
26+
viewBox="0 0 24 24"
27+
fill="currentColor"
28+
className="h-24 w-24"
29+
>
30+
<path
31+
fillRule="evenodd"
32+
d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
33+
clipRule="evenodd"
34+
/>
35+
</svg>
36+
</div>
37+
<p className="font-bold text-lg text-center text-red-500 mb-2">
38+
{title}
39+
</p>
40+
<p className="text-center text-lg px-4 leading-snug text-gray-500">
41+
{message} Do you want to continue?
42+
</p>
43+
</div>
44+
<div className="flex items-end gap-2">
45+
<button
46+
className="bg-[#FF486E] hover:bg-[#ed1149] transition-colors text-white font-semibold py-4 rounded-md w-full"
47+
onClick={() => {
48+
control.close();
49+
onGoBack();
50+
}}
51+
>
52+
Go Back
53+
</button>
54+
<button
55+
className="border border-gray-400 text-gray-500 font-semibold py-4 rounded-md w-full transition-colors hover:bg-gray-50"
56+
onClick={() => control.close()}
57+
>
58+
Continue
59+
</button>
60+
</div>
61+
</div>
62+
</div>
63+
</div>
64+
);
65+
};

Diff for: src/components/SwapWidget/SwapDetails.tsx

+34-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ChevronDownIcon, PencilSquareIcon } from "@heroicons/react/20/solid";
22
import * as Collapsible from "@radix-ui/react-collapsible";
33
import { RouteResponse } from "@skip-router/core";
44
import { clsx } from "clsx";
5-
import { useMemo } from "react";
5+
import { Fragment, useEffect, useMemo } from "react";
66

77
import { disclosure, useDisclosureKey } from "@/context/disclosures";
88
import { useSettingsStore } from "@/context/settings";
@@ -15,6 +15,8 @@ import { FormValues } from "./useSwapWidget";
1515
type Props = FormValues & {
1616
amountOut: string;
1717
route: RouteResponse;
18+
priceImpactPercent: number;
19+
priceImpactThresholdReached: boolean;
1820
};
1921

2022
export const SwapDetails = ({
@@ -25,6 +27,8 @@ export const SwapDetails = ({
2527
destinationChain,
2628
destinationAsset,
2729
route,
30+
priceImpactPercent,
31+
priceImpactThresholdReached,
2832
}: Props) => {
2933
const [open, control] = useDisclosureKey("swapDetailsCollapsible");
3034

@@ -43,6 +47,12 @@ export const SwapDetails = ({
4347
return +feeAmount / Math.pow(10, 18);
4448
}, [axelarTransferOperation]);
4549

50+
useEffect(() => {
51+
if (priceImpactThresholdReached) {
52+
control.open();
53+
}
54+
}, [control, priceImpactThresholdReached]);
55+
4656
if (!(sourceChain && sourceAsset && destinationChain && destinationAsset)) {
4757
return null;
4858
}
@@ -110,6 +120,29 @@ export const SwapDetails = ({
110120
"[&_dd]:text-end [&_dd]:tabular-nums",
111121
)}
112122
>
123+
{priceImpactPercent ? (
124+
<Fragment>
125+
<dt>
126+
<span
127+
className={clsx(
128+
priceImpactThresholdReached ? "text-red-500" : undefined,
129+
)}
130+
>
131+
Price Impact
132+
</span>
133+
</dt>
134+
<dd
135+
className={clsx(
136+
priceImpactThresholdReached ? "text-red-500" : undefined,
137+
)}
138+
>
139+
{new Intl.NumberFormat("en-US", {
140+
style: "percent",
141+
maximumFractionDigits: 2,
142+
}).format(priceImpactPercent)}
143+
</dd>
144+
</Fragment>
145+
) : null}
113146
<dt>
114147
Max Slippage{" "}
115148
<SimpleTooltip label="Click to change max slippage">

Diff for: src/components/SwapWidget/SwapWidget.tsx

+21
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,20 @@ export const SwapWidget: FC = () => {
4747
onSourceAssetChange,
4848
onDestinationChainChange,
4949
onDestinationAssetChange,
50+
swapPriceImpactPercent,
51+
priceImpactThresholdReached,
5052
routeError,
53+
routeWarningTitle,
54+
routeWarningMessage,
5155
} = useSwapWidget();
5256

57+
let usdDiffPercent = 0.0;
58+
if (route?.usdAmountIn && route?.usdAmountOut) {
59+
usdDiffPercent =
60+
(parseFloat(route.usdAmountOut) - parseFloat(route.usdAmountIn)) /
61+
parseFloat(route.usdAmountIn);
62+
}
63+
5364
const {
5465
address,
5566
isWalletConnected: isSourceWalletConnected,
@@ -114,6 +125,7 @@ export const SwapWidget: FC = () => {
114125
<div data-testid="source">
115126
<AssetInput
116127
amount={amountIn}
128+
amountUSD={route?.usdAmountIn}
117129
asset={sourceAsset}
118130
chain={sourceChain}
119131
chains={chains ?? []}
@@ -174,6 +186,8 @@ export const SwapWidget: FC = () => {
174186
<div data-testid="destination">
175187
<AssetInput
176188
amount={amountOut}
189+
amountUSD={route?.usdAmountOut}
190+
diffPercentage={usdDiffPercent}
177191
asset={destinationAsset}
178192
chain={destinationChain}
179193
chains={chains ?? []}
@@ -196,6 +210,8 @@ export const SwapWidget: FC = () => {
196210
destinationChain={destinationChain}
197211
destinationAsset={destinationAsset}
198212
route={route}
213+
priceImpactPercent={swapPriceImpactPercent ?? 0}
214+
priceImpactThresholdReached={priceImpactThresholdReached}
199215
/>
200216
)}
201217
{routeLoading && <RouteLoadingBanner />}
@@ -251,6 +267,11 @@ export const SwapWidget: FC = () => {
251267
route={route}
252268
transactionCount={numberOfTransactions}
253269
insufficientBalance={insufficientBalance}
270+
shouldShowPriceImpactWarning={
271+
!!routeWarningTitle && !!routeWarningMessage
272+
}
273+
routeWarningTitle={routeWarningTitle}
274+
routeWarningMessage={routeWarningMessage}
254275
/>
255276
{insufficientBalance && (
256277
<p className="text-center font-semibold text-sm text-red-500">

0 commit comments

Comments
 (0)