diff --git a/README.md b/README.md
index 139f5a454..9bdb02ca2 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ We use `react-query` in conjunction with `graphql-codegen` for interacting with
- run the graphql-codegen task with `pnpm nx run oeth-shared:codegen-graphql`, it will generate
- the global types in `libs/oeth/shared/src/generated/graphql.ts` and
- the generated hooks next to your graphql file (i.e. `/libs/oeth/history/src/queries.generated.tsx`)
-- use the generated hooks in your component with fully typed args and results!
+- use the generated hooks in your component with fully typed args and results
Couple of things to note:
- generated hooks receives args as first param, second param exposes all react-query api for controlling execution
diff --git a/apps/oeth/src/App.tsx b/apps/oeth/src/App.tsx
index e6f6fbea7..04f67ea27 100644
--- a/apps/oeth/src/App.tsx
+++ b/apps/oeth/src/App.tsx
@@ -11,12 +11,8 @@ export const App = () => {
({
- xs: '112px',
- md: `${theme.mixins.toolbar.height}px`,
- }),
}}
maxWidth="sm"
>
diff --git a/apps/oeth/src/components/Topnav.tsx b/apps/oeth/src/components/Topnav.tsx
index 035e03fae..703d55a8f 100644
--- a/apps/oeth/src/components/Topnav.tsx
+++ b/apps/oeth/src/components/Topnav.tsx
@@ -31,177 +31,187 @@ export function Topnav(props: BoxProps) {
useState(null);
return (
- ({
- position: 'fixed',
- top: 0,
- left: 0,
- width: 1,
- zIndex: theme.zIndex.appBar,
- backgroundColor: alpha(theme.palette.background.default, 0.6),
- backdropFilter: 'blur(15px)',
- height: {
- xs: '112px',
- md: `${theme.mixins.toolbar.height}px`,
- },
- display: 'grid',
- borderBottom: {
- xs: 'none',
- md: `1px solid ${theme.palette.background.paper}`,
- },
- columnGap: { xs: 1, md: 10 },
- rowGap: { xs: 0, md: 10 },
- alignItems: 'center',
- px: {
- xs: 1.5,
- md: 3,
- },
- pt: {
- xs: 1.5,
- md: 0,
- },
- gridTemplateColumns: {
- xs: '1fr 1fr',
- md: 'auto 1fr auto',
- },
- })}
- >
+ <>
({
+ xs: '112px',
+ md: `${theme.mixins.toolbar.height}px`,
+ }),
+ }}
+ />
+ ({
- '& img': {
- maxHeight: {
- xs: '1rem',
- md: '1.5rem',
- },
- maxWidth: {
- xs: theme.typography.pxToRem(100),
- sm: theme.typography.pxToRem(120),
- md: theme.typography.pxToRem(180),
- },
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ width: 1,
+ zIndex: theme.zIndex.appBar,
+ backgroundColor: alpha(theme.palette.background.default, 0.6),
+ backdropFilter: 'blur(15px)',
+ height: {
+ xs: '112px',
+ md: `${theme.mixins.toolbar.height}px`,
},
- })}
- >
-
-
- {
- navigate(value);
- }}
- sx={{
- order: {
- xs: 2,
- md: 0,
+ display: 'grid',
+ borderBottom: {
+ xs: 'none',
+ md: `1px solid ${theme.palette.background.paper}`,
},
- gridColumn: {
- xs: 'span 2',
- md: 'span 1',
+ columnGap: { xs: 1, md: 10 },
+ rowGap: { xs: 0, md: 10 },
+ alignItems: 'center',
+ px: {
+ xs: 1.5,
+ md: 3,
},
- marginBlockStart: {
- xs: 2,
+ pt: {
+ xs: 1.5,
md: 0,
},
- '& .MuiTabs-flexContainer': {
- justifyContent: {
- xs: 'center',
- md: 'flex-start',
- },
+ gridTemplateColumns: {
+ xs: '1fr 1fr',
+ md: 'auto 1fr auto',
},
- }}
- >
- {routes[0].children.map((route) => (
-
- ))}
-
-
- ({
+ '& img': {
+ maxHeight: {
+ xs: '1rem',
+ md: '1.5rem',
+ },
+ maxWidth: {
+ xs: theme.typography.pxToRem(100),
+ sm: theme.typography.pxToRem(120),
+ md: theme.typography.pxToRem(180),
+ },
+ },
+ })}
+ >
+
+
+ {
+ navigate(value);
+ }}
sx={{
- borderRadius: 25,
- paddingX: {
- md: 3,
+ order: {
xs: 2,
+ md: 0,
},
- paddingY: {
- md: 1,
- xs: 0.75,
+ gridColumn: {
+ xs: 'span 2',
+ md: 'span 1',
},
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- fontWeight: 500,
- minHeight: { xs: 36, md: 44 },
- background: `linear-gradient(0deg, ${alpha(
- theme.palette.common.white,
- 0.05,
- )} 0%, ${alpha(theme.palette.common.white, 0.05)} 100%), ${
- theme.palette.background.paper
- };`,
- '&:hover': {
- background: (theme) => theme.palette.background.paper,
+ marginBlockStart: {
+ xs: 2,
+ md: 0,
+ },
+ '& .MuiTabs-flexContainer': {
+ justifyContent: {
+ xs: 'center',
+ md: 'flex-start',
+ },
},
}}
>
- {isMd
- ? intl.formatMessage({ defaultMessage: 'IPFS' })
- : intl.formatMessage({ defaultMessage: 'View on IPFS' })}
-
- {
- if (isConnected) {
- setAccountModalAnchor(e.currentTarget);
- }
+ {routes[0].children.map((route) => (
+
+ ))}
+
+
+ theme.palette.background.paper,
+ },
+ }}
+ >
+ {isMd
+ ? intl.formatMessage({ defaultMessage: 'IPFS' })
+ : intl.formatMessage({ defaultMessage: 'View on IPFS' })}
+
+ {
+ if (isConnected) {
+ setAccountModalAnchor(e.currentTarget);
+ }
+ }}
+ sx={{
+ borderRadius: 25,
+ paddingX: {
+ md: 3,
+ xs: 2,
+ },
+ paddingY: {
+ md: 1,
+ xs: 0.75,
+ },
+ minWidth: 36,
+ maxWidth: { xs: isConnected ? 36 : 160, sm: 160, lg: 220 },
+ fontWeight: 500,
+ minHeight: { xs: 36, md: 44 },
+ }}
+ />
+
+
+ theme.palette.background.paper,
+ position: 'relative',
+ width: 'calc(100% + 1.5rem)',
+ bottom: '-3rem',
+ left: '-0.75rem',
}}
/>
-
- theme.palette.background.paper,
- position: 'relative',
- width: 'calc(100% + 1.5rem)',
- bottom: '-3rem',
- left: '-0.75rem',
- }}
- />
-
+ >
);
}
diff --git a/libs/oeth/history/src/components/APYContainer.tsx b/libs/oeth/history/src/components/APYContainer.tsx
index 3b5e28f5e..a78598026 100644
--- a/libs/oeth/history/src/components/APYContainer.tsx
+++ b/libs/oeth/history/src/components/APYContainer.tsx
@@ -53,7 +53,7 @@ export function APYContainer() {
- {intl.formatMessage({ defaultMessage: 'OETH transactions' })}
+ {intl.formatMessage({ defaultMessage: 'OETH Transactions' })}
-
+
{table.getHeaderGroups().map((headerGroup) => (
{
);
};
-export const useHandleSlippageChange = () => {
- const [, setRedeemState] = useRedeemState();
-
- return useCallback(
- (value: number) => {
- setRedeemState(
- produce((state) => {
- state.slippage = value;
- }),
- );
- },
- [setRedeemState],
- );
-};
-
export const useHandleRedeem = () => {
const intl = useIntl();
+ const { value: slippage } = useSlippage();
const pushNotification = usePushNotification();
const { address } = useAccount();
- const [{ amountIn, amountOut, slippage }, setRedeemState] = useRedeemState();
+ const [{ amountIn, amountOut }, setRedeemState] = useRedeemState();
const wagmiClient = useQueryClient();
return useCallback(async () => {
diff --git a/libs/oeth/redeem/src/state.ts b/libs/oeth/redeem/src/state.ts
index 9591b9f82..a15ae9d57 100644
--- a/libs/oeth/redeem/src/state.ts
+++ b/libs/oeth/redeem/src/state.ts
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { contracts, tokens, whales } from '@origin/shared/contracts';
-import { usePushNotification } from '@origin/shared/providers';
+import { usePushNotification, useSlippage } from '@origin/shared/providers';
import { isNilOrEmpty } from '@origin/shared/utils';
import { useDebouncedEffect } from '@react-hookz/web';
import { useQuery, useQueryClient } from '@tanstack/react-query';
@@ -23,13 +23,13 @@ export const { Provider: RedeemProvider, useTracked: useRedeemState } =
split: [],
gas: 0n,
rate: 0,
- slippage: 0.01,
isEstimateLoading: false,
isRedeemLoading: false,
});
const intl = useIntl();
const queryClient = useQueryClient();
const pushNotification = usePushNotification();
+ const { value: slippage } = useSlippage();
const { data: splitAddresses } = useQuery({
queryKey: ['assetsDecimals'],
@@ -123,7 +123,7 @@ export const { Provider: RedeemProvider, useTracked: useRedeemState } =
const minAmountOut = parseUnits(
(
+formatUnits(total, MIX_TOKEN.decimals) -
- +formatUnits(total, MIX_TOKEN.decimals) * state.slippage
+ +formatUnits(total, MIX_TOKEN.decimals) * slippage
).toString(),
MIX_TOKEN.decimals,
);
diff --git a/libs/oeth/redeem/src/types.ts b/libs/oeth/redeem/src/types.ts
index 415f58999..6a50aca6c 100644
--- a/libs/oeth/redeem/src/types.ts
+++ b/libs/oeth/redeem/src/types.ts
@@ -11,7 +11,6 @@ export type RedeemState = {
split: RedeemEstimate[];
gas: bigint;
rate: number;
- slippage: number;
isEstimateLoading: boolean;
isRedeemLoading: boolean;
};
diff --git a/libs/oeth/redeem/src/views/RedeemView.tsx b/libs/oeth/redeem/src/views/RedeemView.tsx
index 159d12084..382796cac 100644
--- a/libs/oeth/redeem/src/views/RedeemView.tsx
+++ b/libs/oeth/redeem/src/views/RedeemView.tsx
@@ -11,17 +11,17 @@ import {
import { GasPopover } from '@origin/oeth/shared';
import { TokenInput } from '@origin/shared/components';
import { tokens } from '@origin/shared/contracts';
-import { ConnectedButton, usePrices } from '@origin/shared/providers';
+import {
+ ConnectedButton,
+ usePrices,
+ useSlippage,
+} from '@origin/shared/providers';
import { composeContexts } from '@origin/shared/utils';
import { useIntl } from 'react-intl';
import { useAccount, useBalance } from 'wagmi';
import { RedeemRoute } from '../components/RedeemRoute';
-import {
- useHandleAmountInChange,
- useHandleRedeem,
- useHandleSlippageChange,
-} from '../hooks';
+import { useHandleAmountInChange, useHandleRedeem } from '../hooks';
import { RedeemProvider, useRedeemState } from '../state';
import type { BoxProps } from '@mui/material';
@@ -55,9 +55,9 @@ export const RedeemView = () =>
function RedeemViewWrapped() {
const intl = useIntl();
+ const { value: slippage, set: setSlippage } = useSlippage();
const { address, isConnected } = useAccount();
- const [{ amountIn, slippage, isRedeemLoading, isEstimateLoading }] =
- useRedeemState();
+ const [{ amountIn, isRedeemLoading, isEstimateLoading }] = useRedeemState();
const { data: prices, isLoading: isPricesLoading } = usePrices();
const { data: balOeth, isLoading: isBalOethLoading } = useBalance({
address,
@@ -65,16 +65,20 @@ function RedeemViewWrapped() {
watch: true,
scopeKey: 'redeem_balance',
});
- const handleSlippageChange = useHandleSlippageChange();
+
const handleAmountInChange = useHandleAmountInChange();
const handleRedeem = useHandleRedeem();
+ const handleSlippageChange = (val: number) => {
+ setSlippage(val);
+ };
+
const redeemButtonLabel =
amountIn === 0n
? intl.formatMessage({ defaultMessage: 'Enter an amount' })
: amountIn > balOeth?.value
? intl.formatMessage({ defaultMessage: 'Insufficient funds' })
- : intl.formatMessage({ defaultMessage: 'Redeem for mix' });
+ : intl.formatMessage({ defaultMessage: 'Redeem' });
const redeemButtonDisabled =
isBalOethLoading ||
isEstimateLoading ||
diff --git a/libs/oeth/shared/src/components/AccountPopover.tsx b/libs/oeth/shared/src/components/AccountPopover.tsx
index 3cac0c061..21c09a77d 100644
--- a/libs/oeth/shared/src/components/AccountPopover.tsx
+++ b/libs/oeth/shared/src/components/AccountPopover.tsx
@@ -20,6 +20,8 @@ import type { StackProps } from '@mui/material';
import type { Token } from '@origin/shared/contracts';
const balanceTokens = [
+ tokens.mainnet.OETH,
+ tokens.mainnet.WOETH,
tokens.mainnet.WETH,
tokens.mainnet.rETH,
tokens.mainnet.frxETH,
diff --git a/libs/oeth/shared/src/components/GasPopover.tsx b/libs/oeth/shared/src/components/GasPopover.tsx
index 7a7cdab56..510e85e97 100644
--- a/libs/oeth/shared/src/components/GasPopover.tsx
+++ b/libs/oeth/shared/src/components/GasPopover.tsx
@@ -14,7 +14,7 @@ import {
Stack,
useTheme,
} from '@mui/material';
-import { PercentInput } from '@origin/shared/components';
+import { InfoTooltip, PercentInput } from '@origin/shared/components';
import { useIntl } from 'react-intl';
import { useFeeData } from 'wagmi';
@@ -89,8 +89,18 @@ export function GasPopover({
>
-
+
{intl.formatMessage({ defaultMessage: 'Price Tolerance' })}
+
diff --git a/libs/oeth/swap/src/components/ApyHeader.tsx b/libs/oeth/swap/src/components/ApyHeader.tsx
index 04c01e999..d2bffbfcc 100644
--- a/libs/oeth/swap/src/components/ApyHeader.tsx
+++ b/libs/oeth/swap/src/components/ApyHeader.tsx
@@ -94,19 +94,21 @@ export const ApyHeader = (props: StackProps) => {
}}
MenuListProps={{ dense: true }}
>
- {trailingOptions.map((t) => (
-
- ))}
+ {trailingOptions
+ .filter((t) => t.value !== trailing.value)
+ .map((t) => (
+
+ ))}
diff --git a/libs/oeth/swap/src/components/BestRoutes.tsx b/libs/oeth/swap/src/components/BestRoutes.tsx
index 9ca8a76d3..8de1bc56e 100644
--- a/libs/oeth/swap/src/components/BestRoutes.tsx
+++ b/libs/oeth/swap/src/components/BestRoutes.tsx
@@ -17,7 +17,7 @@ export function BestRoutes(props: Grid2Props) {
return (
{swapRoutes.slice(0, 2).map((route, index) => (
-
+
`1px solid ${theme.palette.grey[800]}`,
- borderRadius: 1,
+ border: `1px solid transparent`,
+ position: 'relative',
height: 1,
...(isSelected
? {
@@ -100,110 +91,109 @@ export function SwapRouteCard({
...rest?.sx,
}}
role="button"
- onClick={() => onSelect(route)}
+ onClick={() => {
+ onSelect(route);
+ }}
>
-
-
+ {isBest && (
+ theme.shape.borderRadius,
+ background: (theme) => theme.palette.background.gradient1,
+ fontSize: 12,
+ top: 0,
+ right: 0,
+ px: 1,
+ pt: 0.25,
+ }}
+ >
+ {intl.formatMessage({ defaultMessage: 'Best' })}
+
+ )}
+
+
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+
+
{isLoading ? (
-
+
) : (
-
+ formatAmount(route.estimatedAmount, route.tokenOut.decimals)
)}
-
-
-
+
+
+
+
+ {isLoading ? (
+
+ ) : (
+ `(${intl.formatNumber(convertedAmount, currencyFormat)})`
+ )}
+
+
+
+
+
+ {isLoading ? (
+
+ ) : (
+ intl.formatMessage(routeActionLabel[route.action])
+ )}
+
+
+
+
+ {intl.formatMessage({ defaultMessage: 'Rate:' })}
+
+
{isLoading ? (
-
+
) : (
- formatAmount(route.estimatedAmount, route.tokenOut.decimals)
+ `1:${intl.formatNumber(route.rate, quantityFormat)}`
)}
-
-
-
- {isLoading ? (
+
+
+
+ {intl.formatMessage({ defaultMessage: 'Gas:' })}
+
+
+ {isGasLoading ? (
) : (
- `(${intl.formatNumber(convertedAmount, currencyFormat)})`
+ `~${intl.formatNumber(gasPrice, currencyFormat)}`
)}
-
-
- {isBest && (
- theme.shape.borderRadius,
- background: (theme) => theme.palette.background.gradient1,
- fontSize: 12,
- top: (theme) => theme.spacing(-3),
- right: (theme) => theme.spacing(-1.25),
- px: 1,
- pt: 0.25,
- }}
- >
- {intl.formatMessage({ defaultMessage: 'Best' })}
-
- )}
-
- }
- >
-
-
- {isLoading ? (
-
- ) : (
- intl.formatMessage(routeActionLabel[route.action])
- )}
-
-
-
-
- {intl.formatMessage({ defaultMessage: 'Rate:' })}
-
-
- {isLoading ? (
-
- ) : (
- `1:${intl.formatNumber(route.rate, quantityFormat)}`
- )}
-
-
-
-
- {intl.formatMessage({ defaultMessage: 'Gas:' })}
-
-
- {isGasLoading ? (
-
- ) : (
- `~${intl.formatNumber(gasPrice, currencyFormat)}`
- )}
-
+
+
diff --git a/libs/oeth/swap/src/constants.ts b/libs/oeth/swap/src/constants.ts
index 5f630efdc..55623e682 100644
--- a/libs/oeth/swap/src/constants.ts
+++ b/libs/oeth/swap/src/constants.ts
@@ -19,14 +19,26 @@ export const routeActionLabel: Record = {
'mint-vault': defineMessage({ defaultMessage: 'Mint with Vault' }),
'swap-curve': defineMessage({ defaultMessage: 'Swap with Curve' }),
'swap-curve-eth': defineMessage({ defaultMessage: 'Swap with CurvePool' }),
- 'swap-zapper-eth': defineMessage({ defaultMessage: 'Zap + Mint with Vault' }),
+ 'swap-zapper-eth': defineMessage({ defaultMessage: 'Mint with Vault' }),
'swap-zapper-sfrxeth': defineMessage({
- defaultMessage: 'Zap + Mint with Vault',
+ defaultMessage: 'Mint with Vault',
}),
'unwrap-woeth': defineMessage({ defaultMessage: 'Unwrap with Origin' }),
'wrap-oeth': defineMessage({ defaultMessage: 'Wrap with Origin' }),
};
+export const buttonActionLabel: Record = {
+ 'mint-vault': defineMessage({ defaultMessage: 'Mint' }),
+ 'swap-curve': defineMessage({ defaultMessage: 'Swap' }),
+ 'swap-curve-eth': defineMessage({ defaultMessage: 'Swap' }),
+ 'swap-zapper-eth': defineMessage({ defaultMessage: 'Mint' }),
+ 'swap-zapper-sfrxeth': defineMessage({
+ defaultMessage: 'Mint',
+ }),
+ 'unwrap-woeth': defineMessage({ defaultMessage: 'Unwrap' }),
+ 'wrap-oeth': defineMessage({ defaultMessage: 'Wrap' }),
+};
+
export const swapRoutes = [
// Mint
{
diff --git a/libs/oeth/swap/src/hooks.ts b/libs/oeth/swap/src/hooks.ts
index 2508f7ac7..dd95fbc14 100644
--- a/libs/oeth/swap/src/hooks.ts
+++ b/libs/oeth/swap/src/hooks.ts
@@ -1,6 +1,10 @@
import { useCallback, useMemo } from 'react';
-import { useCurve, usePushNotification } from '@origin/shared/providers';
+import {
+ useCurve,
+ usePushNotification,
+ useSlippage,
+} from '@origin/shared/providers';
import { isNilOrEmpty } from '@origin/shared/utils';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { produce } from 'immer';
@@ -22,8 +26,10 @@ export const useHandleAmountInChange = () => {
(amount: bigint) => {
setSwapState(
produce((state) => {
- state.amountIn = amount;
- state.isSwapRoutesLoading = amount !== 0n;
+ if (state.amountIn !== amount) {
+ state.amountIn = amount;
+ state.isSwapRoutesLoading = amount !== 0n;
+ }
}),
);
},
@@ -166,21 +172,6 @@ export const useSwapRouteAllowance = (route: SwapRoute) => {
});
};
-export const useHandleSlippageChange = () => {
- const [, setSwapState] = useSwapState();
-
- return useCallback(
- (value: number) => {
- setSwapState(
- produce((state) => {
- state.slippage = value;
- }),
- );
- },
- [setSwapState],
- );
-};
-
export const useHandleApprove = () => {
const intl = useIntl();
const { address } = useAccount();
@@ -263,13 +254,14 @@ export const useHandleApprove = () => {
export const useHandleSwap = () => {
const intl = useIntl();
+ const { value: slippage } = useSlippage();
const { address } = useAccount();
const curve = useCurve();
const queryClient = useQueryClient();
const wagmiClient = useWagmiClient();
const pushNotification = usePushNotification();
const [
- { amountIn, amountOut, selectedSwapRoute, slippage, tokenIn, tokenOut },
+ { amountIn, amountOut, selectedSwapRoute, tokenIn, tokenOut },
setSwapState,
] = useSwapState();
@@ -302,35 +294,25 @@ export const useHandleSwap = () => {
title: intl.formatMessage({ defaultMessage: 'Swap complete' }),
severity: 'success',
});
- setSwapState(
- produce((draft) => {
- draft.isSwapLoading = false;
- }),
- );
},
onError: () => {
pushNotification({
title: intl.formatMessage({ defaultMessage: 'Swap failed' }),
severity: 'error',
});
- setSwapState(
- produce((draft) => {
- draft.isSwapLoading = false;
- }),
- );
},
onReject: () => {
pushNotification({
title: intl.formatMessage({ defaultMessage: 'Swap cancelled' }),
severity: 'info',
});
- setSwapState(
- produce((draft) => {
- draft.isSwapLoading = false;
- }),
- );
},
});
+ setSwapState(
+ produce((draft) => {
+ draft.isSwapLoading = false;
+ }),
+ );
}, [
address,
amountIn,
diff --git a/libs/oeth/swap/src/state.ts b/libs/oeth/swap/src/state.ts
index 089de3d8d..0e60a175f 100644
--- a/libs/oeth/swap/src/state.ts
+++ b/libs/oeth/swap/src/state.ts
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { tokens } from '@origin/shared/contracts';
-import { useCurve } from '@origin/shared/providers';
+import { useCurve, useSlippage } from '@origin/shared/providers';
import { useDebouncedEffect } from '@react-hookz/web';
import { useQueryClient } from '@tanstack/react-query';
import { produce } from 'immer';
@@ -21,13 +21,13 @@ export const { Provider: SwapProvider, useTracked: useSwapState } =
tokenOut: tokens.mainnet.OETH,
swapRoutes: [],
selectedSwapRoute: null,
- slippage: 0.001,
isSwapRoutesLoading: false,
isApproved: false,
isApprovalLoading: false,
isSwapLoading: false,
});
const queryClient = useQueryClient();
+ const { value: slippage } = useSlippage();
const { CurveRegistryExchange, OethPoolUnderlyings } = useCurve();
useDebouncedEffect(
@@ -55,7 +55,7 @@ export const { Provider: SwapProvider, useTracked: useSwapState } =
state.tokenIn.symbol,
state.tokenOut.symbol,
route.action,
- state.slippage,
+ slippage,
state.amountIn.toString(),
] as const,
queryFn: async () =>
@@ -65,7 +65,7 @@ export const { Provider: SwapProvider, useTracked: useSwapState } =
amountIn: state.amountIn,
amountOut: state.amountOut,
route,
- slippage: state.slippage,
+ slippage,
curve: {
CurveRegistryExchange,
OethPoolUnderlyings,
diff --git a/libs/oeth/swap/src/types.ts b/libs/oeth/swap/src/types.ts
index 060b23520..4cf5e9db0 100644
--- a/libs/oeth/swap/src/types.ts
+++ b/libs/oeth/swap/src/types.ts
@@ -122,7 +122,6 @@ export type SwapState = {
tokenOut: Token;
swapRoutes: EstimatedSwapRoute[];
selectedSwapRoute: EstimatedSwapRoute | null;
- slippage: number;
isSwapRoutesLoading: boolean;
isApproved: boolean;
isApprovalLoading: boolean;
diff --git a/libs/oeth/swap/src/views/SwapView.tsx b/libs/oeth/swap/src/views/SwapView.tsx
index a7417ccfa..285c7df93 100644
--- a/libs/oeth/swap/src/views/SwapView.tsx
+++ b/libs/oeth/swap/src/views/SwapView.tsx
@@ -15,19 +15,22 @@ import {
} from '@mui/material';
import { GasPopover } from '@origin/oeth/shared';
import { TokenInput } from '@origin/shared/components';
-import { ConnectedButton, usePrices } from '@origin/shared/providers';
+import {
+ ConnectedButton,
+ usePrices,
+ useSlippage,
+} from '@origin/shared/providers';
import { composeContexts, isNilOrEmpty } from '@origin/shared/utils';
import { useIntl } from 'react-intl';
-import { useAccount, useBalance } from 'wagmi';
+import { mainnet, useAccount, useBalance, useNetwork } from 'wagmi';
import { ApyHeader } from '../components/ApyHeader';
import { SwapRoute } from '../components/SwapRoute';
import { TokenSelectModal } from '../components/TokenSelectModal';
-import { routeActionLabel } from '../constants';
+import { buttonActionLabel } from '../constants';
import {
useHandleAmountInChange,
useHandleApprove,
- useHandleSlippageChange,
useHandleSwap,
useHandleTokenChange,
useHandleTokenFlip,
@@ -69,7 +72,9 @@ export const SwapView = () =>
function SwapViewWrapped() {
const intl = useIntl();
+ const { value: slippage, set: setSlippage } = useSlippage();
const { address, isConnected } = useAccount();
+ const { chain } = useNetwork();
const [tokenSource, setTokenSource] = useState(null);
const [
{
@@ -78,7 +83,6 @@ function SwapViewWrapped() {
tokenIn,
tokenOut,
selectedSwapRoute,
- slippage,
isSwapLoading,
isSwapRoutesLoading,
isApprovalLoading,
@@ -99,7 +103,6 @@ function SwapViewWrapped() {
watch: true,
scopeKey: 'swap_balance',
});
- const handleSlippageChange = useHandleSlippageChange();
const handleAmountInChange = useHandleAmountInChange();
const handleTokenChange = useHandleTokenChange();
const handleTokenFlip = useHandleTokenFlip();
@@ -114,6 +117,10 @@ function SwapViewWrapped() {
handleTokenChange(tokenSource, value);
};
+ const handleSlippageChange = (val: number) => {
+ setSlippage(val);
+ };
+
const needsApproval =
isConnected &&
amountIn > 0n &&
@@ -128,7 +135,7 @@ function SwapViewWrapped() {
: amountIn > balTokenIn?.value
? intl.formatMessage({ defaultMessage: 'Insufficient funds' })
: !isNilOrEmpty(selectedSwapRoute)
- ? intl.formatMessage(routeActionLabel[selectedSwapRoute?.action])
+ ? intl.formatMessage(buttonActionLabel[selectedSwapRoute?.action])
: '';
const amountInInputDisabled = isSwapLoading || isApprovalLoading;
const approveButtonDisabled =
@@ -188,6 +195,10 @@ function SwapViewWrapped() {
onTokenClick={() => {
setTokenSource('tokenIn');
}}
+ isNativeCurrency={
+ tokenIn.symbol ===
+ (chain?.nativeCurrency.symbol ?? mainnet.nativeCurrency.symbol)
+ }
tokenPriceUsd={prices?.[tokenIn.symbol]}
isPriceLoading={isPriceLoading}
isConnected={isConnected}
@@ -233,6 +244,10 @@ function SwapViewWrapped() {
onTokenClick={() => {
setTokenSource('tokenOut');
}}
+ isNativeCurrency={
+ tokenOut.symbol ===
+ (chain?.nativeCurrency.symbol ?? mainnet.nativeCurrency.symbol)
+ }
tokenPriceUsd={prices?.[tokenOut.symbol]}
isPriceLoading={isSwapRoutesLoading || isPriceLoading}
isConnected={isConnected}
diff --git a/libs/shared/components/src/InfoTooltip/index.tsx b/libs/shared/components/src/InfoTooltip/index.tsx
index c625bb3c9..94bc25eb7 100644
--- a/libs/shared/components/src/InfoTooltip/index.tsx
+++ b/libs/shared/components/src/InfoTooltip/index.tsx
@@ -1,15 +1,18 @@
import { Box, Tooltip, Typography } from '@mui/material';
+import type { BoxProps } from '@mui/material';
+
export type InfoTooltipProps = {
tooltipLabel: string;
iconSize?: number;
iconColor?: string;
-};
+} & BoxProps;
export function InfoTooltip({
tooltipLabel,
iconSize = 12,
iconColor = 'text.secondary',
+ ...rest
}: InfoTooltipProps) {
return (
theme.typography.pxToRem(iconSize),
height: (theme) => theme.typography.pxToRem(iconSize),
color: iconColor,
+
+ ...rest?.sx,
}}
>
diff --git a/libs/shared/components/src/Inputs/TokenInput.tsx b/libs/shared/components/src/Inputs/TokenInput.tsx
index 178340083..3615606ee 100644
--- a/libs/shared/components/src/Inputs/TokenInput.tsx
+++ b/libs/shared/components/src/Inputs/TokenInput.tsx
@@ -7,7 +7,7 @@ import {
isNilOrEmpty,
} from '@origin/shared/utils';
import { useIntl } from 'react-intl';
-import { formatUnits } from 'viem';
+import { formatUnits, parseEther } from 'viem';
import { BigIntInput } from './BigIntInput';
@@ -16,6 +16,8 @@ import type { Token } from '@origin/shared/contracts';
import type { BigintInputProps } from './BigIntInput';
+const MIN_ETH_FOR_GAS = '0.015';
+
export type TokenInputProps = {
amount: bigint;
decimals?: number;
@@ -30,6 +32,7 @@ export type TokenInputProps = {
disableMaxButton?: boolean;
token: Token;
onTokenClick?: () => void;
+ isNativeCurrency?: boolean;
isTokenClickDisabled?: boolean;
tokenPriceUsd?: number;
isPriceLoading?: boolean;
@@ -56,6 +59,7 @@ export const TokenInput = forwardRef(
disableMaxButton,
token,
onTokenClick,
+ isNativeCurrency = false,
isTokenClickDisabled,
tokenPriceUsd = 0,
isPriceLoading,
@@ -68,10 +72,16 @@ export const TokenInput = forwardRef(
const intl = useIntl();
const handleMaxClick = () => {
- onAmountChange(balance);
+ const max = isNativeCurrency
+ ? balance - parseEther(MIN_ETH_FOR_GAS)
+ : balance;
+ onAmountChange(max);
};
const amountUsd = +formatUnits(amount, decimals) * tokenPriceUsd;
+ const maxVisible =
+ !hideMaxButton &&
+ (isNativeCurrency ? balance > parseEther(MIN_ETH_FOR_GAS) : true);
const maxDisabled = disableMaxButton || !isConnected || isBalanceLoading;
return (
@@ -148,7 +158,7 @@ export const TokenInput = forwardRef(
},
)}
- {!hideMaxButton && (
+ {maxVisible && (