diff --git a/.eslintignore b/.eslintignore
index f06235c46..bfe0ccf2b 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,2 +1,3 @@
node_modules
dist
+*.generated.ts
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
index 9559167ba..2aa46d632 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -18,7 +18,6 @@
"@nx/enforce-module-boundaries": [
"error",
{
- "enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
diff --git a/apps/oeth/src/components/Topnav.tsx b/apps/oeth/src/components/Topnav.tsx
index 703d55a8f..c3f407eb1 100644
--- a/apps/oeth/src/components/Topnav.tsx
+++ b/apps/oeth/src/components/Topnav.tsx
@@ -10,7 +10,7 @@ import {
useMediaQuery,
useTheme,
} from '@mui/material';
-import { AccountPopover } from '@origin/oeth/shared';
+import { AccountPopover, ActivityButton } from '@origin/oeth/shared';
import { OpenAccountModalButton } from '@origin/shared/providers';
import { useIntl } from 'react-intl';
import { Link, useLocation, useNavigate } from 'react-router-dom';
@@ -198,6 +198,16 @@ export function Topnav(props: BoxProps) {
anchor={accountModalAnchor}
setAnchor={setAccountModalAnchor}
/>
+
,
diff --git a/libs/oeth/history/src/queries.generated.ts b/libs/oeth/history/src/queries.generated.ts
index eb3e0b9ca..cc8d19dcb 100644
--- a/libs/oeth/history/src/queries.generated.ts
+++ b/libs/oeth/history/src/queries.generated.ts
@@ -1,40 +1,21 @@
-import { graphqlClient } from '@origin/oeth/shared';
-import { useQuery } from '@tanstack/react-query';
+import * as Types from '@origin/oeth/shared';
-import type * as Types from '@origin/oeth/shared';
-import type { UseQueryOptions } from '@tanstack/react-query';
+import { useQuery, UseQueryOptions } from '@tanstack/react-query';
+import { graphqlClient } from '@origin/oeth/shared';
export type HistoryPageQueryVariables = Types.Exact<{
address: Types.Scalars['String']['input'];
offset: Types.Scalars['Int']['input'];
filters?: Types.InputMaybe | Types.HistoryType>;
}>;
-export type HistoryPageQuery = {
- __typename?: 'Query';
- addresses: Array<{
- __typename?: 'Address';
- balance: string;
- earned: string;
- isContract: boolean;
- rebasingOption: Types.RebasingOption;
- lastUpdated: string;
- history: Array<{
- __typename?: 'History';
- type: Types.HistoryType;
- value: string;
- txHash: string;
- timestamp: string;
- balance: string;
- }>;
- }>;
-};
-export type HistoryApyQueryVariables = Types.Exact<{ [key: string]: never }>;
+export type HistoryPageQuery = { __typename?: 'Query', addresses: Array<{ __typename?: 'Address', balance: string, earned: string, isContract: boolean, rebasingOption: Types.RebasingOption, lastUpdated: string, history: Array<{ __typename?: 'History', type: Types.HistoryType, value: string, txHash: string, timestamp: string, balance: string }> }> };
+
+export type HistoryApyQueryVariables = Types.Exact<{ [key: string]: never; }>;
+
+
+export type HistoryApyQuery = { __typename?: 'Query', apies: Array<{ __typename?: 'APY', apy7DayAvg: number, apy30DayAvg: number }> };
-export type HistoryApyQuery = {
- __typename?: 'Query';
- apies: Array<{ __typename?: 'APY'; apy7DayAvg: number; apy30DayAvg: number }>;
-};
export const HistoryPageDocument = `
query HistoryPage($address: String!, $offset: Int!, $filters: [HistoryType!]) {
@@ -59,32 +40,23 @@ export const HistoryPageDocument = `
}
}
`;
-export const useHistoryPageQuery = (
- variables: HistoryPageQueryVariables,
- options?: UseQueryOptions,
-) =>
- useQuery(
- ['HistoryPage', variables],
- graphqlClient(
- HistoryPageDocument,
- variables,
- ),
- options,
- );
+export const useHistoryPageQuery = <
+ TData = HistoryPageQuery,
+ TError = unknown
+ >(
+ variables: HistoryPageQueryVariables,
+ options?: UseQueryOptions
+ ) =>
+ useQuery(
+ ['HistoryPage', variables],
+ graphqlClient(HistoryPageDocument, variables),
+ options
+ );
+
+useHistoryPageQuery.getKey = (variables: HistoryPageQueryVariables) => ['HistoryPage', variables];
+;
-useHistoryPageQuery.getKey = (variables: HistoryPageQueryVariables) => [
- 'HistoryPage',
- variables,
-];
-useHistoryPageQuery.fetcher = (
- variables: HistoryPageQueryVariables,
- options?: RequestInit['headers'],
-) =>
- graphqlClient(
- HistoryPageDocument,
- variables,
- options,
- );
+useHistoryPageQuery.fetcher = (variables: HistoryPageQueryVariables, options?: RequestInit['headers']) => graphqlClient(HistoryPageDocument, variables, options);
export const HistoryApyDocument = `
query HistoryApy {
apies(limit: 1, orderBy: timestamp_DESC) {
@@ -93,27 +65,20 @@ export const HistoryApyDocument = `
}
}
`;
-export const useHistoryApyQuery = (
- variables?: HistoryApyQueryVariables,
- options?: UseQueryOptions,
-) =>
- useQuery(
- variables === undefined ? ['HistoryApy'] : ['HistoryApy', variables],
- graphqlClient(
- HistoryApyDocument,
- variables,
- ),
- options,
- );
+export const useHistoryApyQuery = <
+ TData = HistoryApyQuery,
+ TError = unknown
+ >(
+ variables?: HistoryApyQueryVariables,
+ options?: UseQueryOptions
+ ) =>
+ useQuery(
+ variables === undefined ? ['HistoryApy'] : ['HistoryApy', variables],
+ graphqlClient(HistoryApyDocument, variables),
+ options
+ );
+
+useHistoryApyQuery.getKey = (variables?: HistoryApyQueryVariables) => variables === undefined ? ['HistoryApy'] : ['HistoryApy', variables];
+;
-useHistoryApyQuery.getKey = (variables?: HistoryApyQueryVariables) =>
- variables === undefined ? ['HistoryApy'] : ['HistoryApy', variables];
-useHistoryApyQuery.fetcher = (
- variables?: HistoryApyQueryVariables,
- options?: RequestInit['headers'],
-) =>
- graphqlClient(
- HistoryApyDocument,
- variables,
- options,
- );
+useHistoryApyQuery.fetcher = (variables?: HistoryApyQueryVariables, options?: RequestInit['headers']) => graphqlClient(HistoryApyDocument, variables, options);
\ No newline at end of file
diff --git a/libs/oeth/redeem/src/hooks.tsx b/libs/oeth/redeem/src/hooks.tsx
index 58ff0c4e7..6f822edee 100644
--- a/libs/oeth/redeem/src/hooks.tsx
+++ b/libs/oeth/redeem/src/hooks.tsx
@@ -1,12 +1,16 @@
import { useCallback } from 'react';
-import { contracts } from '@origin/shared/contracts';
+import { Box } from '@mui/material';
import {
- BlockExplorerLink,
- usePushNotification,
- useSlippage,
-} from '@origin/shared/providers';
-import { isNilOrEmpty } from '@origin/shared/utils';
+ RedeemNotification,
+ useDeleteActivity,
+ usePushActivity,
+ useUpdateActivity,
+} from '@origin/oeth/shared';
+import { NotificationSnack } from '@origin/shared/components';
+import { contracts, tokens } from '@origin/shared/contracts';
+import { usePushNotification, useSlippage } from '@origin/shared/providers';
+import { isNilOrEmpty, isUserRejected } from '@origin/shared/utils';
import {
prepareWriteContract,
waitForTransaction,
@@ -40,6 +44,9 @@ export const useHandleRedeem = () => {
const intl = useIntl();
const { value: slippage } = useSlippage();
const pushNotification = usePushNotification();
+ const pushActivity = usePushActivity();
+ const updateActivity = useUpdateActivity();
+ const deleteActivity = useDeleteActivity();
const { address } = useAccount();
const [{ amountIn, amountOut }, setRedeemState] = useRedeemState();
const wagmiClient = useQueryClient();
@@ -49,6 +56,23 @@ export const useHandleRedeem = () => {
return;
}
+ const minAmountOut = parseUnits(
+ (
+ +formatUnits(amountOut, MIX_TOKEN.decimals) -
+ +formatUnits(amountOut, MIX_TOKEN.decimals) * slippage
+ ).toString(),
+ MIX_TOKEN.decimals,
+ );
+
+ const activity = pushActivity({
+ type: 'redeem',
+ status: 'pending',
+ tokenIn: tokens.mainnet.OETH,
+ tokenOut: MIX_TOKEN,
+ amountIn,
+ amountOut,
+ });
+
setRedeemState(
produce((draft) => {
draft.isRedeemLoading = true;
@@ -56,14 +80,6 @@ export const useHandleRedeem = () => {
);
try {
- const minAmountOut = parseUnits(
- (
- +formatUnits(amountOut, MIX_TOKEN.decimals) -
- +formatUnits(amountOut, MIX_TOKEN.decimals) * slippage
- ).toString(),
- MIX_TOKEN.decimals,
- );
-
const { request } = await prepareWriteContract({
address: contracts.mainnet.OETHVaultCore.address,
abi: contracts.mainnet.OETHVaultCore.abi,
@@ -71,43 +87,67 @@ export const useHandleRedeem = () => {
args: [amountIn, minAmountOut],
});
const { hash } = await writeContract(request);
+ setRedeemState(
+ produce((draft) => {
+ draft.isRedeemLoading = false;
+ }),
+ );
const txReceipt = await waitForTransaction({ hash });
-
- console.log('redeem vault done!');
wagmiClient.invalidateQueries({ queryKey: ['redeem_balance'] });
+ updateActivity({ ...activity, status: 'success', txReceipt });
pushNotification({
- title: intl.formatMessage({ defaultMessage: 'Redeem complete' }),
- severity: 'success',
- content: ,
+ content: (
+
+ ),
});
- } catch (e) {
- console.error(`redeem vault error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED') {
+ } catch (error) {
+ if (isUserRejected(error)) {
+ deleteActivity(activity.id);
pushNotification({
- title: intl.formatMessage({ defaultMessage: 'Redeem vault' }),
- severity: 'info',
+ content: (
+ }
+ title={intl.formatMessage({
+ defaultMessage: 'Operation Cancelled',
+ })}
+ subtitle={intl.formatMessage({
+ defaultMessage: 'User rejected operation',
+ })}
+ />
+ ),
});
} else {
+ updateActivity({
+ ...activity,
+ status: 'error',
+ error: error?.shortMessage ?? error.message,
+ });
pushNotification({
- title: intl.formatMessage({ defaultMessage: 'Redeem vault' }),
- severity: 'error',
+ content: (
+
+ ),
});
}
}
-
- setRedeemState(
- produce((draft) => {
- draft.isRedeemLoading = false;
- }),
- );
}, [
address,
amountIn,
amountOut,
+ deleteActivity,
intl,
+ pushActivity,
pushNotification,
setRedeemState,
slippage,
+ updateActivity,
wagmiClient,
]);
};
diff --git a/libs/oeth/redeem/src/state.ts b/libs/oeth/redeem/src/state.ts
index a15ae9d57..f428a44ee 100644
--- a/libs/oeth/redeem/src/state.ts
+++ b/libs/oeth/redeem/src/state.ts
@@ -77,7 +77,7 @@ export const { Provider: RedeemProvider, useTracked: useRedeemState } =
try {
splitEstimates = await queryClient.fetchQuery({
queryKey: ['splitEstimates', state.amountIn.toString()],
- queryFn: () =>
+ queryFn: async () =>
readContract({
address: contracts.mainnet.OETHVaultCore.address,
abi: contracts.mainnet.OETHVaultCore.abi,
@@ -85,8 +85,8 @@ export const { Provider: RedeemProvider, useTracked: useRedeemState } =
args: [state.amountIn],
}),
});
- } catch (e) {
- console.error(`redeem vault estimate amount error.\n${e.message}`);
+ } catch (error) {
+ console.error(`Fail to estimate redeem operation.\n${error.message}`);
setState(
produce((draft) => {
draft.amountIn = 0n;
@@ -99,7 +99,7 @@ export const { Provider: RedeemProvider, useTracked: useRedeemState } =
title: intl.formatMessage({
defaultMessage: 'Error while estimating',
}),
- message: e.shortMessage,
+ message: error?.shortMessage ?? error.message,
severity: 'error',
});
@@ -145,10 +145,8 @@ export const { Provider: RedeemProvider, useTracked: useRedeemState } =
account: whales.mainnet.OETH,
}),
});
- } catch (e) {
- console.error(
- `redeem vault estimate gas error. Using default!\n${e.message}`,
- );
+ } catch (error) {
+ console.log(`Redeem uses fix gas estimate: 1500000`);
gasEstimate = 1500000n;
}
diff --git a/libs/oeth/redeem/src/views/RedeemView.tsx b/libs/oeth/redeem/src/views/RedeemView.tsx
index 382796cac..26fd8c606 100644
--- a/libs/oeth/redeem/src/views/RedeemView.tsx
+++ b/libs/oeth/redeem/src/views/RedeemView.tsx
@@ -8,7 +8,7 @@ import {
Stack,
Typography,
} from '@mui/material';
-import { GasPopover } from '@origin/oeth/shared';
+import { ApyHeader, GasPopover } from '@origin/oeth/shared';
import { TokenInput } from '@origin/shared/components';
import { tokens } from '@origin/shared/contracts';
import {
@@ -87,54 +87,63 @@ function RedeemViewWrapped() {
amountIn === 0n;
return (
-
-
-
- {intl.formatMessage({ defaultMessage: 'Redeem' })}
-
-
-
- }
- />
-
-
- `linear-gradient(${theme.palette.grey[900]}, ${
- theme.palette.grey[900]
- }) padding-box,
+ <>
+
+
+
+
+ {intl.formatMessage({ defaultMessage: 'Redeem' })}
+
+ theme.spacing(-0.75),
+ svg: { width: 16, height: 16 },
+ },
+ }}
+ />
+
+ }
+ />
+
+
+ `linear-gradient(${theme.palette.grey[900]}, ${
+ theme.palette.grey[900]
+ }) padding-box,
linear-gradient(90deg, ${alpha(
theme.palette.primary.main,
0.4,
@@ -142,41 +151,42 @@ function RedeemViewWrapped() {
theme.palette.primary.dark,
0.4,
)} 100%) border-box;`,
- },
- '&:focus-within': {
- background: (theme) =>
- `linear-gradient(${theme.palette.grey[900]}, ${theme.palette.grey[900]}) padding-box,
+ },
+ '&:focus-within': {
+ background: (theme) =>
+ `linear-gradient(${theme.palette.grey[900]}, ${theme.palette.grey[900]}) padding-box,
linear-gradient(90deg, var(--mui-palette-primary-main) 0%, var(--mui-palette-primary-dark) 100%) border-box;`,
- },
- }}
- />
-
-
-
-
-
- {isEstimateLoading ? (
-
- ) : isRedeemLoading ? (
- intl.formatMessage({ defaultMessage: 'Waiting for signature' })
- ) : (
- redeemButtonLabel
- )}
-
-
-
+ },
+ }}
+ />
+
+
+
+
+
+ {isEstimateLoading ? (
+
+ ) : isRedeemLoading ? (
+ intl.formatMessage({ defaultMessage: 'Waiting for signature' })
+ ) : (
+ redeemButtonLabel
+ )}
+
+
+
+ >
);
}
diff --git a/libs/oeth/shared/codegen.ts b/libs/oeth/shared/codegen.ts
index 144a7e360..4b67d5611 100644
--- a/libs/oeth/shared/codegen.ts
+++ b/libs/oeth/shared/codegen.ts
@@ -6,7 +6,6 @@ const config: CodegenConfig = {
schema: process.env.VITE_SUBSQUID_URL,
documents: ['**/src/**/*.graphql'],
plugins: ['typescript'],
- hooks: { afterOneFileWrite: ['prettier --write', 'eslint --fix'] },
config: {
scalars: {
BigInt: 'string',
@@ -22,7 +21,6 @@ const config: CodegenConfig = {
extension: '.generated.ts',
baseTypesPath: '~@origin/oeth/shared',
},
- hooks: { afterOneFileWrite: ['prettier --write', 'eslint --fix'] },
plugins: ['typescript-operations', 'typescript-react-query'],
config: {
exposeFetcher: true,
diff --git a/libs/oeth/shared/src/components/ActivityProvider/components/ActivityButton.tsx b/libs/oeth/shared/src/components/ActivityProvider/components/ActivityButton.tsx
new file mode 100644
index 000000000..1b7dcdc57
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/components/ActivityButton.tsx
@@ -0,0 +1,38 @@
+import { useState } from 'react';
+
+import { IconButton } from '@mui/material';
+import { ActivityIcon } from '@origin/shared/components';
+
+import { useGlobalStatus } from '../hooks';
+import { ActivityPopover } from './ActivityPopover';
+
+import type { IconButtonProps } from '@mui/material';
+
+export const ActivityButton = (
+ props: Omit,
+) => {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const status = useGlobalStatus();
+
+ return (
+ <>
+ setAnchorEl(e.currentTarget)}
+ >
+
+
+
+ >
+ );
+};
diff --git a/libs/oeth/shared/src/components/ActivityProvider/components/ActivityPopover.tsx b/libs/oeth/shared/src/components/ActivityProvider/components/ActivityPopover.tsx
new file mode 100644
index 000000000..c4229d51d
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/components/ActivityPopover.tsx
@@ -0,0 +1,145 @@
+import {
+ Button,
+ Divider,
+ Popover,
+ Stack,
+ Typography,
+ useTheme,
+} from '@mui/material';
+import { isNilOrEmpty } from '@origin/shared/utils';
+import { produce } from 'immer';
+import { descend, pipe, prop, sort, take } from 'ramda';
+import { useIntl } from 'react-intl';
+
+import { useActivityState } from '../state';
+import { ApprovalNotification } from './ApprovalNotification';
+import { RedeemNotification } from './RedeemNotification';
+import { SwapNotification } from './SwapNotification';
+
+import type { StackProps } from '@mui/material';
+
+import type { Activity } from '../types';
+
+export type AcitivityPopoverProps = {
+ anchor: HTMLElement | null;
+ setAnchor: (value: HTMLButtonElement | null) => void;
+};
+
+export const ActivityPopover = ({
+ anchor,
+ setAnchor,
+}: AcitivityPopoverProps) => {
+ const intl = useIntl();
+ const theme = useTheme();
+ const [{ activities, maxVisible }, setActivityState] = useActivityState();
+
+ const handleClose = () => {
+ setAnchor(null);
+ };
+
+ const handleClearAll = () => {
+ setActivityState(
+ produce((state) => {
+ state.activities = [];
+ }),
+ );
+ };
+
+ const sortedActivities = pipe(
+ sort(descend(prop('createdOn'))),
+ take(maxVisible),
+ )(activities) as Activity[];
+
+ return (
+ ({
+ xs: '90vw',
+ md: `min(${theme.typography.pxToRem(400)}, 90vw)`,
+ }),
+ [theme.breakpoints.down('md')]: {
+ left: '0 !important',
+ right: 0,
+ marginInline: 'auto',
+ },
+ },
+ }}
+ >
+
+
+
+ {intl.formatMessage({ defaultMessage: 'Recent Activity' })}
+
+
+
+
+
+ }>
+ {isNilOrEmpty(sortedActivities) ? (
+
+ ) : (
+ sortedActivities.map(
+ (a) =>
+ ({
+ approval: (
+
+ ),
+ redeem: (
+
+ ),
+ swap: (
+
+ ),
+ })[a.type],
+ )
+ )}
+
+
+
+ );
+};
+
+function EmptyActivity(props: StackProps) {
+ const intl = useIntl();
+
+ return (
+
+
+ {intl.formatMessage({ defaultMessage: 'No Activity' })}
+
+
+ );
+}
diff --git a/libs/oeth/shared/src/components/ActivityProvider/components/ApprovalNotification.tsx b/libs/oeth/shared/src/components/ActivityProvider/components/ApprovalNotification.tsx
new file mode 100644
index 000000000..95074e067
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/components/ApprovalNotification.tsx
@@ -0,0 +1,78 @@
+import { Box, Typography } from '@mui/material';
+import { ActivityIcon, NotificationSnack } from '@origin/shared/components';
+import { isNilOrEmpty } from '@origin/shared/utils';
+import { defineMessage, useIntl } from 'react-intl';
+import { formatUnits } from 'viem';
+
+import type { StackProps } from '@mui/material';
+import type { Token } from '@origin/shared/contracts';
+import type { MessageDescriptor } from 'react-intl';
+import type { TransactionReceipt } from 'viem';
+
+import type { GlobalActivityStatus } from '../types';
+
+type ApprovalNotificationProps = {
+ status: GlobalActivityStatus;
+ tokenIn: Token;
+ amountIn?: bigint;
+ txReceipt?: TransactionReceipt;
+ error?: string;
+} & StackProps;
+
+const title: Record = {
+ pending: defineMessage({ defaultMessage: 'Approving' }),
+ success: defineMessage({ defaultMessage: 'Approved' }),
+ error: defineMessage({ defaultMessage: 'Error while approving' }),
+ idle: defineMessage({ defaultMessage: 'Approve' }),
+};
+
+export const ApprovalNotification = ({
+ status,
+ tokenIn,
+ amountIn,
+ txReceipt,
+ error,
+ ...rest
+}: ApprovalNotificationProps) => {
+ const intl = useIntl();
+
+ return (
+ }
+ title={intl.formatMessage(title[status])}
+ href={
+ isNilOrEmpty(txReceipt?.transactionHash)
+ ? null
+ : `https://etherscan.io/tx/${txReceipt.transactionHash}`
+ }
+ subtitle={
+ isNilOrEmpty(error) ? (
+
+ {intl.formatMessage(
+ {
+ defaultMessage: '{amountIn} {symbolIn}',
+ },
+ {
+ amountIn: intl.formatNumber(
+ +formatUnits(amountIn, tokenIn.decimals),
+ { minimumFractionDigits: 4, maximumFractionDigits: 4 },
+ ),
+ symbolIn: tokenIn.symbol,
+ },
+ )}
+
+ ) : (
+ {error}
+ )
+ }
+ endIcon={
+
+ }
+ />
+ );
+};
diff --git a/libs/oeth/shared/src/components/ActivityProvider/components/RedeemNotification.tsx b/libs/oeth/shared/src/components/ActivityProvider/components/RedeemNotification.tsx
new file mode 100644
index 000000000..df96c0add
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/components/RedeemNotification.tsx
@@ -0,0 +1,106 @@
+import { Box, Stack, Typography } from '@mui/material';
+import {
+ ActivityIcon,
+ Mix,
+ NotificationSnack,
+} from '@origin/shared/components';
+import { isNilOrEmpty } from '@origin/shared/utils';
+import { defineMessage, useIntl } from 'react-intl';
+import { formatUnits } from 'viem';
+
+import type { StackProps } from '@mui/material';
+import type { Token } from '@origin/shared/contracts';
+import type { MessageDescriptor } from 'react-intl';
+import type { TransactionReceipt } from 'viem';
+
+import type { GlobalActivityStatus } from '../types';
+
+type RedeemNotificationProps = {
+ status: GlobalActivityStatus;
+ tokenIn: Token;
+ tokenOut: Token;
+ amountIn?: bigint;
+ amountOut?: bigint;
+ txReceipt?: TransactionReceipt;
+ error?: string;
+} & StackProps;
+
+const title: Record = {
+ pending: defineMessage({ defaultMessage: 'Redeeming' }),
+ success: defineMessage({ defaultMessage: 'Redeemed' }),
+ error: defineMessage({ defaultMessage: 'Error while redeeming' }),
+ idle: defineMessage({ defaultMessage: 'Redeem' }),
+};
+
+export const RedeemNotification = ({
+ status,
+ tokenIn,
+ tokenOut,
+ amountIn,
+ amountOut,
+ txReceipt,
+ error,
+ ...rest
+}: RedeemNotificationProps) => {
+ const intl = useIntl();
+
+ return (
+ }
+ title={intl.formatMessage(title[status])}
+ href={
+ isNilOrEmpty(txReceipt?.transactionHash)
+ ? null
+ : `https://etherscan.io/tx/${txReceipt.transactionHash}`
+ }
+ subtitle={
+ isNilOrEmpty(error) ? (
+
+ {intl.formatMessage(
+ {
+ defaultMessage: '{amountIn} {symbolIn}',
+ },
+ {
+ amountIn: intl.formatNumber(
+ +formatUnits(amountIn, tokenIn.decimals),
+ { minimumFractionDigits: 4, maximumFractionDigits: 4 },
+ ),
+ symbolIn: tokenIn.symbol,
+ amountOut: intl.formatNumber(
+ +formatUnits(amountOut, tokenOut.decimals),
+ { minimumFractionDigits: 4, maximumFractionDigits: 4 },
+ ),
+ },
+ )}
+
+ ) : (
+ {error}
+ )
+ }
+ endIcon={
+
+
+
+
+
+ }
+ />
+ );
+};
diff --git a/libs/oeth/shared/src/components/ActivityProvider/components/SwapNotification.tsx b/libs/oeth/shared/src/components/ActivityProvider/components/SwapNotification.tsx
new file mode 100644
index 000000000..423bb9aee
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/components/SwapNotification.tsx
@@ -0,0 +1,100 @@
+import { Box, Stack, Typography } from '@mui/material';
+import { ActivityIcon, NotificationSnack } from '@origin/shared/components';
+import { isNilOrEmpty } from '@origin/shared/utils';
+import { defineMessage, useIntl } from 'react-intl';
+import { formatUnits } from 'viem';
+
+import type { StackProps } from '@mui/material';
+import type { Token } from '@origin/shared/contracts';
+import type { MessageDescriptor } from 'react-intl';
+import type { TransactionReceipt } from 'viem';
+
+import type { GlobalActivityStatus } from '../types';
+
+type SwapNotificationProps = {
+ status: GlobalActivityStatus;
+ tokenIn: Token;
+ tokenOut: Token;
+ amountIn?: bigint;
+ amountOut?: bigint;
+ txReceipt?: TransactionReceipt;
+ error?: string;
+} & StackProps;
+
+const title: Record = {
+ pending: defineMessage({ defaultMessage: 'Swapping' }),
+ success: defineMessage({ defaultMessage: 'Swapped' }),
+ error: defineMessage({ defaultMessage: 'Error while swapping' }),
+ idle: defineMessage({ defaultMessage: 'Swap' }),
+};
+
+export const SwapNotification = ({
+ status,
+ tokenIn,
+ tokenOut,
+ amountIn,
+ amountOut,
+ txReceipt,
+ error,
+ ...rest
+}: SwapNotificationProps) => {
+ const intl = useIntl();
+
+ return (
+ }
+ title={intl.formatMessage(title[status])}
+ href={
+ isNilOrEmpty(txReceipt?.transactionHash)
+ ? null
+ : `https://etherscan.io/tx/${txReceipt.transactionHash}`
+ }
+ subtitle={
+ isNilOrEmpty(error) ? (
+
+ {intl.formatMessage(
+ {
+ defaultMessage:
+ '{amountIn} {symbolIn} for {amountOut} {symbolOut}',
+ },
+ {
+ amountIn: intl.formatNumber(
+ +formatUnits(amountIn, tokenIn.decimals),
+ { minimumFractionDigits: 4, maximumFractionDigits: 4 },
+ ),
+ symbolIn: tokenIn.symbol,
+ amountOut: intl.formatNumber(
+ +formatUnits(amountOut, tokenOut.decimals),
+ { minimumFractionDigits: 4, maximumFractionDigits: 4 },
+ ),
+ symbolOut: tokenOut.symbol,
+ },
+ )}
+
+ ) : (
+ {error}
+ )
+ }
+ endIcon={
+
+
+
+
+
+ }
+ />
+ );
+};
diff --git a/libs/oeth/shared/src/components/ActivityProvider/components/index.ts b/libs/oeth/shared/src/components/ActivityProvider/components/index.ts
new file mode 100644
index 000000000..14b1dccb3
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/components/index.ts
@@ -0,0 +1,4 @@
+export * from './ActivityButton';
+export * from './ApprovalNotification';
+export * from './RedeemNotification';
+export * from './SwapNotification';
diff --git a/libs/oeth/shared/src/components/ActivityProvider/hooks.ts b/libs/oeth/shared/src/components/ActivityProvider/hooks.ts
new file mode 100644
index 000000000..a8f83ae49
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/hooks.ts
@@ -0,0 +1,109 @@
+import { useCallback, useEffect, useState } from 'react';
+
+import { isNilOrEmpty } from '@origin/shared/utils';
+import { usePrevious } from '@react-hookz/web';
+import { produce } from 'immer';
+import { groupBy, prop, propEq } from 'ramda';
+
+import { useActivityState } from './state';
+
+import type { Activity, GlobalActivityStatus } from './types';
+
+export const usePushActivity = () => {
+ const [, setState] = useActivityState();
+
+ return useCallback(
+ (value: Omit) => {
+ const activity = {
+ ...value,
+ id: Date.now().toString(),
+ createdOn: Date.now(),
+ };
+ setState(
+ produce((state) => {
+ state.activities.unshift(activity);
+ }),
+ );
+
+ return activity;
+ },
+ [setState],
+ );
+};
+
+export const useUpdateActivity = () => {
+ const [, setState] = useActivityState();
+
+ return useCallback(
+ (activity: Partial) => {
+ setState(
+ produce((state) => {
+ const idx = state.activities.findIndex(propEq(activity.id, 'id'));
+ if (idx > -1) {
+ state.activities[idx] = {
+ ...state.activities[idx],
+ ...activity,
+ };
+ }
+ }),
+ );
+ },
+ [setState],
+ );
+};
+
+export const useDeleteActivity = () => {
+ const [, setState] = useActivityState();
+
+ return useCallback(
+ (id: string) => {
+ setState(
+ produce((state) => {
+ const idx = state.activities.findIndex(propEq(id, 'id'));
+ if (idx > -1) {
+ state.activities.splice(idx, 1);
+ }
+ }),
+ );
+ },
+ [setState],
+ );
+};
+
+export const useGlobalStatus = () => {
+ const [{ activities }] = useActivityState();
+ const [status, setStatus] = useState('idle');
+ const prev = usePrevious(activities);
+
+ useEffect(() => {
+ const prevGrouped = groupBy(prop('status'), prev ?? []);
+ const grouped = groupBy(prop('status'), activities ?? []);
+
+ if (isNilOrEmpty(grouped.pending)) {
+ if (
+ !isNilOrEmpty(grouped.success) &&
+ prevGrouped?.success?.length !== grouped?.success?.length
+ ) {
+ setStatus('success');
+ setTimeout(() => {
+ setStatus('idle');
+ }, 5000);
+ } else if (
+ !isNilOrEmpty(grouped.error) &&
+ prevGrouped?.error?.length !== grouped?.error?.length
+ ) {
+ setStatus('error');
+ setTimeout(() => {
+ setStatus('idle');
+ }, 5000);
+ } else {
+ setStatus('idle');
+ }
+ } else {
+ setStatus('pending');
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [activities]);
+
+ return status;
+};
diff --git a/libs/oeth/shared/src/components/ActivityProvider/index.ts b/libs/oeth/shared/src/components/ActivityProvider/index.ts
new file mode 100644
index 000000000..a260a059b
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/index.ts
@@ -0,0 +1,4 @@
+export * from './components';
+export * from './hooks';
+export * from './state';
+export * from './types';
diff --git a/libs/oeth/shared/src/components/ActivityProvider/state.ts b/libs/oeth/shared/src/components/ActivityProvider/state.ts
new file mode 100644
index 000000000..62c2a9ef3
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/state.ts
@@ -0,0 +1,15 @@
+import { useState } from 'react';
+
+import { createContainer } from 'react-tracked';
+
+import type { Activity } from './types';
+
+type ActivityState = {
+ activities: Activity[];
+ maxVisible: number;
+};
+
+export const { Provider: ActivityProvider, useTracked: useActivityState } =
+ createContainer(() =>
+ useState({ activities: [], maxVisible: 10 }),
+ );
diff --git a/libs/oeth/shared/src/components/ActivityProvider/types.ts b/libs/oeth/shared/src/components/ActivityProvider/types.ts
new file mode 100644
index 000000000..171386145
--- /dev/null
+++ b/libs/oeth/shared/src/components/ActivityProvider/types.ts
@@ -0,0 +1,22 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import type { Token } from '@origin/shared/contracts';
+import type { TransactionReceipt } from 'viem';
+
+export type ActivityType = 'swap' | 'approval' | 'redeem';
+
+export type ActivityStatus = 'pending' | 'success' | 'error';
+
+export type GlobalActivityStatus = 'idle' | ActivityStatus;
+
+export type Activity = {
+ id: string;
+ createdOn: number;
+ tokenIn: Token;
+ tokenOut: Token;
+ amountIn?: bigint;
+ amountOut?: bigint;
+ txReceipt?: TransactionReceipt;
+ type: ActivityType;
+ status: ActivityStatus;
+ error?: string;
+};
diff --git a/libs/oeth/shared/src/components/ApyHeader/index.tsx b/libs/oeth/shared/src/components/ApyHeader/index.tsx
new file mode 100644
index 000000000..12bd788c9
--- /dev/null
+++ b/libs/oeth/shared/src/components/ApyHeader/index.tsx
@@ -0,0 +1,118 @@
+import { useState } from 'react';
+
+import {
+ Box,
+ Button,
+ Menu,
+ MenuItem,
+ Skeleton,
+ Stack,
+ Typography,
+} from '@mui/material';
+import { defineMessage, useIntl } from 'react-intl';
+
+import { useApiesQuery } from './queries.generated';
+
+import type { StackProps } from '@mui/material';
+
+const trailingOptions = [
+ {
+ label: defineMessage({ defaultMessage: '30 days trailing APY' }),
+ value: 30,
+ },
+ { label: defineMessage({ defaultMessage: '7 days trailing APY' }), value: 7 },
+];
+
+export const ApyHeader = (props: StackProps) => {
+ const intl = useIntl();
+ const [trailing, setTrailing] = useState(trailingOptions[0]);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const { data: apy, isLoading: apyLoading } = useApiesQuery(
+ {
+ limit: 1,
+ },
+ {
+ select: (data) => data.apies[0],
+ },
+ );
+
+ return (
+
+ {apyLoading ? (
+
+ ) : (
+
+ {intl.formatNumber(
+ trailing.value === 30 ? apy.apy30DayAvg : apy.apy7DayAvg,
+ { minimumFractionDigits: 2, maximumFractionDigits: 2 },
+ )}
+ %
+
+ )}
+
+
+
+
+ );
+};
diff --git a/libs/oeth/shared/src/components/ApyHeader/queries.generated.ts b/libs/oeth/shared/src/components/ApyHeader/queries.generated.ts
new file mode 100644
index 000000000..caaac857b
--- /dev/null
+++ b/libs/oeth/shared/src/components/ApyHeader/queries.generated.ts
@@ -0,0 +1,43 @@
+import * as Types from '@origin/oeth/shared';
+
+import { useQuery, UseQueryOptions } from '@tanstack/react-query';
+import { graphqlClient } from '@origin/oeth/shared';
+export type ApiesQueryVariables = Types.Exact<{
+ limit?: Types.InputMaybe;
+}>;
+
+
+export type ApiesQuery = { __typename?: 'Query', apies: Array<{ __typename?: 'APY', id: string, timestamp: string, apy7DayAvg: number, apy30DayAvg: number }> };
+
+
+export const ApiesDocument = `
+ query Apies($limit: Int) {
+ apies(
+ limit: $limit
+ orderBy: timestamp_DESC
+ where: {timestamp_gt: "2023-06-06T12:38:47.000000Z"}
+ ) {
+ id
+ timestamp
+ apy7DayAvg
+ apy30DayAvg
+ }
+}
+ `;
+export const useApiesQuery = <
+ TData = ApiesQuery,
+ TError = unknown
+ >(
+ variables?: ApiesQueryVariables,
+ options?: UseQueryOptions
+ ) =>
+ useQuery(
+ variables === undefined ? ['Apies'] : ['Apies', variables],
+ graphqlClient(ApiesDocument, variables),
+ options
+ );
+
+useApiesQuery.getKey = (variables?: ApiesQueryVariables) => variables === undefined ? ['Apies'] : ['Apies', variables];
+;
+
+useApiesQuery.fetcher = (variables?: ApiesQueryVariables, options?: RequestInit['headers']) => graphqlClient(ApiesDocument, variables, options);
\ No newline at end of file
diff --git a/libs/oeth/swap/src/queries.graphql b/libs/oeth/shared/src/components/ApyHeader/queries.graphql
similarity index 100%
rename from libs/oeth/swap/src/queries.graphql
rename to libs/oeth/shared/src/components/ApyHeader/queries.graphql
diff --git a/libs/oeth/shared/src/components/GasPopover.tsx b/libs/oeth/shared/src/components/GasPopover.tsx
index 510e85e97..58d146408 100644
--- a/libs/oeth/shared/src/components/GasPopover.tsx
+++ b/libs/oeth/shared/src/components/GasPopover.tsx
@@ -7,8 +7,6 @@ import {
FormControl,
FormHelperText,
IconButton,
- InputAdornment,
- InputBase,
InputLabel,
Popover,
Stack,
@@ -16,7 +14,6 @@ import {
} from '@mui/material';
import { InfoTooltip, PercentInput } from '@origin/shared/components';
import { useIntl } from 'react-intl';
-import { useFeeData } from 'wagmi';
import type { IconButtonProps } from '@mui/material';
@@ -45,7 +42,6 @@ export function GasPopover({
const theme = useTheme();
const intl = useIntl();
const [anchorEl, setAnchorEl] = useState(null);
- const { data: feeData } = useFeeData({ formatUnits: 'gwei' });
return (
<>
@@ -87,7 +83,7 @@ export function GasPopover({
},
}}
>
-
+
-
-
- {intl.formatMessage({ defaultMessage: 'Gas Price' })}
-
-
- theme.palette.secondary.main,
- backgroundColor: (theme) =>
- alpha(theme.palette.secondary.main, 0.05),
- paddingInlineEnd: 2,
- '& .MuiInputBase-input': {
- textAlign: 'right',
- borderColor: (theme) => theme.palette.secondary.main,
- '&::placeholder': {
- color: 'text.primary',
- opacity: 1,
- },
- },
- }}
- endAdornment={
-
- {intl.formatMessage({ defaultMessage: 'GWEI' })}
-
- }
- />
-
-
>
diff --git a/libs/oeth/shared/src/components/index.ts b/libs/oeth/shared/src/components/index.ts
index 45b4fb9d0..5ba8e635d 100644
--- a/libs/oeth/shared/src/components/index.ts
+++ b/libs/oeth/shared/src/components/index.ts
@@ -1,2 +1,4 @@
+export * from './ActivityProvider';
+export * from './ApyHeader';
export * from './AccountPopover';
export * from './GasPopover';
diff --git a/libs/oeth/swap/src/actions/index.ts b/libs/oeth/swap/src/actions/index.ts
index 4890e10a5..016b2d6a4 100644
--- a/libs/oeth/swap/src/actions/index.ts
+++ b/libs/oeth/swap/src/actions/index.ts
@@ -43,9 +43,11 @@ const defaultApi: SwapApi = {
},
approve: async () => {
console.log('Approve operation not implemented');
+ return null;
},
swap: async () => {
console.log('Route swap operation not implemented');
+ return null;
},
};
diff --git a/libs/oeth/swap/src/actions/mintVault.ts b/libs/oeth/swap/src/actions/mintVault.ts
index 331cae331..7b25cd10f 100644
--- a/libs/oeth/swap/src/actions/mintVault.ts
+++ b/libs/oeth/swap/src/actions/mintVault.ts
@@ -8,7 +8,6 @@ import {
prepareWriteContract,
readContract,
readContracts,
- waitForTransaction,
writeContract,
} from '@wagmi/core';
import { formatUnits, parseUnits } from 'viem';
@@ -84,39 +83,37 @@ const estimateGas: EstimateGas = async ({
return gasEstimate;
} catch {}
- try {
- const [rebaseThreshold, autoAllocateThreshold] =
- await queryClient.fetchQuery({
- queryKey: ['vault-info', tokenOut.address],
- queryFn: () =>
- readContracts({
- contracts: [
- {
- address: contracts.mainnet.OETHVaultCore.address,
- abi: contracts.mainnet.OETHVaultCore.abi,
- functionName: 'rebaseThreshold',
- },
- {
- address: contracts.mainnet.OETHVaultCore.address,
- abi: contracts.mainnet.OETHVaultCore.abi,
- functionName: 'autoAllocateThreshold',
- },
- ],
- }),
- staleTime: Infinity,
- });
-
- // TODO check validity
- gasEstimate = 220000n;
- if (amountIn > autoAllocateThreshold?.result) {
- gasEstimate = 2900000n;
- } else if (amountIn > rebaseThreshold?.result) {
- gasEstimate = 510000n;
- }
- } catch (e) {
- console.error(`mint vault gas estimate error!\n${e.message}`);
+ const [rebaseThreshold, autoAllocateThreshold] = await queryClient.fetchQuery(
+ {
+ queryKey: ['vault-info', tokenOut.address],
+ queryFn: () =>
+ readContracts({
+ contracts: [
+ {
+ address: contracts.mainnet.OETHVaultCore.address,
+ abi: contracts.mainnet.OETHVaultCore.abi,
+ functionName: 'rebaseThreshold',
+ },
+ {
+ address: contracts.mainnet.OETHVaultCore.address,
+ abi: contracts.mainnet.OETHVaultCore.abi,
+ functionName: 'autoAllocateThreshold',
+ },
+ ],
+ }),
+ staleTime: Infinity,
+ },
+ );
+
+ gasEstimate = 220000n;
+ if (amountIn > autoAllocateThreshold?.result) {
+ gasEstimate = 2900000n;
+ } else if (amountIn > rebaseThreshold?.result) {
+ gasEstimate = 510000n;
}
+ console.log(`Mint vault uses fix gas estimate: ${gasEstimate}`);
+
return gasEstimate;
};
@@ -158,7 +155,9 @@ const estimateApprovalGas: EstimateApprovalGas = async ({
args: [contracts.mainnet.OETHVaultCore.address, amountIn],
account: address,
});
- } catch {}
+ } catch {
+ console.log(`Mint vault uses fix approval gas estimate: 0`);
+ }
return approvalEstimate;
};
@@ -206,35 +205,16 @@ const estimateRoute: EstimateRoute = async ({
};
};
-const approve: Approve = async ({
- tokenIn,
- amountIn,
- onSuccess,
- onError,
- onReject,
-}) => {
- try {
- const { request } = await prepareWriteContract({
- address: tokenIn.address,
- abi: erc20ABI,
- functionName: 'approve',
- args: [contracts.mainnet.OETHVaultCore.address, amountIn],
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
-
- console.log(`mint vault approval done!`);
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`mint vault approval error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Mint vault approval');
- } else if (onError) {
- await onError('Mint vault approval');
- }
- }
+const approve: Approve = async ({ tokenIn, amountIn }) => {
+ const { request } = await prepareWriteContract({
+ address: tokenIn.address,
+ abi: erc20ABI,
+ functionName: 'approve',
+ args: [contracts.mainnet.OETHVaultCore.address, amountIn],
+ });
+ const { hash } = await writeContract(request);
+
+ return hash;
};
const swap: Swap = async ({
@@ -243,24 +223,17 @@ const swap: Swap = async ({
amountIn,
slippage,
amountOut,
- onSuccess,
- onError,
- onReject,
}) => {
const { address } = getAccount();
if (amountIn === 0n || isNilOrEmpty(address)) {
- return;
+ return null;
}
const approved = await allowance({ tokenIn, tokenOut });
if (approved < amountIn) {
- console.error(`mint vault is not approved`);
- if (onError) {
- await onError('Mint vault is not approved');
- }
- return;
+ throw new Error(`Mint vault is not approved`);
}
const minAmountOut = parseUnits(
@@ -271,28 +244,15 @@ const swap: Swap = async ({
tokenOut.decimals,
);
- try {
- const { request } = await prepareWriteContract({
- address: contracts.mainnet.OETHVaultCore.address,
- abi: contracts.mainnet.OETHVaultCore.abi,
- functionName: 'mint',
- args: [tokenIn.address, amountIn, minAmountOut],
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
-
- console.log('mint vault done!');
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`mint vault error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Mint vault swap');
- } else if (onError) {
- await onError('Mint vault swap');
- }
- }
+ const { request } = await prepareWriteContract({
+ address: contracts.mainnet.OETHVaultCore.address,
+ abi: contracts.mainnet.OETHVaultCore.abi,
+ functionName: 'mint',
+ args: [tokenIn.address, amountIn, minAmountOut],
+ });
+ const { hash } = await writeContract(request);
+
+ return hash;
};
export default {
diff --git a/libs/oeth/swap/src/actions/swapCurve/index.ts b/libs/oeth/swap/src/actions/swapCurve/index.ts
index 680619264..256f53ba1 100644
--- a/libs/oeth/swap/src/actions/swapCurve/index.ts
+++ b/libs/oeth/swap/src/actions/swapCurve/index.ts
@@ -5,7 +5,6 @@ import {
getPublicClient,
prepareWriteContract,
readContract,
- waitForTransaction,
writeContract,
} from '@wagmi/core';
import { formatUnits, maxUint256, parseUnits } from 'viem';
@@ -35,7 +34,7 @@ const estimateAmount: EstimateAmount = async ({
const curveConfig = curveRoutes[tokenIn.symbol]?.[tokenOut.symbol];
if (isNilOrEmpty(curveConfig)) {
- console.error(
+ throw new Error(
`No curve route found, verify exchange mapping ${tokenIn.symbol} -> ${tokenOut.symbol}`,
);
}
@@ -78,11 +77,9 @@ const estimateGas: EstimateGas = async ({
const curveConfig = curveRoutes[tokenIn.symbol]?.[tokenOut.symbol];
if (isNilOrEmpty(curveConfig)) {
- console.error(
+ throw new Error(
`No curve route found, verify exchange mapping ${tokenIn.symbol} -> ${tokenOut.symbol}`,
);
-
- return gasEstimate;
}
try {
@@ -100,9 +97,7 @@ const estimateGas: EstimateGas = async ({
...(isNilOrEmpty(tokenIn.address) && { value: amountIn }),
});
} catch (e) {
- console.error(
- `swap curve exchange multiple gas estimate error, returning fix estimate! \n${e.message}`,
- );
+ console.log(`Swap curve uses fix gas estimate: 350000`);
gasEstimate = 350000n;
}
@@ -152,7 +147,9 @@ const estimateApprovalGas: EstimateApprovalGas = async ({
args: [curve.CurveRegistryExchange.address, amountIn],
account: address,
});
- } catch {}
+ } catch {
+ console.log(`Swap curve uses fix approval gas estimate: 0`);
+ }
return approvalEstimate;
};
@@ -207,36 +204,16 @@ const estimateRoute: EstimateRoute = async ({
};
};
-const approve: Approve = async ({
- tokenIn,
- amountIn,
- curve,
- onSuccess,
- onError,
- onReject,
-}) => {
- try {
- const { request } = await prepareWriteContract({
- address: tokenIn.address,
- abi: erc20ABI,
- functionName: 'approve',
- args: [curve.CurveRegistryExchange.address, amountIn],
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
+const approve: Approve = async ({ tokenIn, amountIn, curve }) => {
+ const { request } = await prepareWriteContract({
+ address: tokenIn.address,
+ abi: erc20ABI,
+ functionName: 'approve',
+ args: [curve.CurveRegistryExchange.address, amountIn],
+ });
+ const { hash } = await writeContract(request);
- console.log(`swap curve exchange multiple approval done!`);
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`swap curve exchange multiple approval error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Swap Curve approval');
- } else if (onError) {
- await onError('Swap Curve approval');
- }
- }
+ return hash;
};
const swap: Swap = async ({
@@ -246,24 +223,17 @@ const swap: Swap = async ({
amountOut,
slippage,
curve,
- onSuccess,
- onError,
- onReject,
}) => {
const { address } = getAccount();
if (amountIn === 0n || isNilOrEmpty(address)) {
- return;
+ return null;
}
const approved = await allowance({ tokenIn, tokenOut, curve });
if (approved < amountIn) {
- console.error(`swap curve exchange multiple is not approved`);
- if (onError) {
- await onError('swap curve exchange multiple is not approved');
- }
- return;
+ throw new Error(`Swap curve is not approved`);
}
const minAmountOut = parseUnits(
@@ -277,43 +247,21 @@ const swap: Swap = async ({
const curveConfig = curveRoutes[tokenIn.symbol]?.[tokenOut?.symbol];
if (isNilOrEmpty(curveConfig)) {
- console.error(
+ throw new Error(
`No curve route found, verify exchange mapping ${tokenIn.symbol} -> ${tokenOut.symbol}`,
);
- if (onError) {
- await onError('No curve route found');
- }
- return;
}
- try {
- const { request } = await prepareWriteContract({
- address: curve.CurveRegistryExchange.address,
- abi: curve.CurveRegistryExchange.abi,
- functionName: 'exchange_multiple',
- args: [
- curveConfig.routes,
- curveConfig.swapParams,
- amountIn,
- minAmountOut,
- ],
- ...(isNilOrEmpty(tokenIn.address) && { value: amountIn }),
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
+ const { request } = await prepareWriteContract({
+ address: curve.CurveRegistryExchange.address,
+ abi: curve.CurveRegistryExchange.abi,
+ functionName: 'exchange_multiple',
+ args: [curveConfig.routes, curveConfig.swapParams, amountIn, minAmountOut],
+ ...(isNilOrEmpty(tokenIn.address) && { value: amountIn }),
+ });
+ const { hash } = await writeContract(request);
- console.log('swap curve exchange multiple done!');
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`swap curve exchange multiple error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Swap Curve exchange multiple');
- } else if (onError) {
- await onError('Swap Curve exchange multiple');
- }
- }
+ return hash;
};
export default {
diff --git a/libs/oeth/swap/src/actions/swapCurveEth.ts b/libs/oeth/swap/src/actions/swapCurveEth.ts
index c38e8fe64..b69776272 100644
--- a/libs/oeth/swap/src/actions/swapCurveEth.ts
+++ b/libs/oeth/swap/src/actions/swapCurveEth.ts
@@ -5,7 +5,6 @@ import {
getPublicClient,
prepareWriteContract,
readContract,
- waitForTransaction,
writeContract,
} from '@wagmi/core';
import { formatUnits, isAddressEqual, maxUint256, parseUnits } from 'viem';
@@ -93,9 +92,7 @@ const estimateGas: EstimateGas = async ({
account: address ?? ETH_ADDRESS_CURVE,
});
} catch (e) {
- console.error(
- `swap curve OETHPool gas estimate error, returning fix estimate!\n${e.message}`,
- );
+ console.log(`Swap curve OETH Pool uses fix gas estimate: 180000`);
gasEstimate = 180000n;
}
@@ -110,6 +107,7 @@ const allowance: Allowance = async () => {
const estimateApprovalGas: EstimateApprovalGas = async () => {
// ETH doesn't need approval
+ console.log(`Swap curve OETH Pool uses fix approval gas estimate: 0`);
return 0n;
};
@@ -163,11 +161,9 @@ const estimateRoute: EstimateRoute = async ({
};
};
-const approve: Approve = async ({ onSuccess }) => {
+const approve: Approve = async () => {
// ETH doesn't need approval
- if (onSuccess) {
- await onSuccess(null);
- }
+ return null;
};
const swap: Swap = async ({
@@ -177,12 +173,9 @@ const swap: Swap = async ({
amountOut,
slippage,
curve,
- onSuccess,
- onError,
- onReject,
}) => {
if (amountIn === 0n) {
- return;
+ return null;
}
const minAmountOut = parseUnits(
@@ -193,42 +186,29 @@ const swap: Swap = async ({
tokenOut.decimals,
);
- try {
- const { request } = await prepareWriteContract({
- address: contracts.mainnet.curveOethPool.address,
- abi: contracts.mainnet.curveOethPool.abi,
- functionName: 'exchange',
- args: [
- BigInt(
- curve.OethPoolUnderlyings.findIndex((t) =>
- isAddressEqual(t, tokenIn.address ?? ETH_ADDRESS_CURVE),
- ),
+ const { request } = await prepareWriteContract({
+ address: contracts.mainnet.curveOethPool.address,
+ abi: contracts.mainnet.curveOethPool.abi,
+ functionName: 'exchange',
+ args: [
+ BigInt(
+ curve.OethPoolUnderlyings.findIndex((t) =>
+ isAddressEqual(t, tokenIn.address ?? ETH_ADDRESS_CURVE),
),
- BigInt(
- curve.OethPoolUnderlyings.findIndex((t) =>
- isAddressEqual(t, tokenOut.address ?? ETH_ADDRESS_CURVE),
- ),
+ ),
+ BigInt(
+ curve.OethPoolUnderlyings.findIndex((t) =>
+ isAddressEqual(t, tokenOut.address ?? ETH_ADDRESS_CURVE),
),
- amountIn,
- minAmountOut,
- ],
- ...(isNilOrEmpty(tokenIn.address) && { value: amountIn }),
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
+ ),
+ amountIn,
+ minAmountOut,
+ ],
+ ...(isNilOrEmpty(tokenIn.address) && { value: amountIn }),
+ });
+ const { hash } = await writeContract(request);
- console.log('swap curve OETHPool done!');
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`swap curve OETHPool error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Swap Curve exchange');
- } else if (onError) {
- await onError('Swap Curve exchange');
- }
- }
+ return hash;
};
export default {
diff --git a/libs/oeth/swap/src/actions/swapZapperEth.ts b/libs/oeth/swap/src/actions/swapZapperEth.ts
index d3799be32..b2499059d 100644
--- a/libs/oeth/swap/src/actions/swapZapperEth.ts
+++ b/libs/oeth/swap/src/actions/swapZapperEth.ts
@@ -6,7 +6,6 @@ import {
getPublicClient,
prepareWriteContract,
readContract,
- waitForTransaction,
writeContract,
} from '@wagmi/core';
import { formatUnits, maxUint256 } from 'viem';
@@ -26,7 +25,7 @@ const estimateAmount: EstimateAmount = async ({ amountIn }) => {
};
const estimateGas: EstimateGas = async ({ amountIn }) => {
- let gasEstimate = 200000n;
+ let gasEstimate = 0n;
const { address } = getAccount();
@@ -44,7 +43,10 @@ const estimateGas: EstimateGas = async ({ amountIn }) => {
value: amountIn,
account: address,
});
- } catch {}
+ } catch {
+ console.log(`Swap zapper uses fix gas estimate: 200000`);
+ gasEstimate = 200000n;
+ }
return gasEstimate;
};
@@ -97,7 +99,9 @@ const estimateApprovalGas: EstimateApprovalGas = async ({
args: [contracts.mainnet.OETHZapper.address, amountIn],
account: address,
});
- } catch {}
+ } catch {
+ console.log(`Swap zapper uses fix approval gas estimate: 0`);
+ }
return approvalEstimate;
};
@@ -140,92 +144,44 @@ const estimateRoute: EstimateRoute = async ({
};
};
-const approve: Approve = async ({
- tokenIn,
- tokenOut,
- amountIn,
- onSuccess,
- onError,
- onReject,
-}) => {
- if (
- (isNilOrEmpty(tokenIn.address) || isNilOrEmpty(tokenOut.address)) &&
- onSuccess
- ) {
- console.log(`swap eth does not require approval!`);
- onSuccess(null);
+const approve: Approve = async ({ tokenIn, tokenOut, amountIn }) => {
+ if (isNilOrEmpty(tokenIn.address) || isNilOrEmpty(tokenOut.address)) {
+ return null;
}
- try {
- const { request } = await prepareWriteContract({
- address: tokenIn.address,
- abi: erc20ABI,
- functionName: 'approve',
- args: [contracts.mainnet.OETHZapper.address, amountIn],
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
-
- console.log(`swap zapper eth approval done!`);
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`swap zapper eth approval error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Swap Zapper ETH approval');
- } else if (onError) {
- await onError('Swap Zapper ETH approval');
- }
- }
+ const { request } = await prepareWriteContract({
+ address: tokenIn.address,
+ abi: erc20ABI,
+ functionName: 'approve',
+ args: [contracts.mainnet.OETHZapper.address, amountIn],
+ });
+ const { hash } = await writeContract(request);
+
+ return hash;
};
-const swap: Swap = async ({
- tokenIn,
- tokenOut,
- amountIn,
- onSuccess,
- onError,
- onReject,
-}) => {
+const swap: Swap = async ({ tokenIn, tokenOut, amountIn }) => {
const { address } = getAccount();
if (amountIn === 0n || isNilOrEmpty(address)) {
- return;
+ return null;
}
const approved = await allowance({ tokenIn, tokenOut });
if (approved < amountIn) {
- console.error(`swap zapper eth is not approved`);
- if (onError) {
- await onError('Swap Zapper Eth is not approved');
- }
- return;
+ throw new Error(`Swap zapper is not approved`);
}
- try {
- const { request } = await prepareWriteContract({
- address: contracts.mainnet.OETHZapper.address,
- abi: contracts.mainnet.OETHZapper.abi,
- functionName: 'deposit',
- value: amountIn,
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
-
- console.log('swap zapper eth done!');
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`swap zapper eth error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Swap Zapper Eth');
- } else if (onError) {
- await onError('Swap Zapper Eth');
- }
- }
+ const { request } = await prepareWriteContract({
+ address: contracts.mainnet.OETHZapper.address,
+ abi: contracts.mainnet.OETHZapper.abi,
+ functionName: 'deposit',
+ value: amountIn,
+ });
+ const { hash } = await writeContract(request);
+
+ return hash;
};
export default {
diff --git a/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts b/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts
index 5d855e988..225b7de42 100644
--- a/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts
+++ b/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts
@@ -7,7 +7,6 @@ import {
prepareWriteContract,
readContract,
readContracts,
- waitForTransaction,
writeContract,
} from '@wagmi/core';
import { formatUnits, maxUint256, parseUnits } from 'viem';
@@ -54,6 +53,7 @@ const estimateAmount: EstimateAmount = async ({ tokenOut, amountIn }) => {
};
const estimateGas: EstimateGas = async () => {
+ console.log(`Swap zapper sfrxETH uses fix gas estimate: 90000`);
return 90000n;
};
@@ -106,6 +106,7 @@ const estimateApprovalGas: EstimateApprovalGas = async ({
account: address,
});
} catch {
+ console.log(`Swap zapper sfrxETH uses fix approval gas estimate: 64000`);
approvalEstimate = 64000n;
}
@@ -150,44 +151,20 @@ const estimateRoute: EstimateRoute = async ({
};
};
-const approve: Approve = async ({
- tokenIn,
- tokenOut,
- amountIn,
- onSuccess,
- onError,
- onReject,
-}) => {
- if (
- (isNilOrEmpty(tokenIn.address) || isNilOrEmpty(tokenOut.address)) &&
- onSuccess
- ) {
- console.log(`swap zapper does not require approval!`);
- onSuccess(null);
+const approve: Approve = async ({ tokenIn, tokenOut, amountIn }) => {
+ if (isNilOrEmpty(tokenIn.address) || isNilOrEmpty(tokenOut.address)) {
+ return null;
}
- try {
- const { request } = await prepareWriteContract({
- address: tokenIn.address,
- abi: erc20ABI,
- functionName: 'approve',
- args: [contracts.mainnet.OETHZapper.address, amountIn],
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
+ const { request } = await prepareWriteContract({
+ address: tokenIn.address,
+ abi: erc20ABI,
+ functionName: 'approve',
+ args: [contracts.mainnet.OETHZapper.address, amountIn],
+ });
+ const { hash } = await writeContract(request);
- console.log(`swap zapper approval done!`);
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`swap zapper approval error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Swap Zapper approval');
- } else if (onError) {
- await onError('Swap Zapper approval');
- }
- }
+ return hash;
};
const swap: Swap = async ({
@@ -196,9 +173,6 @@ const swap: Swap = async ({
amountIn,
slippage,
amountOut,
- onSuccess,
- onError,
- onReject,
}) => {
const { address } = getAccount();
@@ -209,11 +183,7 @@ const swap: Swap = async ({
const approved = await allowance({ tokenIn, tokenOut });
if (approved < amountIn) {
- console.error(`swap zapper is not approved`);
- if (onError) {
- await onError('Swap Zapper is not approved');
- }
- return;
+ throw new Error(`Swap zapper sfrxETH is not approved`);
}
const minAmountOut = parseUnits(
@@ -224,28 +194,15 @@ const swap: Swap = async ({
tokenOut.decimals,
);
- try {
- const { request } = await prepareWriteContract({
- address: contracts.mainnet.OETHZapper.address,
- abi: contracts.mainnet.OETHZapper.abi,
- functionName: 'depositSFRXETH',
- args: [amountIn, minAmountOut],
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
+ const { request } = await prepareWriteContract({
+ address: contracts.mainnet.OETHZapper.address,
+ abi: contracts.mainnet.OETHZapper.abi,
+ functionName: 'depositSFRXETH',
+ args: [amountIn, minAmountOut],
+ });
+ const { hash } = await writeContract(request);
- console.log('swap zapper sfrxEth done!');
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`swap zapper sfrxEth error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Swap Zapper');
- } else if (onError) {
- await onError('Swap Zapper');
- }
- }
+ return hash;
};
export default {
diff --git a/libs/oeth/swap/src/actions/unwrapWOETH.ts b/libs/oeth/swap/src/actions/unwrapWOETH.ts
index 13bfdea9c..863efa020 100644
--- a/libs/oeth/swap/src/actions/unwrapWOETH.ts
+++ b/libs/oeth/swap/src/actions/unwrapWOETH.ts
@@ -5,7 +5,6 @@ import {
getPublicClient,
prepareWriteContract,
readContract,
- waitForTransaction,
writeContract,
} from '@wagmi/core';
import { formatUnits, maxUint256 } from 'viem';
@@ -68,7 +67,9 @@ const estimateGas: EstimateGas = async ({ amountIn }) => {
args: [amountIn, whales.mainnet.WOETH, whales.mainnet.WOETH],
account: whales.mainnet.WOETH,
});
- } catch {}
+ } catch {
+ console.log(`Unwrap WOETH uses fix gas estimate: 0`);
+ }
return gasEstimate;
};
@@ -80,6 +81,7 @@ const allowance: Allowance = async () => {
const estimateApprovalGas: EstimateApprovalGas = async () => {
// Unwrap WOETH does not require approval
+ console.log(`Unwrap WOETH uses fix gas estimate: 0`);
return 0n;
};
@@ -121,11 +123,9 @@ const estimateRoute: EstimateRoute = async ({
};
};
-const approve: Approve = async ({ onSuccess }) => {
+const approve: Approve = async () => {
// Unwrap WOETH does not require approval
- if (onSuccess) {
- await onSuccess(null);
- }
+ return null;
};
const swap: Swap = async ({ amountIn }) => {
@@ -135,21 +135,15 @@ const swap: Swap = async ({ amountIn }) => {
return;
}
- try {
- const { request } = await prepareWriteContract({
- address: contracts.mainnet.WOETH.address,
- abi: contracts.mainnet.WOETH.abi,
- functionName: 'redeem',
- args: [amountIn, address, address],
- });
- const { hash } = await writeContract(request);
- await waitForTransaction({ hash });
- // TODO trigger notification
- console.log('unwrap woeth done!');
- } catch (e) {
- // TODO trigger notification
- console.log(`unwrap woeth error!\n${e.message}`);
- }
+ const { request } = await prepareWriteContract({
+ address: contracts.mainnet.WOETH.address,
+ abi: contracts.mainnet.WOETH.abi,
+ functionName: 'redeem',
+ args: [amountIn, address, address],
+ });
+ const { hash } = await writeContract(request);
+
+ return hash;
};
export default {
diff --git a/libs/oeth/swap/src/actions/wrapOETH.ts b/libs/oeth/swap/src/actions/wrapOETH.ts
index 075f0e696..869e97e46 100644
--- a/libs/oeth/swap/src/actions/wrapOETH.ts
+++ b/libs/oeth/swap/src/actions/wrapOETH.ts
@@ -6,7 +6,6 @@ import {
getPublicClient,
prepareWriteContract,
readContract,
- waitForTransaction,
writeContract,
} from '@wagmi/core';
import { formatUnits } from 'viem';
@@ -69,7 +68,9 @@ const estimateGas: EstimateGas = async ({ amountIn }) => {
args: [amountIn, whales.mainnet.OETH],
account: whales.mainnet.OETH,
});
- } catch {}
+ } catch {
+ console.log(`Wrap OETH uses fix gas estimate: 0`);
+ }
return gasEstimate;
};
@@ -112,7 +113,9 @@ const estimateApprovalGas: EstimateApprovalGas = async ({
args: [contracts.mainnet.WOETH.address, amountIn],
account: address,
});
- } catch {}
+ } catch {
+ console.log(`Wrap OETH uses fix approval gas estimate: 0`);
+ }
return approvalEstimate;
};
@@ -155,83 +158,40 @@ const estimateRoute: EstimateRoute = async ({
};
};
-const approve: Approve = async ({
- tokenIn,
- amountIn,
- onSuccess,
- onError,
- onReject,
-}) => {
- try {
- const { request } = await prepareWriteContract({
- address: tokenIn.address,
- abi: erc20ABI,
- functionName: 'approve',
- args: [contracts.mainnet.WOETH.address, amountIn],
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
-
- console.log(`wrap oeth approval done!`);
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`wrap oeth approval error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Wrap OETH approval');
- } else if (onError) {
- await onError('Wrap OETH approval');
- }
- }
+const approve: Approve = async ({ tokenIn, amountIn }) => {
+ const { request } = await prepareWriteContract({
+ address: tokenIn.address,
+ abi: erc20ABI,
+ functionName: 'approve',
+ args: [contracts.mainnet.WOETH.address, amountIn],
+ });
+ const { hash } = await writeContract(request);
+
+ return hash;
};
-const swap: Swap = async ({
- tokenIn,
- tokenOut,
- amountIn,
- onSuccess,
- onError,
- onReject,
-}) => {
+const swap: Swap = async ({ tokenIn, tokenOut, amountIn }) => {
const { address } = getAccount();
if (amountIn === 0n || isNilOrEmpty(address)) {
- return;
+ return null;
}
const approved = await allowance({ tokenIn, tokenOut });
if (approved < amountIn) {
- console.error(`wrap oeth is not approved`);
- if (onError) {
- await onError('Wrap OETH is not approved');
- }
- return;
+ throw new Error(`Wrap OETH is not approved`);
}
- try {
- const { request } = await prepareWriteContract({
- address: contracts.mainnet.WOETH.address,
- abi: contracts.mainnet.WOETH.abi,
- functionName: 'deposit',
- args: [amountIn, address],
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
-
- console.log('wrap oeth done!');
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`wrap oeth error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Wrap OETH');
- } else if (onError) {
- await onError('Wrap OETH');
- }
- }
+ const { request } = await prepareWriteContract({
+ address: contracts.mainnet.WOETH.address,
+ abi: contracts.mainnet.WOETH.abi,
+ functionName: 'deposit',
+ args: [amountIn, address],
+ });
+ const { hash } = await writeContract(request);
+
+ return hash;
};
export default {
diff --git a/libs/oeth/swap/src/components/ApyHeader.tsx b/libs/oeth/swap/src/components/ApyHeader.tsx
deleted file mode 100644
index d2bffbfcc..000000000
--- a/libs/oeth/swap/src/components/ApyHeader.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { useState } from 'react';
-
-import {
- Box,
- Button,
- Menu,
- MenuItem,
- Paper,
- Skeleton,
- Stack,
- Typography,
-} from '@mui/material';
-import { defineMessage, useIntl } from 'react-intl';
-
-import { useApiesQuery } from '../queries.generated';
-
-import type { StackProps } from '@mui/material';
-
-const trailingOptions = [
- { label: defineMessage({ defaultMessage: '30 days trailing' }), value: 30 },
- { label: defineMessage({ defaultMessage: '7 days trailing' }), value: 7 },
-];
-
-export const ApyHeader = (props: StackProps) => {
- const intl = useIntl();
- const [trailing, setTrailing] = useState(trailingOptions[0]);
- const [anchorEl, setAnchorEl] = useState(null);
- const { data: apy, isLoading: apyLoading } = useApiesQuery(
- {
- limit: 1,
- },
- {
- select: (data) => data.apies[0],
- },
- );
-
- return (
-
-
- {apyLoading ? (
-
- ) : (
-
- {intl.formatNumber(
- trailing.value === 30 ? apy.apy30DayAvg : apy.apy7DayAvg,
- { minimumFractionDigits: 2, maximumFractionDigits: 2 },
- )}
- %
-
- )}
-
-
-
-
-
- );
-};
diff --git a/libs/oeth/swap/src/components/BestRoutes.tsx b/libs/oeth/swap/src/components/BestRoutes.tsx
index 8de1bc56e..c449b6ed2 100644
--- a/libs/oeth/swap/src/components/BestRoutes.tsx
+++ b/libs/oeth/swap/src/components/BestRoutes.tsx
@@ -10,8 +10,7 @@ import type { Grid2Props } from '@mui/material';
export type BestRoutesProps = { isLoading: boolean } & Grid2Props;
export function BestRoutes(props: Grid2Props) {
- const [{ swapRoutes, selectedSwapRoute, isSwapRoutesLoading }] =
- useSwapState();
+ const [{ swapRoutes, selectedSwapRoute }] = useSwapState();
const handleSelectSwapRoute = useHandleSelectSwapRoute();
return (
@@ -24,7 +23,6 @@ export function BestRoutes(props: Grid2Props) {
isBest={index === 0}
onSelect={handleSelectSwapRoute}
route={route}
- isLoading={isSwapRoutesLoading}
/>
))}
diff --git a/libs/oeth/swap/src/components/SwapRouteCard.tsx b/libs/oeth/swap/src/components/SwapRouteCard.tsx
index 2e0bf7f83..f84d8764e 100644
--- a/libs/oeth/swap/src/components/SwapRouteCard.tsx
+++ b/libs/oeth/swap/src/components/SwapRouteCard.tsx
@@ -21,7 +21,6 @@ import type { EstimatedSwapRoute } from '../types';
export type SwapRouteCardProps = {
isSelected: boolean;
isBest: boolean;
- isLoading: boolean;
onSelect: (route: EstimatedSwapRoute) => void;
route: EstimatedSwapRoute;
} & Omit;
@@ -29,19 +28,29 @@ export type SwapRouteCardProps = {
export function SwapRouteCard({
isSelected,
isBest,
- isLoading,
onSelect,
route,
...rest
}: SwapRouteCardProps) {
const intl = useIntl();
- const [{ amountIn }] = useSwapState();
+ const [{ amountIn, isSwapRoutesLoading }] = useSwapState();
const { data: prices } = usePrices();
- const { data: swapGasPrice, isLoading: swapGasPriceLoading } = useGasPrice(
- route.gas,
- );
- const { data: approvalGasPrice, isLoading: approvalGasPriceLoading } =
- useGasPrice(route.approvalGas, { refetchInterval: 30e3 });
+ const {
+ data: swapGasPrice,
+ isLoading: swapGasPriceLoading,
+ isFetching: swapGasPriceFetching,
+ } = useGasPrice(route.gas, {
+ refetchInterval: 30e3,
+ enabled: route.gas > 0n,
+ });
+ const {
+ data: approvalGasPrice,
+ isLoading: approvalGasPriceLoading,
+ isFetching: approvalGasPriceFetching,
+ } = useGasPrice(route.approvalGas, {
+ refetchInterval: 30e3,
+ enabled: route.approvalGas > 0n,
+ });
const { data: allowance } = useSwapRouteAllowance(route);
const estimatedAmount = +formatUnits(
@@ -51,7 +60,9 @@ export function SwapRouteCard({
const convertedAmount =
(prices?.[route.tokenOut.symbol] ?? 1) * estimatedAmount;
const isGasLoading =
- isLoading || swapGasPriceLoading || approvalGasPriceLoading;
+ isSwapRoutesLoading ||
+ (swapGasPriceLoading && swapGasPriceFetching) ||
+ (approvalGasPriceLoading && approvalGasPriceFetching);
const gasPrice =
swapGasPrice?.gasCostUsd +
(allowance < amountIn ? approvalGasPrice?.gasCostUsd : 0);
@@ -114,7 +125,7 @@ export function SwapRouteCard({
- {isLoading ? (
+ {isSwapRoutesLoading ? (
) : (
- {isLoading ? (
+ {isSwapRoutesLoading ? (
) : (
formatAmount(route.estimatedAmount, route.tokenOut.decimals)
@@ -139,7 +150,7 @@ export function SwapRouteCard({
- {isLoading ? (
+ {isSwapRoutesLoading ? (
) : (
`(${intl.formatNumber(convertedAmount, currencyFormat)})`
@@ -152,7 +163,7 @@ export function SwapRouteCard({
fontWeight={500}
sx={{ fontSize: 12, marginBlock: { xs: 1.5, md: 1 } }}
>
- {isLoading ? (
+ {isSwapRoutesLoading ? (
) : (
intl.formatMessage(routeActionLabel[route.action])
@@ -169,7 +180,7 @@ export function SwapRouteCard({
{intl.formatMessage({ defaultMessage: 'Rate:' })}
- {isLoading ? (
+ {isSwapRoutesLoading ? (
) : (
`1:${intl.formatNumber(route.rate, quantityFormat)}`
diff --git a/libs/oeth/swap/src/hooks.ts b/libs/oeth/swap/src/hooks.tsx
similarity index 59%
rename from libs/oeth/swap/src/hooks.ts
rename to libs/oeth/swap/src/hooks.tsx
index dd95fbc14..5b7770817 100644
--- a/libs/oeth/swap/src/hooks.ts
+++ b/libs/oeth/swap/src/hooks.tsx
@@ -1,12 +1,22 @@
import { useCallback, useMemo } from 'react';
+import { Box } from '@mui/material';
+import {
+ ApprovalNotification,
+ SwapNotification,
+ useDeleteActivity,
+ usePushActivity,
+ useUpdateActivity,
+} from '@origin/oeth/shared';
+import { NotificationSnack } from '@origin/shared/components';
import {
useCurve,
usePushNotification,
useSlippage,
} from '@origin/shared/providers';
-import { isNilOrEmpty } from '@origin/shared/utils';
+import { isNilOrEmpty, isUserRejected } from '@origin/shared/utils';
import { useQuery, useQueryClient } from '@tanstack/react-query';
+import { waitForTransaction } from '@wagmi/core';
import { produce } from 'immer';
import { useIntl } from 'react-intl';
import { useAccount, useQueryClient as useWagmiClient } from 'wagmi';
@@ -161,12 +171,18 @@ export const useSwapRouteAllowance = (route: SwapRoute) => {
route?.tokenOut.symbol,
route?.action,
],
- queryFn: () =>
- swapActions[route.action].allowance({
- tokenIn: route.tokenIn,
- tokenOut: route.tokenOut,
- curve,
- }),
+ queryFn: async () => {
+ let res = 0n;
+ try {
+ res = await swapActions[route.action].allowance({
+ tokenIn: route.tokenIn,
+ tokenOut: route.tokenOut,
+ curve,
+ });
+ } catch {}
+
+ return res;
+ },
enabled: !isNilOrEmpty(route),
placeholderData: 0n,
});
@@ -179,8 +195,13 @@ export const useHandleApprove = () => {
const queryClient = useQueryClient();
const wagmiClient = useWagmiClient();
const pushNotification = usePushNotification();
- const [{ amountIn, selectedSwapRoute, tokenIn, tokenOut }, setSwapState] =
- useSwapState();
+ const pushActivity = usePushActivity();
+ const updateActivity = useUpdateActivity();
+ const deleteActivity = useDeleteActivity();
+ const [
+ { amountIn, amountOut, selectedSwapRoute, tokenIn, tokenOut },
+ setSwapState,
+ ] = useSwapState();
return useCallback(async () => {
if (isNilOrEmpty(selectedSwapRoute) || isNilOrEmpty(address)) {
@@ -192,62 +213,98 @@ export const useHandleApprove = () => {
draft.isApprovalLoading = true;
}),
);
- await swapActions[selectedSwapRoute.action].approve({
+ const activity = pushActivity({
tokenIn,
tokenOut,
+ type: 'approval',
+ status: 'pending',
amountIn,
- curve,
- onSuccess: () => {
+ amountOut,
+ });
+ try {
+ const hash = await swapActions[selectedSwapRoute.action].approve({
+ tokenIn,
+ tokenOut,
+ amountIn,
+ curve,
+ });
+ setSwapState(
+ produce((draft) => {
+ draft.isApprovalLoading = false;
+ }),
+ );
+ if (!isNilOrEmpty(hash)) {
+ const txReceipt = await waitForTransaction({ hash });
wagmiClient.invalidateQueries({
queryKey: ['swap_balance'],
});
queryClient.invalidateQueries({
queryKey: ['swap_allowance'],
});
+ updateActivity({ ...activity, status: 'success', txReceipt });
pushNotification({
- title: intl.formatMessage({ defaultMessage: 'Approval complete' }),
- severity: 'success',
+ content: (
+
+ ),
});
- setSwapState(
- produce((draft) => {
- draft.isApprovalLoading = false;
- }),
- );
- },
- onError: () => {
+ }
+ } catch (error) {
+ setSwapState(
+ produce((draft) => {
+ draft.isApprovalLoading = false;
+ }),
+ );
+ if (isUserRejected(error)) {
+ deleteActivity(activity.id);
pushNotification({
- title: intl.formatMessage({ defaultMessage: 'Approval failed' }),
- severity: 'error',
+ content: (
+ }
+ title={intl.formatMessage({
+ defaultMessage: 'Operation Cancelled',
+ })}
+ subtitle={intl.formatMessage({
+ defaultMessage: 'User rejected operation',
+ })}
+ />
+ ),
+ });
+ } else {
+ updateActivity({
+ ...activity,
+ status: 'error',
+ error: error?.shortMessage ?? error.message,
});
- setSwapState(
- produce((draft) => {
- draft.isApprovalLoading = false;
- }),
- );
- },
- onReject: () => {
pushNotification({
- title: intl.formatMessage({ defaultMessage: 'Approval cancelled' }),
- severity: 'info',
+ content: (
+
+ ),
});
- setSwapState(
- produce((draft) => {
- draft.isApprovalLoading = false;
- }),
- );
- },
- });
+ }
+ }
}, [
address,
amountIn,
+ amountOut,
curve,
+ deleteActivity,
intl,
+ pushActivity,
pushNotification,
queryClient,
selectedSwapRoute,
setSwapState,
tokenIn,
tokenOut,
+ updateActivity,
wagmiClient,
]);
};
@@ -260,6 +317,9 @@ export const useHandleSwap = () => {
const queryClient = useQueryClient();
const wagmiClient = useWagmiClient();
const pushNotification = usePushNotification();
+ const pushActivity = usePushActivity();
+ const updateActivity = useUpdateActivity();
+ const deleteActivity = useDeleteActivity();
const [
{ amountIn, amountOut, selectedSwapRoute, tokenIn, tokenOut },
setSwapState,
@@ -270,20 +330,36 @@ export const useHandleSwap = () => {
return;
}
+ const activity = pushActivity({
+ tokenIn,
+ tokenOut,
+ type: 'swap',
+ status: 'pending',
+ amountIn,
+ amountOut,
+ });
setSwapState(
produce((draft) => {
draft.isSwapLoading = true;
}),
);
- await swapActions[selectedSwapRoute.action].swap({
- tokenIn,
- tokenOut,
- amountIn,
- estimatedRoute: selectedSwapRoute,
- slippage,
- amountOut,
- curve,
- onSuccess: () => {
+ try {
+ const hash = await swapActions[selectedSwapRoute.action].swap({
+ tokenIn,
+ tokenOut,
+ amountIn,
+ estimatedRoute: selectedSwapRoute,
+ slippage,
+ amountOut,
+ curve,
+ });
+ setSwapState(
+ produce((draft) => {
+ draft.isSwapLoading = false;
+ }),
+ );
+ if (!isNilOrEmpty(hash)) {
+ const txReceipt = await waitForTransaction({ hash });
wagmiClient.invalidateQueries({
queryKey: ['swap_balance'],
});
@@ -291,34 +367,62 @@ export const useHandleSwap = () => {
queryKey: ['swap_allowance'],
});
pushNotification({
- title: intl.formatMessage({ defaultMessage: 'Swap complete' }),
- severity: 'success',
+ content: (
+
+ ),
});
- },
- onError: () => {
+ updateActivity({ ...activity, status: 'success', txReceipt });
+ }
+ } catch (error) {
+ setSwapState(
+ produce((draft) => {
+ draft.isSwapLoading = false;
+ }),
+ );
+ if (isUserRejected(error)) {
+ deleteActivity(activity.id);
pushNotification({
- title: intl.formatMessage({ defaultMessage: 'Swap failed' }),
- severity: 'error',
+ content: (
+ }
+ title={intl.formatMessage({
+ defaultMessage: 'Operation Cancelled',
+ })}
+ subtitle={intl.formatMessage({
+ defaultMessage: 'User rejected operation',
+ })}
+ />
+ ),
+ });
+ } else {
+ updateActivity({
+ ...activity,
+ status: 'error',
+ error: error.shortMessage,
});
- },
- onReject: () => {
pushNotification({
- title: intl.formatMessage({ defaultMessage: 'Swap cancelled' }),
- severity: 'info',
+ content: (
+
+ ),
});
- },
- });
- setSwapState(
- produce((draft) => {
- draft.isSwapLoading = false;
- }),
- );
+ }
+ }
}, [
address,
amountIn,
amountOut,
curve,
+ deleteActivity,
intl,
+ pushActivity,
pushNotification,
queryClient,
selectedSwapRoute,
@@ -326,6 +430,7 @@ export const useHandleSwap = () => {
slippage,
tokenIn,
tokenOut,
+ updateActivity,
wagmiClient,
]);
};
diff --git a/libs/oeth/swap/src/queries.generated.ts b/libs/oeth/swap/src/queries.generated.ts
deleted file mode 100644
index 148c79d8f..000000000
--- a/libs/oeth/swap/src/queries.generated.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { graphqlClient } from '@origin/oeth/shared';
-import { useQuery } from '@tanstack/react-query';
-
-import type * as Types from '@origin/oeth/shared';
-import type { UseQueryOptions } from '@tanstack/react-query';
-export type ApiesQueryVariables = Types.Exact<{
- limit?: Types.InputMaybe;
-}>;
-
-export type ApiesQuery = {
- __typename?: 'Query';
- apies: Array<{
- __typename?: 'APY';
- id: string;
- timestamp: string;
- apy7DayAvg: number;
- apy30DayAvg: number;
- }>;
-};
-
-export const ApiesDocument = `
- query Apies($limit: Int) {
- apies(
- limit: $limit
- orderBy: timestamp_DESC
- where: {timestamp_gt: "2023-06-06T12:38:47.000000Z"}
- ) {
- id
- timestamp
- apy7DayAvg
- apy30DayAvg
- }
-}
- `;
-export const useApiesQuery = (
- variables?: ApiesQueryVariables,
- options?: UseQueryOptions,
-) =>
- useQuery(
- variables === undefined ? ['Apies'] : ['Apies', variables],
- graphqlClient(ApiesDocument, variables),
- options,
- );
-
-useApiesQuery.getKey = (variables?: ApiesQueryVariables) =>
- variables === undefined ? ['Apies'] : ['Apies', variables];
-useApiesQuery.fetcher = (
- variables?: ApiesQueryVariables,
- options?: RequestInit['headers'],
-) =>
- graphqlClient(
- ApiesDocument,
- variables,
- options,
- );
diff --git a/libs/oeth/swap/src/state.ts b/libs/oeth/swap/src/state.ts
index 0e60a175f..dfd1b3893 100644
--- a/libs/oeth/swap/src/state.ts
+++ b/libs/oeth/swap/src/state.ts
@@ -10,7 +10,7 @@ import { createContainer } from 'react-tracked';
import { swapActions } from './actions';
import { getAvailableRoutes } from './utils';
-import type { SwapState } from './types';
+import type { EstimatedSwapRoute, SwapState } from './types';
export const { Provider: SwapProvider, useTracked: useSwapState } =
createContainer(() => {
@@ -58,19 +58,39 @@ export const { Provider: SwapProvider, useTracked: useSwapState } =
slippage,
state.amountIn.toString(),
] as const,
- queryFn: async () =>
- swapActions[route.action].estimateRoute({
- tokenIn: route.tokenIn,
- tokenOut: route.tokenOut,
- amountIn: state.amountIn,
- amountOut: state.amountOut,
- route,
- slippage,
- curve: {
- CurveRegistryExchange,
- OethPoolUnderlyings,
- },
- }),
+ queryFn: async () => {
+ let res: EstimatedSwapRoute;
+ try {
+ res = await swapActions[route.action].estimateRoute({
+ tokenIn: route.tokenIn,
+ tokenOut: route.tokenOut,
+ amountIn: state.amountIn,
+ amountOut: state.amountOut,
+ route,
+ slippage,
+ curve: {
+ CurveRegistryExchange,
+ OethPoolUnderlyings,
+ },
+ });
+ } catch (error) {
+ console.error(
+ `Fail to estimate route ${route.action}\n${error.message}`,
+ );
+ res = {
+ tokenIn: route.tokenIn,
+ tokenOut: route.tokenOut,
+ estimatedAmount: 0n,
+ action: route.action,
+ allowanceAmount: 0n,
+ approvalGas: 0n,
+ gas: 0n,
+ rate: 0,
+ };
+ }
+
+ return res;
+ },
}),
),
);
diff --git a/libs/oeth/swap/src/types.ts b/libs/oeth/swap/src/types.ts
index 4cf5e9db0..4a9f6076b 100644
--- a/libs/oeth/swap/src/types.ts
+++ b/libs/oeth/swap/src/types.ts
@@ -1,6 +1,5 @@
import type { Contract, Token } from '@origin/shared/contracts';
import type { HexAddress } from '@origin/shared/utils';
-import type { TransactionReceipt } from 'viem';
export type TokenSource = 'tokenIn' | 'tokenOut';
@@ -25,9 +24,6 @@ type Args = {
CurveRegistryExchange: Contract;
OethPoolUnderlyings: HexAddress[];
};
- onSuccess?: (txReceipt: TransactionReceipt) => void | Promise;
- onError?: (msg: string) => void | Promise;
- onReject?: (msg: string) => void | Promise;
};
export type EstimateAmount = (
@@ -63,17 +59,8 @@ export type EstimateApprovalGas = (
) => Promise;
export type Approve = (
- args: Pick<
- Args,
- | 'tokenIn'
- | 'tokenOut'
- | 'amountIn'
- | 'curve'
- | 'onSuccess'
- | 'onError'
- | 'onReject'
- >,
-) => Promise;
+ args: Pick,
+) => Promise;
export type Swap = (
args: Pick<
@@ -85,11 +72,8 @@ export type Swap = (
| 'slippage'
| 'estimatedRoute'
| 'curve'
- | 'onSuccess'
- | 'onError'
- | 'onReject'
>,
-) => Promise;
+) => Promise;
export type SwapApi = {
estimateAmount: EstimateAmount;
diff --git a/libs/oeth/swap/src/views/SwapView.tsx b/libs/oeth/swap/src/views/SwapView.tsx
index 285c7df93..5783a9abe 100644
--- a/libs/oeth/swap/src/views/SwapView.tsx
+++ b/libs/oeth/swap/src/views/SwapView.tsx
@@ -13,7 +13,7 @@ import {
Stack,
Typography,
} from '@mui/material';
-import { GasPopover } from '@origin/oeth/shared';
+import { ApyHeader, GasPopover } from '@origin/oeth/shared';
import { TokenInput } from '@origin/shared/components';
import {
ConnectedButton,
@@ -24,7 +24,6 @@ import { composeContexts, isNilOrEmpty } from '@origin/shared/utils';
import { useIntl } from 'react-intl';
import { mainnet, useAccount, useBalance, useNetwork } from 'wagmi';
-import { ApyHeader } from '../components/ApyHeader';
import { SwapRoute } from '../components/SwapRoute';
import { TokenSelectModal } from '../components/TokenSelectModal';
import { buttonActionLabel } from '../constants';
diff --git a/libs/shared/components/src/Icons/ActivityIcon.tsx b/libs/shared/components/src/Icons/ActivityIcon.tsx
new file mode 100644
index 000000000..baaf1133b
--- /dev/null
+++ b/libs/shared/components/src/Icons/ActivityIcon.tsx
@@ -0,0 +1,48 @@
+import { keyframes } from '@emotion/react';
+import { Box, Fade } from '@mui/material';
+
+import type { BoxProps } from '@mui/material';
+
+export type ActivityIconStatus = 'idle' | 'pending' | 'success' | 'error';
+
+const spin = keyframes`
+ to {
+ transform: rotate(360deg);
+ }
+`;
+
+const iconPaths: Record = {
+ idle: '/images/activity.svg',
+ pending: '/images/pending.svg',
+ error: '/images/failed.svg',
+ success: '/images/success.svg',
+};
+
+type ActivityIconProps = {
+ status: ActivityIconStatus;
+ disablePendingSpin?: boolean;
+} & BoxProps<'img'>;
+
+export const ActivityIcon = ({
+ status,
+ disablePendingSpin,
+ ...rest
+}: ActivityIconProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/libs/shared/components/src/LinkIcon/index.tsx b/libs/shared/components/src/Icons/LinkIcon.tsx
similarity index 100%
rename from libs/shared/components/src/LinkIcon/index.tsx
rename to libs/shared/components/src/Icons/LinkIcon.tsx
diff --git a/libs/shared/components/src/Icons/index.ts b/libs/shared/components/src/Icons/index.ts
new file mode 100644
index 000000000..a592efbb0
--- /dev/null
+++ b/libs/shared/components/src/Icons/index.ts
@@ -0,0 +1,2 @@
+export * from './ActivityIcon';
+export * from './LinkIcon';
diff --git a/libs/shared/components/src/Notifications/NotificationSnack.tsx b/libs/shared/components/src/Notifications/NotificationSnack.tsx
new file mode 100644
index 000000000..7cecf6ab1
--- /dev/null
+++ b/libs/shared/components/src/Notifications/NotificationSnack.tsx
@@ -0,0 +1,56 @@
+import { Stack, Typography } from '@mui/material';
+import { isNilOrEmpty } from '@origin/shared/utils';
+
+import { LinkIcon } from '../Icons';
+
+import type { StackProps, TypographyProps } from '@mui/material';
+import type { ReactNode } from 'react';
+
+export type NotificationSnackProps = {
+ icon?: ReactNode;
+ title: ReactNode;
+ href?: string;
+ subtitle: ReactNode;
+ endIcon?: ReactNode;
+ titleProps?: TypographyProps;
+ subtitleProps?: TypographyProps;
+} & Omit;
+
+export const NotificationSnack = ({
+ icon,
+ title,
+ href,
+ subtitle,
+ endIcon,
+ titleProps,
+ subtitleProps,
+ ...rest
+}: NotificationSnackProps) => {
+ return (
+
+
+
+ {icon}
+ {typeof title === 'string' ? (
+ {title}
+ ) : (
+ title
+ )}
+ {!isNilOrEmpty(href) && }
+
+
+ {typeof subtitle === 'string' ? (
+
+ {subtitle}
+
+ ) : (
+ subtitle
+ )}
+
+
+
+ {endIcon}
+
+
+ );
+};
diff --git a/libs/shared/components/src/Notifications/index.ts b/libs/shared/components/src/Notifications/index.ts
new file mode 100644
index 000000000..755134b64
--- /dev/null
+++ b/libs/shared/components/src/Notifications/index.ts
@@ -0,0 +1 @@
+export * from './NotificationSnack';
diff --git a/libs/shared/components/src/index.ts b/libs/shared/components/src/index.ts
index 525e4f83b..1e8949d57 100644
--- a/libs/shared/components/src/index.ts
+++ b/libs/shared/components/src/index.ts
@@ -2,8 +2,9 @@ export * from './Cards';
export * from './DataTable';
export * from './InfoTooltip';
export * from './Inputs';
-export * from './LinkIcon';
+export * from './Icons';
export * from './Loader';
export * from './MiddleTruncated';
export * from './Mix';
+export * from './Notifications';
export * from './top-nav';
diff --git a/libs/shared/components/src/top-nav/ConnectedButton.tsx b/libs/shared/components/src/top-nav/ConnectedButton.tsx
index 121e778be..523c4b805 100644
--- a/libs/shared/components/src/top-nav/ConnectedButton.tsx
+++ b/libs/shared/components/src/top-nav/ConnectedButton.tsx
@@ -12,7 +12,7 @@ import {
} from '@mui/material';
import { useIntl } from 'react-intl';
-import { LinkIcon } from '../LinkIcon';
+import { LinkIcon } from '../Icons';
import { MiddleTruncated } from '../MiddleTruncated';
import { Icon } from './Icon';
import { styles } from './utils';
diff --git a/libs/shared/components/src/top-nav/Transaction.tsx b/libs/shared/components/src/top-nav/Transaction.tsx
index 5bef600f7..437090a1d 100644
--- a/libs/shared/components/src/top-nav/Transaction.tsx
+++ b/libs/shared/components/src/top-nav/Transaction.tsx
@@ -2,7 +2,7 @@ import { keyframes } from '@emotion/react';
import { Stack, Typography } from '@mui/material';
import { useIntl } from 'react-intl';
-import { LinkIcon } from '../LinkIcon';
+import { LinkIcon } from '../Icons';
import { Icon } from './Icon';
import { messages } from './utils';
diff --git a/libs/shared/providers/src/notifications/components/NotificationSnack.tsx b/libs/shared/providers/src/notifications/components/NotificationSnack.tsx
index e20102af1..e4f5d4bda 100644
--- a/libs/shared/providers/src/notifications/components/NotificationSnack.tsx
+++ b/libs/shared/providers/src/notifications/components/NotificationSnack.tsx
@@ -47,8 +47,12 @@ export const NotificationSnack = ({
borderRadius: 1,
minWidth: { sm: 300, md: 400, lg: 500, xl: 600 },
maxWidth: { sm: 400, md: 500, lg: 600, xl: 700 },
+ '.MuiAlert-message': {
+ width: 1,
+ },
...AlertProps?.sx,
}}
+ {...(!isNilOrEmpty(content) && { icon: false })}
onClose={handleCloseClick}
>
{!isNilOrEmpty(title) && (
diff --git a/libs/shared/providers/src/notifications/hooks.ts b/libs/shared/providers/src/notifications/hooks.ts
index ae269fc29..a127e3db4 100644
--- a/libs/shared/providers/src/notifications/hooks.ts
+++ b/libs/shared/providers/src/notifications/hooks.ts
@@ -10,7 +10,7 @@ import type { ReactNode } from 'react';
type NotificationOptions = {
severity?: AlertColor;
- title: string;
+ title?: string;
message?: string;
content?: ReactNode;
visible?: boolean;
diff --git a/libs/shared/providers/src/notifications/types.ts b/libs/shared/providers/src/notifications/types.ts
index 322fa21e7..1c4e95d31 100644
--- a/libs/shared/providers/src/notifications/types.ts
+++ b/libs/shared/providers/src/notifications/types.ts
@@ -3,8 +3,8 @@ import type { ReactNode } from 'react';
export type Notification = {
id: string;
- severity: AlertColor;
- title: string;
+ severity?: AlertColor;
+ title?: string;
message?: string;
content?: ReactNode;
createdOn: number;
diff --git a/libs/shared/theme/src/theme.tsx b/libs/shared/theme/src/theme.tsx
index 773463223..81f65f3d0 100644
--- a/libs/shared/theme/src/theme.tsx
+++ b/libs/shared/theme/src/theme.tsx
@@ -164,7 +164,7 @@ export const theme = extendTheme({
),
warning: (
-
+
),
},
},
@@ -364,6 +364,16 @@ export const theme = extendTheme({
}),
},
},
+ MuiIconButton: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ background: theme.palette.background.gradientPaper,
+ '&:hover': {
+ background: theme.palette.background.paper,
+ },
+ }),
+ },
+ },
MuiInputBase: {
styleOverrides: {
root: ({ theme }) => ({
@@ -464,7 +474,7 @@ export const theme = extendTheme({
},
styleOverrides: {
text: ({ theme }) => ({
- borderRadius: 15,
+ borderRadius: 10,
// backgroundColor: theme.palette.grey[900],
}),
},
diff --git a/libs/shared/utils/src/errors.ts b/libs/shared/utils/src/errors.ts
new file mode 100644
index 000000000..f99046b61
--- /dev/null
+++ b/libs/shared/utils/src/errors.ts
@@ -0,0 +1,6 @@
+import { pathEq } from 'ramda';
+
+export const isUserRejected = pathEq('UserRejectedRequestError', [
+ 'cause',
+ 'name',
+]);
diff --git a/libs/shared/utils/src/index.ts b/libs/shared/utils/src/index.ts
index aef480533..c571e023d 100644
--- a/libs/shared/utils/src/index.ts
+++ b/libs/shared/utils/src/index.ts
@@ -1,6 +1,7 @@
export * from './addresses';
export * from './BigInt';
export * from './composeContext';
+export * from './errors';
export * from './formatters';
export * from './isNilOrEmpty';
export * from './types';