diff --git a/apps/oeth/src/routes.ts b/apps/oeth/src/routes.ts
index dde93b321..ee7103f0c 100644
--- a/apps/oeth/src/routes.ts
+++ b/apps/oeth/src/routes.ts
@@ -1,4 +1,5 @@
import { HistoryView } from '@origin/oeth/history';
+import { RedeemView } from '@origin/oeth/redeem';
import { SwapView } from '@origin/oeth/swap';
import { defineMessage } from 'react-intl';
@@ -16,6 +17,11 @@ export const routes: RouteObject[] = [
Component: SwapView,
handle: { label: defineMessage({ defaultMessage: 'Swap' }) },
},
+ {
+ path: '/redeem',
+ Component: RedeemView,
+ handle: { label: defineMessage({ defaultMessage: 'Redeem' }) },
+ },
{
path: '/history',
Component: HistoryView,
diff --git a/libs/oeth/redeem/.babelrc b/libs/oeth/redeem/.babelrc
new file mode 100644
index 000000000..1ea870ead
--- /dev/null
+++ b/libs/oeth/redeem/.babelrc
@@ -0,0 +1,12 @@
+{
+ "presets": [
+ [
+ "@nx/react/babel",
+ {
+ "runtime": "automatic",
+ "useBuiltIns": "usage"
+ }
+ ]
+ ],
+ "plugins": []
+}
diff --git a/libs/oeth/redeem/.eslintrc.json b/libs/oeth/redeem/.eslintrc.json
new file mode 100644
index 000000000..a786f2cf3
--- /dev/null
+++ b/libs/oeth/redeem/.eslintrc.json
@@ -0,0 +1,34 @@
+{
+ "extends": [
+ "plugin:@nx/react",
+ "../../../.eslintrc.json"
+ ],
+ "ignorePatterns": [
+ "!**/*"
+ ],
+ "overrides": [
+ {
+ "files": [
+ "*.ts",
+ "*.tsx",
+ "*.js",
+ "*.jsx"
+ ],
+ "rules": {}
+ },
+ {
+ "files": [
+ "*.ts",
+ "*.tsx"
+ ],
+ "rules": {}
+ },
+ {
+ "files": [
+ "*.js",
+ "*.jsx"
+ ],
+ "rules": {}
+ }
+ ]
+}
diff --git a/libs/oeth/redeem/README.md b/libs/oeth/redeem/README.md
new file mode 100644
index 000000000..23f8464a4
--- /dev/null
+++ b/libs/oeth/redeem/README.md
@@ -0,0 +1,7 @@
+# redeem
+
+This library was generated with [Nx](https://nx.dev).
+
+## Running unit tests
+
+Run `nx test redeem` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/oeth/redeem/project.json b/libs/oeth/redeem/project.json
new file mode 100644
index 000000000..dad759723
--- /dev/null
+++ b/libs/oeth/redeem/project.json
@@ -0,0 +1,20 @@
+{
+ "name": "oeth-redeem",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "libs/oeth/redeem/src",
+ "projectType": "library",
+ "tags": [],
+ "targets": {
+ "lint": {
+ "executor": "@nx/linter:eslint",
+ "outputs": [
+ "{options.outputFile}"
+ ],
+ "options": {
+ "lintFilePatterns": [
+ "libs/oeth/redeem/**/*.{ts,tsx,js,jsx}"
+ ]
+ }
+ }
+ }
+}
diff --git a/libs/oeth/redeem/src/components/Mix.tsx b/libs/oeth/redeem/src/components/Mix.tsx
new file mode 100644
index 000000000..fa2fa137e
--- /dev/null
+++ b/libs/oeth/redeem/src/components/Mix.tsx
@@ -0,0 +1,43 @@
+import { Box, Stack } from '@mui/material';
+
+import type { SxProps } from '@mui/material';
+
+interface Props {
+ size?: number;
+ sx?: SxProps;
+}
+
+export function Mix({ size = 2, sx }: Props) {
+ const imgSrc = [
+ '/images/currency/weth-icon-small.png',
+ '/images/currency/reth-icon-small.png',
+ '/images/currency/steth-icon-small.svg',
+ '/images/currency/frxeth-icon-small.svg',
+ ];
+
+ return (
+
+ {imgSrc.map((img, index, arr) => (
+
+ ))}
+
+ );
+}
diff --git a/libs/oeth/redeem/src/components/RedeemInfo.tsx b/libs/oeth/redeem/src/components/RedeemInfo.tsx
new file mode 100644
index 000000000..af891b812
--- /dev/null
+++ b/libs/oeth/redeem/src/components/RedeemInfo.tsx
@@ -0,0 +1,43 @@
+import { Box, Tooltip, Typography } from '@mui/material';
+import { useIntl } from 'react-intl';
+
+import type { Theme } from '@mui/material';
+
+export function RedeemInfo() {
+ const intl = useIntl();
+
+ return (
+
+ {intl.formatMessage({
+ defaultMessage: 'Redeem OETH for the basket of underlying assets',
+ })}
+
+ }
+ componentsProps={{
+ tooltip: {
+ sx: {
+ paddingInline: 2,
+ paddingBlock: 1.5,
+ borderRadius: 2,
+ border: '1px solid',
+ borderColor: (theme) => theme.palette.grey[500],
+ boxShadow: (theme: Theme) => theme.shadows[23],
+ },
+ },
+ }}
+ >
+ theme.typography.pxToRem(12),
+ height: (theme) => theme.typography.pxToRem(12),
+ color: (theme) => theme.palette.text.secondary,
+ }}
+ >
+
+ );
+}
diff --git a/libs/oeth/redeem/src/components/RedeemRoute.tsx b/libs/oeth/redeem/src/components/RedeemRoute.tsx
new file mode 100644
index 000000000..a67cd7800
--- /dev/null
+++ b/libs/oeth/redeem/src/components/RedeemRoute.tsx
@@ -0,0 +1,61 @@
+import { Collapse, Stack, Typography } from '@mui/material';
+import { Card, cardStyles } from '@origin/shared/components';
+import { useIntl } from 'react-intl';
+
+import { useRedeemState } from '../state';
+import { RedeemInfo } from './RedeemInfo';
+import { RedeemSplitCard } from './RedeemSplitCard';
+import { RouteCard } from './RouteCard';
+
+import type { CardProps } from '@mui/material';
+
+export function RedeemRoute(props: Omit) {
+ const intl = useIntl();
+ const [{ amountOut }] = useRedeemState();
+
+ const hasContent = amountOut > 0n;
+
+ return (
+ theme.palette.background.default,
+ backgroundColor: 'grey.900',
+ borderRadius: 1,
+ ...props?.sx,
+ }}
+ title={
+
+ {intl.formatMessage({ defaultMessage: 'Route' })}
+
+
+ }
+ sxCardTitle={{ borderBottom: 'none', paddingBlock: 1, paddingInline: 2 }}
+ sxCardContent={{
+ ...(hasContent
+ ? cardStyles
+ : {
+ p: 0,
+ paddingBlock: 0,
+ paddingInline: 0,
+ '&:last-child': { pb: 0 },
+ }),
+ }}
+ >
+
+
+
+
+
+
+
+ );
+}
diff --git a/libs/oeth/redeem/src/components/RedeemSplitCard.tsx b/libs/oeth/redeem/src/components/RedeemSplitCard.tsx
new file mode 100644
index 000000000..ae8343ea9
--- /dev/null
+++ b/libs/oeth/redeem/src/components/RedeemSplitCard.tsx
@@ -0,0 +1,96 @@
+import {
+ Box,
+ Card,
+ CardHeader,
+ Skeleton,
+ Stack,
+ Typography,
+} from '@mui/material';
+import { usePrices } from '@origin/shared/providers';
+import { currencyFormat, formatAmount } from '@origin/shared/utils';
+import { useIntl } from 'react-intl';
+import { formatUnits } from 'viem';
+
+import { useRedeemState } from '../state';
+import { Mix } from './Mix';
+
+import type { CardProps } from '@origin/shared/components';
+
+export const RedeemSplitCard = (
+ props: Omit,
+) => {
+ const intl = useIntl();
+ const { data: prices, isLoading: isPricesLoading } = usePrices();
+ const [{ split, isEstimateLoading }] = useRedeemState();
+
+ return (
+
+ theme.spacing(0.5, 0, 1.5, 0),
+ borderBottom: (theme) => `1px solid ${theme.palette.divider}`,
+ }}
+ title={
+
+
+
+ {intl.formatMessage({
+ defaultMessage: 'Redeem basket of assets',
+ })}
+
+
+ }
+ />
+
+ {split?.map((s) => {
+ const converted =
+ +formatUnits(s.amount, s.token.decimals) * prices?.[s.token.symbol];
+
+ return (
+
+
+
+ {s.token.symbol}
+
+
+
+ {isEstimateLoading ? (
+
+ ) : (
+ formatAmount(s.amount, s.token.decimals)
+ )}
+
+ {isPricesLoading || isEstimateLoading ? (
+
+ ) : (
+
+ {intl.formatNumber(converted, currencyFormat)}
+
+ )}
+
+
+ );
+ })}
+
+
+ );
+};
diff --git a/libs/oeth/redeem/src/components/RouteCard.tsx b/libs/oeth/redeem/src/components/RouteCard.tsx
new file mode 100644
index 000000000..b9bab0353
--- /dev/null
+++ b/libs/oeth/redeem/src/components/RouteCard.tsx
@@ -0,0 +1,171 @@
+import {
+ Box,
+ Card,
+ CardHeader,
+ Skeleton,
+ Stack,
+ Typography,
+} from '@mui/material';
+import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
+import { tokens } from '@origin/shared/contracts';
+import { usePrices } from '@origin/shared/providers';
+import {
+ currencyFormat,
+ formatAmount,
+ quantityFormat,
+} from '@origin/shared/utils';
+import { useIntl } from 'react-intl';
+import { formatUnits } from 'viem';
+
+import { MIX_TOKEN } from '../constants';
+import { useRedeemState } from '../state';
+
+import type { CardProps } from '@mui/material';
+
+export function RouteCard(props: Omit) {
+ const intl = useIntl();
+ const { data: prices } = usePrices();
+ const [{ amountOut, gas, rate, isEstimateLoading }] = useRedeemState();
+
+ const estimatedAmount = +formatUnits(amountOut, MIX_TOKEN.decimals);
+ const convertedAmount =
+ (prices?.[tokens.mainnet.WETH.symbol] ?? 1) * estimatedAmount;
+ const gasAmount = +formatUnits(gas, tokens.mainnet.ETH.decimals);
+
+ return (
+
+
+
+ {isEstimateLoading ? (
+
+ ) : (
+
+ )}
+
+
+
+ {isEstimateLoading ? (
+
+ ) : (
+ formatAmount(amountOut, MIX_TOKEN.decimals)
+ )}
+
+
+
+
+ {isEstimateLoading ? (
+
+ ) : (
+ `(${intl.formatNumber(convertedAmount, currencyFormat)})`
+ )}
+
+
+ theme.shape.borderRadius,
+ background: (theme) => theme.palette.background.gradient1,
+ color: 'primary.contrastText',
+ fontSize: (theme) => theme.typography.pxToRem(12),
+ top: (theme) => theme.spacing(-3),
+ right: (theme) => theme.spacing(-1),
+ paddingInline: 1,
+ }}
+ >
+ {intl.formatMessage({ defaultMessage: 'Best' })}
+
+
+ }
+ >
+
+
+ {isEstimateLoading ? (
+
+ ) : (
+ intl.formatMessage({
+ defaultMessage: 'Request withdrawal via OETH vault',
+ })
+ )}
+
+
+
+
+ {intl.formatMessage({ defaultMessage: 'Rate:' })}
+
+
+ {isEstimateLoading ? (
+
+ ) : (
+ `1:${intl.formatNumber(rate, quantityFormat)}`
+ )}
+
+
+
+
+ {intl.formatMessage({ defaultMessage: 'Gas:' })}
+
+
+ {isEstimateLoading ? (
+
+ ) : (
+ `~${intl.formatNumber(gasAmount, currencyFormat)}`
+ )}
+
+
+
+
+ {intl.formatMessage({ defaultMessage: 'Wait time:' })}
+
+
+ {isEstimateLoading ? (
+
+ ) : (
+ intl.formatMessage({ defaultMessage: '~3 days' })
+ )}
+
+
+
+
+ );
+}
diff --git a/libs/oeth/redeem/src/constants.ts b/libs/oeth/redeem/src/constants.ts
new file mode 100644
index 000000000..f94272e7c
--- /dev/null
+++ b/libs/oeth/redeem/src/constants.ts
@@ -0,0 +1,13 @@
+import { erc20ABI, mainnet } from 'wagmi';
+
+import type { Token } from '@origin/shared/contracts';
+
+export const MIX_TOKEN: Token = {
+ address: undefined,
+ chainId: mainnet.id,
+ abi: erc20ABI,
+ decimals: 18,
+ name: 'Redeem Mix',
+ symbol: 'MIX_TOKEN',
+ icon: '/images/backed-graphic.svg',
+};
diff --git a/libs/oeth/redeem/src/hooks.tsx b/libs/oeth/redeem/src/hooks.tsx
new file mode 100644
index 000000000..1e8b8cd90
--- /dev/null
+++ b/libs/oeth/redeem/src/hooks.tsx
@@ -0,0 +1,126 @@
+import { useCallback } from 'react';
+
+import { contracts } from '@origin/shared/contracts';
+import {
+ BlockExplorerLink,
+ usePushNotification,
+} from '@origin/shared/providers';
+import { isNilOrEmpty } from '@origin/shared/utils';
+import {
+ prepareWriteContract,
+ waitForTransaction,
+ writeContract,
+} from '@wagmi/core';
+import { produce } from 'immer';
+import { useIntl } from 'react-intl';
+import { formatUnits, parseUnits } from 'viem';
+import { useAccount, useQueryClient } from 'wagmi';
+
+import { MIX_TOKEN } from './constants';
+import { useRedeemState } from './state';
+
+export const useHandleAmountInChange = () => {
+ const [, setRedeemState] = useRedeemState();
+
+ return useCallback(
+ (amount: bigint) => {
+ setRedeemState(
+ produce((state) => {
+ state.amountIn = amount;
+ state.isEstimateLoading = amount !== 0n;
+ }),
+ );
+ },
+ [setRedeemState],
+ );
+};
+
+export const useHandleSlippageChange = () => {
+ const [, setRedeemState] = useRedeemState();
+
+ return useCallback(
+ (value: number) => {
+ setRedeemState(
+ produce((state) => {
+ state.slippage = value;
+ }),
+ );
+ },
+ [setRedeemState],
+ );
+};
+
+export const useHandleRedeem = () => {
+ const intl = useIntl();
+ const pushNotification = usePushNotification();
+ const { address } = useAccount();
+ const [{ amountIn, amountOut, slippage }, setRedeemState] = useRedeemState();
+ const wagmiClient = useQueryClient();
+
+ return useCallback(async () => {
+ if (amountIn === 0n || isNilOrEmpty(address)) {
+ return;
+ }
+
+ setRedeemState(
+ produce((draft) => {
+ draft.isRedeemLoading = true;
+ }),
+ );
+
+ 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,
+ functionName: 'redeem',
+ args: [amountIn, minAmountOut],
+ });
+ const { hash } = await writeContract(request);
+ const txReceipt = await waitForTransaction({ hash });
+
+ console.log('redeem vault done!');
+ wagmiClient.invalidateQueries({ queryKey: ['redeem_balance'] });
+ pushNotification({
+ title: intl.formatMessage({ defaultMessage: 'Redeem complete' }),
+ severity: 'success',
+ content: ,
+ });
+ } catch (e) {
+ console.error(`redeem vault error!\n${e.message}`);
+ if (e?.code === 'ACTION_REJECTED') {
+ pushNotification({
+ title: intl.formatMessage({ defaultMessage: 'Redeem vault' }),
+ severity: 'info',
+ });
+ } else {
+ pushNotification({
+ title: intl.formatMessage({ defaultMessage: 'Redeem vault' }),
+ severity: 'error',
+ });
+ }
+ }
+
+ setRedeemState(
+ produce((draft) => {
+ draft.isRedeemLoading = false;
+ }),
+ );
+ }, [
+ address,
+ amountIn,
+ amountOut,
+ intl,
+ pushNotification,
+ setRedeemState,
+ slippage,
+ wagmiClient,
+ ]);
+};
diff --git a/libs/oeth/redeem/src/index.ts b/libs/oeth/redeem/src/index.ts
new file mode 100644
index 000000000..2d32501e5
--- /dev/null
+++ b/libs/oeth/redeem/src/index.ts
@@ -0,0 +1 @@
+export * from './views/RedeemView';
diff --git a/libs/oeth/redeem/src/state.ts b/libs/oeth/redeem/src/state.ts
new file mode 100644
index 000000000..fb1bab443
--- /dev/null
+++ b/libs/oeth/redeem/src/state.ts
@@ -0,0 +1,172 @@
+import { useEffect, useState } from 'react';
+
+import { contracts, tokens, whales } from '@origin/shared/contracts';
+import { usePushNotification } from '@origin/shared/providers';
+import { isNilOrEmpty } from '@origin/shared/utils';
+import { useDebouncedEffect } from '@react-hookz/web';
+import { useQuery, useQueryClient } from '@tanstack/react-query';
+import { getAccount, getPublicClient, readContract } from '@wagmi/core';
+import { produce } from 'immer';
+import { useIntl } from 'react-intl';
+import { createContainer } from 'react-tracked';
+import { formatUnits, isAddressEqual, parseUnits } from 'viem';
+
+import { MIX_TOKEN } from './constants';
+
+import type { RedeemState } from './types';
+
+export const { Provider: RedeemProvider, useTracked: useRedeemState } =
+ createContainer(() => {
+ const [state, setState] = useState({
+ amountIn: 0n,
+ amountOut: 0n,
+ split: [],
+ gas: 0n,
+ rate: 0,
+ slippage: 0.01,
+ isEstimateLoading: false,
+ isRedeemLoading: false,
+ });
+ const intl = useIntl();
+ const queryClient = useQueryClient();
+ const pushNotification = usePushNotification();
+
+ const { data: splitAddresses } = useQuery({
+ queryKey: ['assetsDecimals'],
+ queryFn: async () => {
+ const assets = await readContract({
+ address: contracts.mainnet.OETHVaultCore.address,
+ abi: contracts.mainnet.OETHVaultCore.abi,
+ functionName: 'getAllAssets',
+ });
+
+ return assets;
+ },
+ staleTime: Infinity,
+ });
+
+ useEffect(() => {
+ if (splitAddresses) {
+ setState(
+ produce((draft) => {
+ draft.split = splitAddresses.map((a) => ({
+ amount: 0n,
+ token: Object.values(tokens.mainnet).find(
+ (t) => !isNilOrEmpty(t.address) && isAddressEqual(a, t.address),
+ ),
+ }));
+ }),
+ );
+ }
+ }, [splitAddresses]);
+
+ useDebouncedEffect(
+ async () => {
+ if (state.amountIn === 0n) {
+ setState(
+ produce((draft) => {
+ draft.amountOut = 0n;
+ draft.split.forEach((a) => (a.amount = 0n));
+ draft.isEstimateLoading = false;
+ }),
+ );
+ return;
+ }
+
+ let splitEstimates;
+ try {
+ splitEstimates = await queryClient.fetchQuery({
+ queryKey: ['splitEstimates', state.amountIn.toString()],
+ queryFn: () =>
+ readContract({
+ address: contracts.mainnet.OETHVaultCore.address,
+ abi: contracts.mainnet.OETHVaultCore.abi,
+ functionName: 'calculateRedeemOutputs',
+ args: [state.amountIn],
+ }),
+ });
+ } catch (e) {
+ console.error(`redeem vault estimate amount error.\n${e.message}`);
+ setState(
+ produce((draft) => {
+ draft.amountIn = 0n;
+ draft.amountOut = 0n;
+ draft.split = [];
+ draft.isEstimateLoading = false;
+ }),
+ );
+ pushNotification({
+ title: intl.formatMessage({
+ defaultMessage: 'Error while estimating',
+ }),
+ message: e.shortMessage,
+ severity: 'error',
+ });
+
+ return;
+ }
+
+ const total = splitEstimates.reduce((acc, curr, i) => {
+ if (state.split[i].token.decimals !== MIX_TOKEN.decimals) {
+ const exp = MIX_TOKEN.decimals - state.split[i].token.decimals;
+
+ return acc + curr * (10n ^ BigInt(exp));
+ }
+
+ return acc + curr;
+ }, 0n);
+
+ let gasEstimate = 0n;
+ const publicClient = getPublicClient();
+ const { address } = getAccount();
+
+ const minAmountOut = parseUnits(
+ (
+ +formatUnits(total, MIX_TOKEN.decimals) -
+ +formatUnits(total, MIX_TOKEN.decimals) * state.slippage
+ ).toString(),
+ MIX_TOKEN.decimals,
+ );
+
+ try {
+ gasEstimate = await queryClient.fetchQuery({
+ queryKey: [
+ 'estimateGasRedeem',
+ state.amountIn.toString(),
+ minAmountOut.toString(),
+ address,
+ ],
+ queryFn: () =>
+ publicClient.estimateContractGas({
+ address: contracts.mainnet.OETHVaultCore.address,
+ abi: contracts.mainnet.OETHVaultCore.abi,
+ functionName: 'redeem',
+ args: [state.amountIn, minAmountOut],
+ account: whales.mainnet.OETH,
+ }),
+ });
+ } catch (e) {
+ console.error(
+ `redeem vault estimate gas error. Using default!\n${e.message}`,
+ );
+ gasEstimate = 1500000n;
+ }
+
+ setState(
+ produce((draft) => {
+ draft.amountOut = total;
+ draft.split.forEach((a, i) => (a.amount = splitEstimates[i]));
+ draft.gas = gasEstimate;
+ draft.rate =
+ +formatUnits(state.amountIn, tokens.mainnet.OETH.decimals) /
+ +formatUnits(total, MIX_TOKEN.decimals);
+ draft.isEstimateLoading = false;
+ }),
+ );
+ },
+ [state.amountIn],
+ state.amountIn === 0n ? 0 : 800,
+ );
+
+ return [state, setState];
+ });
diff --git a/libs/oeth/redeem/src/types.ts b/libs/oeth/redeem/src/types.ts
new file mode 100644
index 000000000..415f58999
--- /dev/null
+++ b/libs/oeth/redeem/src/types.ts
@@ -0,0 +1,17 @@
+import type { Token } from '@origin/shared/contracts';
+
+export type RedeemEstimate = {
+ token: Token;
+ amount: bigint;
+};
+
+export type RedeemState = {
+ amountIn: bigint;
+ amountOut: bigint;
+ 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
new file mode 100644
index 000000000..c12f47d75
--- /dev/null
+++ b/libs/oeth/redeem/src/views/RedeemView.tsx
@@ -0,0 +1,216 @@
+import { alpha, Box, CircularProgress, Stack, Typography } from '@mui/material';
+import { GasPopover } from '@origin/oeth/shared';
+import { Card, TokenInput } from '@origin/shared/components';
+import { tokens } from '@origin/shared/contracts';
+import { ConnectedButton, usePrices } from '@origin/shared/providers';
+import { useIntl } from 'react-intl';
+import { useAccount, useBalance } from 'wagmi';
+
+import { RedeemRoute } from '../components/RedeemRoute';
+import {
+ useHandleAmountInChange,
+ useHandleRedeem,
+ useHandleSlippageChange,
+} from '../hooks';
+import { RedeemProvider, useRedeemState } from '../state';
+
+import type { BoxProps } from '@mui/material';
+
+const commonStyles = {
+ paddingBlock: 2.5,
+ paddingBlockStart: 2.625,
+ paddingInline: 2,
+ border: '1px solid',
+ borderColor: 'divider',
+ borderRadius: 1,
+};
+
+const tokenInputStyles = {
+ border: 'none',
+ backgroundColor: 'transparent',
+ borderRadius: 0,
+ paddingBlock: 0,
+ paddingInline: 0,
+ borderImageWidth: 0,
+ boxSizing: 'border-box',
+ '& .MuiInputBase-input': {
+ padding: 0,
+ lineHeight: '1.875rem',
+ boxSizing: 'border-box',
+ fontStyle: 'normal',
+ fontFamily: 'Sailec, Inter, Helvetica, Arial, sans-serif',
+ fontSize: '1.5rem',
+ fontWeight: 700,
+ height: '1.5rem',
+ color: 'primary.contrastText',
+ '&::placeholder': {
+ color: 'text.secondary',
+ opacity: 1,
+ },
+ },
+};
+
+export const RedeemView = () => (
+
+
+
+);
+
+function RedeemViewWrapped() {
+ const intl = useIntl();
+ const { address, isConnected } = useAccount();
+ const [{ amountIn, slippage, isRedeemLoading, isEstimateLoading }] =
+ useRedeemState();
+ const { data: prices, isLoading: isPricesLoading } = usePrices();
+ const { data: balOeth, isLoading: isBalOethLoading } = useBalance({
+ address,
+ token: tokens.mainnet.OETH.address,
+ watch: true,
+ scopeKey: 'redeem_balance',
+ });
+ const handleSlippageChange = useHandleSlippageChange();
+ const handleAmountInChange = useHandleAmountInChange();
+ const handleRedeem = useHandleRedeem();
+
+ const amountInInputDisabled = isRedeemLoading;
+
+ const redeemButtonLabel =
+ amountIn === 0n
+ ? intl.formatMessage({ defaultMessage: 'Enter an amount' })
+ : amountIn > balOeth?.value
+ ? intl.formatMessage({ defaultMessage: 'Insufficient funds' })
+ : intl.formatMessage({ defaultMessage: 'Redeem for mix' });
+ const redeemButtonLoading = isEstimateLoading || isRedeemLoading;
+ const redeemButtonDisabled =
+ isBalOethLoading ||
+ redeemButtonLoading ||
+ amountIn > balOeth?.value ||
+ amountIn === 0n;
+
+ return (
+
+
+
+ {intl.formatMessage({ defaultMessage: 'Swap' })}
+
+
+
+ }
+ >
+
+
+ `linear-gradient(${theme.palette.grey[900]}, ${
+ theme.palette.grey[900]
+ }) padding-box,
+ linear-gradient(90deg, ${alpha(
+ theme.palette.primary.main,
+ 0.4,
+ )} 0%, ${alpha(
+ 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,
+ linear-gradient(90deg, var(--mui-palette-primary-main) 0%, var(--mui-palette-primary-dark) 100%) border-box;`,
+ },
+ }}
+ />
+
+
+
+
+
+
+ {redeemButtonLoading ? (
+
+ ) : (
+ redeemButtonLabel
+ )}
+
+
+
+ );
+}
+
+function ArrowButton(props: BoxProps) {
+ return (
+ theme.palette.background.paper,
+ strokeWidth: (theme) => theme.typography.pxToRem(2),
+ stroke: (theme) => theme.palette.grey[700],
+ backgroundColor: (theme) => theme.palette.divider,
+ ...props?.sx,
+ }}
+ >
+
+
+ );
+}
diff --git a/libs/oeth/redeem/tsconfig.json b/libs/oeth/redeem/tsconfig.json
new file mode 100644
index 000000000..90fcf85c5
--- /dev/null
+++ b/libs/oeth/redeem/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowJs": false,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": false
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ }
+ ],
+ "extends": "../../../tsconfig.base.json"
+}
diff --git a/libs/oeth/redeem/tsconfig.lib.json b/libs/oeth/redeem/tsconfig.lib.json
new file mode 100644
index 000000000..d48c074c6
--- /dev/null
+++ b/libs/oeth/redeem/tsconfig.lib.json
@@ -0,0 +1,24 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../dist/out-tsc",
+ "types": [
+ "node",
+ "@nx/react/typings/cssmodule.d.ts",
+ "@nx/react/typings/image.d.ts"
+ ]
+ },
+ "files": ["../../../libs/shared/theme/src/theme.d.ts"],
+ "exclude": [
+ "jest.config.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.js",
+ "src/**/*.test.js",
+ "src/**/*.spec.jsx",
+ "src/**/*.test.jsx"
+ ],
+ "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
+}
diff --git a/libs/oeth/swap/src/components/GasPopover.tsx b/libs/oeth/shared/src/components/GasPopover.tsx
similarity index 84%
rename from libs/oeth/swap/src/components/GasPopover.tsx
rename to libs/oeth/shared/src/components/GasPopover.tsx
index 3f477a105..d9b471025 100644
--- a/libs/oeth/swap/src/components/GasPopover.tsx
+++ b/libs/oeth/shared/src/components/GasPopover.tsx
@@ -14,16 +14,14 @@ import {
Stack,
useTheme,
} from '@mui/material';
-import { produce } from 'immer';
+import { PercentInput } from '@origin/shared/components';
import { useIntl } from 'react-intl';
import { useFeeData } from 'wagmi';
-import { useSwapState } from '../state';
-
import type { IconButtonProps } from '@mui/material';
-import type { ChangeEvent } from 'react';
-const defaultSlippage = 0.01;
+const DEFAULT_SLIPPAGE = 0.01;
+const WARNING_THRESHOLD = 0.05;
const gridStyles = {
display: 'grid',
@@ -33,19 +31,22 @@ const gridStyles = {
alignItems: 'center',
};
-interface Props {
+export type GasPopoverProps = {
buttonProps?: IconButtonProps;
-}
+ slippage: number;
+ onSlippageChange: (value: number) => void;
+};
-export function GasPopover({ buttonProps }: Props) {
+export function GasPopover({
+ buttonProps,
+ slippage,
+ onSlippageChange,
+}: GasPopoverProps) {
const theme = useTheme();
const intl = useIntl();
const [anchorEl, setAnchorEl] = useState(null);
- const [{ slippage }, setSwapState] = useSwapState();
const { data: feeData } = useFeeData({ formatUnits: 'gwei' });
- const handleSlippageChange = (evt: ChangeEvent) => {};
-
return (
<>
- theme.palette.secondary.main,
@@ -114,15 +115,6 @@ export function GasPopover({ buttonProps }: Props) {
},
},
}}
- onChange={handleSlippageChange}
- endAdornment={
-
- {intl.formatMessage({ defaultMessage: '%' })}
-
- }
/>
- {slippage > 1 ? (
+ {slippage > WARNING_THRESHOLD ? (
= {
'swap-zapper-eth': { ...defaultApi, ...swapZapperEth },
'swap-zapper-sfrxeth': { ...defaultApi, ...swapZapperSfrxeth },
'mint-vault': { ...defaultApi, ...mintVault },
- 'redeem-vault': { ...defaultApi, ...redeemVault },
'wrap-oeth': { ...defaultApi, ...wrapOETH },
'unwrap-woeth': { ...defaultApi, ...unwrapWOETH },
};
diff --git a/libs/oeth/swap/src/actions/redeemVault.ts b/libs/oeth/swap/src/actions/redeemVault.ts
deleted file mode 100644
index 25df7d185..000000000
--- a/libs/oeth/swap/src/actions/redeemVault.ts
+++ /dev/null
@@ -1,228 +0,0 @@
-import { queryClient } from '@origin/oeth/shared';
-import { contracts } from '@origin/shared/contracts';
-import { isNilOrEmpty } from '@origin/shared/utils';
-import {
- erc20ABI,
- getAccount,
- getPublicClient,
- prepareWriteContract,
- readContract,
- readContracts,
- waitForTransaction,
- writeContract,
-} from '@wagmi/core';
-import { formatUnits, maxUint256, parseUnits } from 'viem';
-
-import { MIX_TOKEN } from '../constants';
-
-import type {
- Allowance,
- Approve,
- EstimateAmount,
- EstimateApprovalGas,
- EstimateGas,
- EstimateRoute,
- Swap,
-} from '../types';
-
-const estimateAmount: EstimateAmount = async ({ amountIn }) => {
- if (amountIn === 0n) {
- return 0n;
- }
-
- const assetsDecimals = await queryClient.fetchQuery({
- queryKey: ['assetsDecimals'],
- queryFn: async () => {
- const assets = await readContract({
- address: contracts.mainnet.OETHVaultCore.address,
- abi: contracts.mainnet.OETHVaultCore.abi,
- functionName: 'getAllAssets',
- });
-
- const decimals = await readContracts({
- contracts: assets.map((address) => ({
- address,
- abi: erc20ABI,
- functionName: 'decimals',
- })),
- });
-
- return decimals.map((r) => r.result);
- },
- staleTime: Infinity,
- });
-
- const split = await readContract({
- address: contracts.mainnet.OETHVaultCore.address,
- abi: contracts.mainnet.OETHVaultCore.abi,
- functionName: 'calculateRedeemOutputs',
- args: [amountIn],
- });
-
- return split.reduce((acc, curr, i) => {
- if (assetsDecimals[i] !== MIX_TOKEN.decimals) {
- const exp = MIX_TOKEN.decimals - assetsDecimals[i];
-
- return acc + curr * (10n ^ BigInt(exp));
- }
-
- return acc + curr;
- }, 0n);
-};
-
-const estimateGas: EstimateGas = async ({
- tokenOut,
- amountIn,
- slippage,
- amountOut,
-}) => {
- let gasEstimate = 0n;
-
- if (amountIn === 0n) {
- return gasEstimate;
- }
-
- const publicClient = getPublicClient();
- const { address } = getAccount();
-
- const minAmountOut = parseUnits(
- (
- +formatUnits(amountOut, tokenOut.decimals) -
- +formatUnits(amountOut, tokenOut.decimals) * slippage
- ).toString(),
- tokenOut.decimals,
- );
-
- try {
- gasEstimate = await publicClient.estimateContractGas({
- address: contracts.mainnet.OETHVaultCore.address,
- abi: contracts.mainnet.OETHVaultCore.abi,
- functionName: 'redeem',
- args: [amountIn, minAmountOut],
- account: address,
- });
- } catch {}
-
- return gasEstimate;
-};
-
-const allowance: Allowance = async () => {
- // Redeem OETH does not require approval
- return maxUint256;
-};
-
-const estimateApprovalGas: EstimateApprovalGas = async () => {
- // Redeem OETH does not require approval
- return 0n;
-};
-
-const estimateRoute: EstimateRoute = async ({
- tokenIn,
- tokenOut,
- amountIn,
- route,
- slippage,
-}) => {
- if (amountIn === 0n) {
- return {
- ...route,
- estimatedAmount: 0n,
- gas: 0n,
- rate: 0,
- approvedAmount: 0n,
- approvalGas: 0n,
- };
- }
-
- const [estimatedAmount, approvedAmount, approvalGas] = await Promise.all([
- estimateAmount({
- tokenIn,
- tokenOut,
- amountIn,
- }),
- allowance({ tokenIn, tokenOut }),
- estimateApprovalGas({ amountIn, tokenIn, tokenOut }),
- ]);
- const gas = await estimateGas({
- tokenIn,
- tokenOut,
- amountIn,
- slippage,
- amountOut: estimatedAmount,
- });
-
- return {
- ...route,
- estimatedAmount,
- gas,
- approvalGas,
- approvedAmount,
- rate:
- +formatUnits(amountIn, tokenIn.decimals) /
- +formatUnits(estimatedAmount, tokenOut.decimals),
- };
-};
-
-const approve: Approve = async ({ onSuccess }) => {
- // Redeem OETH does not require approval
- if (onSuccess) {
- await onSuccess(null);
- }
-};
-
-const swap: Swap = async ({
- tokenOut,
- amountIn,
- slippage,
- amountOut,
- onSuccess,
- onError,
- onReject,
-}) => {
- const { address } = getAccount();
-
- if (amountIn === 0n || isNilOrEmpty(address)) {
- return;
- }
-
- const minAmountOut = parseUnits(
- (
- +formatUnits(amountOut, tokenOut.decimals) -
- +formatUnits(amountOut, tokenOut.decimals) * slippage
- ).toString(),
- tokenOut.decimals,
- );
-
- try {
- const { request } = await prepareWriteContract({
- address: contracts.mainnet.OETHVaultCore.address,
- abi: contracts.mainnet.OETHVaultCore.abi,
- functionName: 'redeem',
- args: [amountIn, minAmountOut],
- });
- const { hash } = await writeContract(request);
- const txReceipt = await waitForTransaction({ hash });
-
- console.log('redeem vault done!');
- if (onSuccess) {
- await onSuccess(txReceipt);
- }
- } catch (e) {
- console.error(`redeem vault error!\n${e.message}`);
- if (e?.code === 'ACTION_REJECTED' && onReject) {
- await onReject('Redeem OETH');
- } else if (onError) {
- await onError('Redeem OETH');
- }
- }
-};
-
-export default {
- estimateAmount,
- estimateGas,
- estimateRoute,
- allowance,
- estimateApprovalGas,
- approve,
- swap,
-};
diff --git a/libs/oeth/swap/src/constants.ts b/libs/oeth/swap/src/constants.ts
index 2efdab270..8d7101d4f 100644
--- a/libs/oeth/swap/src/constants.ts
+++ b/libs/oeth/swap/src/constants.ts
@@ -1,25 +1,12 @@
import { tokens } from '@origin/shared/contracts';
import { defineMessage } from 'react-intl';
-import { erc20ABI, mainnet } from 'wagmi';
-import type { Token } from '@origin/shared/contracts';
import type { MessageDescriptor } from 'react-intl';
import type { SwapAction } from './types';
-export const MIX_TOKEN: Token = {
- address: undefined,
- chainId: mainnet.id,
- abi: erc20ABI,
- decimals: 18,
- name: 'Redeem Mix',
- symbol: 'MIX_TOKEN',
- icon: '/images/backed-graphic.svg',
-};
-
export const routeActionLogos: Record = {
'mint-vault': '/images/protocols/origin.svg',
- 'redeem-vault': '/images/protocols/origin.svg',
'swap-curve': '/images/protocols/curve.webp',
'swap-curve-eth': '/images/protocols/curve.webp',
'swap-zapper-eth': '/images/protocols/zapper.svg',
@@ -30,7 +17,6 @@ export const routeActionLogos: Record = {
export const routeActionLabel: Record = {
'mint-vault': defineMessage({ defaultMessage: 'Mint with Vault' }),
- 'redeem-vault': defineMessage({ defaultMessage: 'Redeem with Vault' }),
'swap-curve': defineMessage({ defaultMessage: 'Swap with Curve' }),
'swap-curve-eth': defineMessage({ defaultMessage: 'Swap with CurvePool' }),
'swap-zapper-eth': defineMessage({ defaultMessage: 'Swap with Zapper' }),
@@ -102,11 +88,6 @@ export const swapRoutes = [
action: 'swap-zapper-sfrxeth',
},
// Redeem
- // {
- // tokenIn: tokens.mainnet.OETH,
- // tokenOut: MIX_TOKEN,
- // action: 'redeem-vault',
- // },
{
tokenIn: tokens.mainnet.OETH,
tokenOut: tokens.mainnet.WETH,
diff --git a/libs/oeth/swap/src/hooks.ts b/libs/oeth/swap/src/hooks.ts
index 62cbcd889..31ffd7b92 100644
--- a/libs/oeth/swap/src/hooks.ts
+++ b/libs/oeth/swap/src/hooks.ts
@@ -5,6 +5,7 @@ import { isNilOrEmpty } from '@origin/shared/utils';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { produce } from 'immer';
import { useIntl } from 'react-intl';
+import { useAccount, useQueryClient as useWagmiClient } from 'wagmi';
import { swapActions } from './actions';
import { useSwapState } from './state';
@@ -150,7 +151,7 @@ export const useSelectedSwapRouteAllowance = () => {
return useQuery({
queryKey: [
- 'allowance',
+ 'swap_allowance',
selectedSwapRoute?.tokenIn.symbol,
selectedSwapRoute?.tokenOut.symbol,
selectedSwapRoute?.action,
@@ -166,16 +167,33 @@ export const useSelectedSwapRouteAllowance = () => {
});
};
+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();
const curve = useCurve();
const queryClient = useQueryClient();
+ const wagmiClient = useWagmiClient();
const pushNotification = usePushNotification();
const [{ amountIn, selectedSwapRoute, tokenIn, tokenOut }, setSwapState] =
useSwapState();
return useCallback(async () => {
- if (isNilOrEmpty(selectedSwapRoute)) {
+ if (isNilOrEmpty(selectedSwapRoute) || isNilOrEmpty(address)) {
return;
}
@@ -190,13 +208,11 @@ export const useHandleApprove = () => {
amountIn,
curve,
onSuccess: () => {
+ wagmiClient.invalidateQueries({
+ queryKey: ['swap_balance'],
+ });
queryClient.invalidateQueries({
- queryKey: [
- 'allowance',
- selectedSwapRoute?.tokenIn.symbol,
- selectedSwapRoute?.tokenOut.symbol,
- selectedSwapRoute?.action,
- ],
+ queryKey: ['swap_allowance'],
});
pushNotification({
title: intl.formatMessage({ defaultMessage: 'Approval complete' }),
@@ -232,6 +248,7 @@ export const useHandleApprove = () => {
},
});
}, [
+ address,
amountIn,
curve,
intl,
@@ -241,13 +258,16 @@ export const useHandleApprove = () => {
setSwapState,
tokenIn,
tokenOut,
+ wagmiClient,
]);
};
export const useHandleSwap = () => {
const intl = useIntl();
+ const { address } = useAccount();
const curve = useCurve();
const queryClient = useQueryClient();
+ const wagmiClient = useWagmiClient();
const pushNotification = usePushNotification();
const [
{ amountIn, amountOut, selectedSwapRoute, slippage, tokenIn, tokenOut },
@@ -255,7 +275,7 @@ export const useHandleSwap = () => {
] = useSwapState();
return useCallback(async () => {
- if (isNilOrEmpty(selectedSwapRoute)) {
+ if (isNilOrEmpty(selectedSwapRoute) || isNilOrEmpty(address)) {
return;
}
@@ -273,13 +293,11 @@ export const useHandleSwap = () => {
amountOut,
curve,
onSuccess: () => {
+ wagmiClient.invalidateQueries({
+ queryKey: ['swap_balance'],
+ });
queryClient.invalidateQueries({
- queryKey: [
- 'allowance',
- selectedSwapRoute?.tokenIn.symbol,
- selectedSwapRoute?.tokenOut.symbol,
- selectedSwapRoute?.action,
- ],
+ queryKey: ['swap_allowance'],
});
pushNotification({
title: intl.formatMessage({ defaultMessage: 'Swap complete' }),
@@ -315,6 +333,7 @@ export const useHandleSwap = () => {
},
});
}, [
+ address,
amountIn,
amountOut,
curve,
@@ -326,5 +345,6 @@ export const useHandleSwap = () => {
slippage,
tokenIn,
tokenOut,
+ wagmiClient,
]);
};
diff --git a/libs/oeth/swap/src/state.ts b/libs/oeth/swap/src/state.ts
index 54b5d3960..9b9e46a3c 100644
--- a/libs/oeth/swap/src/state.ts
+++ b/libs/oeth/swap/src/state.ts
@@ -1,9 +1,9 @@
import { useState } from 'react';
-import { queryClient } from '@origin/oeth/shared';
import { tokens } from '@origin/shared/contracts';
import { useCurve } from '@origin/shared/providers';
import { useDebouncedEffect } from '@react-hookz/web';
+import { useQueryClient } from '@tanstack/react-query';
import { produce } from 'immer';
import { createContainer } from 'react-tracked';
@@ -27,6 +27,7 @@ export const { Provider: SwapProvider, useTracked: useSwapState } =
isApprovalLoading: false,
isSwapLoading: false,
});
+ const queryClient = useQueryClient();
const { CurveRegistryExchange, OethPoolUnderlyings } = useCurve();
useDebouncedEffect(
diff --git a/libs/oeth/swap/src/types.ts b/libs/oeth/swap/src/types.ts
index 2c479fb78..268eddc33 100644
--- a/libs/oeth/swap/src/types.ts
+++ b/libs/oeth/swap/src/types.ts
@@ -10,7 +10,6 @@ export type SwapAction =
| 'swap-zapper-eth'
| 'swap-zapper-sfrxeth'
| 'mint-vault'
- | 'redeem-vault'
| 'wrap-oeth'
| 'unwrap-woeth';
diff --git a/libs/oeth/swap/src/views/SwapView.tsx b/libs/oeth/swap/src/views/SwapView.tsx
index c3dc173fe..ef1ffd689 100644
--- a/libs/oeth/swap/src/views/SwapView.tsx
+++ b/libs/oeth/swap/src/views/SwapView.tsx
@@ -8,21 +8,22 @@ import {
Collapse,
IconButton,
Stack,
+ Typography,
} from '@mui/material';
-import { ApyHeader } from '@origin/oeth/shared';
+import { ApyHeader, GasPopover } from '@origin/oeth/shared';
import { Card, TokenInput } from '@origin/shared/components';
import { ConnectedButton, usePrices } from '@origin/shared/providers';
import { isNilOrEmpty } from '@origin/shared/utils';
import { useIntl } from 'react-intl';
import { useAccount, useBalance } from 'wagmi';
-import { GasPopover } from '../components/GasPopover';
import { SwapRoute } from '../components/SwapRoute';
import { TokenSelectModal } from '../components/TokenSelectModal';
import { routeActionLabel } from '../constants';
import {
useHandleAmountInChange,
useHandleApprove,
+ useHandleSlippageChange,
useHandleSwap,
useHandleTokenChange,
useHandleTokenFlip,
@@ -45,6 +46,31 @@ const commonStyles = {
borderRadius: 1,
};
+const tokenInputStyles = {
+ border: 'none',
+ backgroundColor: 'transparent',
+ borderRadius: 0,
+ paddingBlock: 0,
+ paddingInline: 0,
+ borderImageWidth: 0,
+ boxSizing: 'border-box',
+ '& .MuiInputBase-input': {
+ padding: 0,
+ lineHeight: '1.875rem',
+ boxSizing: 'border-box',
+ fontStyle: 'normal',
+ fontFamily: 'Sailec, Inter, Helvetica, Arial, sans-serif',
+ fontSize: '1.5rem',
+ fontWeight: 700,
+ height: '1.5rem',
+ color: 'primary.contrastText',
+ '&::placeholder': {
+ color: 'text.secondary',
+ opacity: 1,
+ },
+ },
+};
+
export const SwapView = () => (
@@ -62,6 +88,7 @@ function SwapViewWrapped() {
tokenIn,
tokenOut,
selectedSwapRoute,
+ slippage,
isSwapLoading,
isSwapRoutesLoading,
isApprovalLoading,
@@ -74,12 +101,15 @@ function SwapViewWrapped() {
address,
token: tokenIn.address,
watch: true,
+ scopeKey: 'swap_balance',
});
const { data: balTokenOut, isLoading: isBalTokenOutLoading } = useBalance({
address,
token: tokenOut.address,
watch: true,
+ scopeKey: 'swap_balance',
});
+ const handleSlippageChange = useHandleSlippageChange();
const handleAmountInChange = useHandleAmountInChange();
const handleTokenChange = useHandleTokenChange();
const handleTokenFlip = useHandleTokenFlip();
@@ -102,7 +132,6 @@ function SwapViewWrapped() {
!isNilOrEmpty(selectedSwapRoute) &&
selectedSwapRoute?.approvedAmount < amountIn &&
allowance < amountIn;
-
const swapButtonLabel =
amountIn === 0n
? intl.formatMessage({ defaultMessage: 'Enter an amount' })
@@ -111,25 +140,21 @@ function SwapViewWrapped() {
: !isNilOrEmpty(selectedSwapRoute)
? intl.formatMessage(routeActionLabel[selectedSwapRoute?.action])
: '';
-
+ const approveButtonLoading = isSwapRoutesLoading || isApprovalLoading;
+ const swapButtonLoading = isSwapRoutesLoading || isSwapLoading;
const amountInInputDisabled = isSwapLoading || isApprovalLoading;
-
const approveButtonDisabled =
isNilOrEmpty(selectedSwapRoute) ||
- isApprovalLoading ||
+ approveButtonLoading ||
amountIn > balTokenIn?.value;
-
const swapButtonDisabled =
needsApproval ||
isNilOrEmpty(selectedSwapRoute) ||
isBalTokenInLoading ||
+ swapButtonLoading ||
amountIn > balTokenIn?.value ||
amountIn === 0n;
- const approveButtonLoading = isSwapRoutesLoading || isApprovalLoading;
-
- const swapButtonLoading = isSwapRoutesLoading || isSwapLoading;
-
return (
<>
@@ -147,8 +172,12 @@ function SwapViewWrapped() {
justifyContent="space-between"
alignItems="center"
>
- {intl.formatMessage({ defaultMessage: 'Swap' })}
+
+ {intl.formatMessage({ defaultMessage: 'Swap' })}
+
{
setTokenSource('tokenOut');
}}
tokenPriceUsd={prices?.[tokenOut.symbol]}
isPriceLoading={isSwapRoutesLoading || isPriceLoading}
- inputProps={{ readOnly: true }}
isConnected={isConnected}
+ inputProps={{ readOnly: true, sx: tokenInputStyles }}
sx={{
...commonStyles,
borderStartStartRadius: 0,
@@ -229,7 +257,7 @@ function SwapViewWrapped() {
backgroundColor: (theme) => alpha(theme.palette.grey[400], 0.2),
}}
/>
-
+
@@ -269,7 +297,7 @@ function SwapViewWrapped() {
);
}
-function SwapButton(props: IconButtonProps) {
+function ArrowButton(props: IconButtonProps) {
return (
diff --git a/libs/oeth/swap/tsconfig.lib.json b/libs/oeth/swap/tsconfig.lib.json
index d11ac2d7a..78f9e0f1c 100644
--- a/libs/oeth/swap/tsconfig.lib.json
+++ b/libs/oeth/swap/tsconfig.lib.json
@@ -20,11 +20,5 @@
"src/**/*.spec.jsx",
"src/**/*.test.jsx"
],
- "include": [
- "src/**/*.js",
- "src/**/*.jsx",
- "src/**/*.ts",
- "src/**/*.tsx",
- "../../shared/providers/src/wagmi/components/TokenSelectModal.tsx"
- ]
+ "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}
diff --git a/libs/shared/components/src/Cards/Card.tsx b/libs/shared/components/src/Cards/Card.tsx
index 0d7ad9dff..f0e1d308e 100644
--- a/libs/shared/components/src/Cards/Card.tsx
+++ b/libs/shared/components/src/Cards/Card.tsx
@@ -8,13 +8,13 @@ export const cardStyles = {
paddingInline: 2,
} as const;
-interface Props {
+export type CardProps = {
title: string | React.ReactNode;
children: React.ReactNode;
sxCardContent?: SxProps;
sxCardTitle?: SxProps;
sx?: SxProps;
-}
+};
export function Card({
title,
@@ -22,7 +22,7 @@ export function Card({
sxCardContent,
sxCardTitle,
sx,
-}: Props) {
+}: CardProps) {
return (
(
) : (
);
diff --git a/libs/shared/components/src/Inputs/PercentInput.tsx b/libs/shared/components/src/Inputs/PercentInput.tsx
new file mode 100644
index 000000000..5d34de566
--- /dev/null
+++ b/libs/shared/components/src/Inputs/PercentInput.tsx
@@ -0,0 +1,85 @@
+import { forwardRef, useEffect, useState } from 'react';
+
+import { InputBase } from '@mui/material';
+import { isNilOrEmpty } from '@origin/shared/utils';
+
+import type { InputBaseProps } from '@mui/material';
+import type { ChangeEvent } from 'react';
+
+export type PercentInputProps = {
+ value: number;
+ precision?: number;
+ onChange?: (value: number) => void;
+} & Omit;
+
+export const PercentInput = forwardRef(
+ ({ value, precision = 4, onChange, ...rest }, ref) => {
+ const [strVal, setStrVal] = useState(formatPercent(value, precision));
+
+ useEffect(() => {
+ if (value === 0 && (isNilOrEmpty(strVal) || strVal.endsWith('.'))) {
+ return;
+ }
+
+ if (value === 0 && !/0\.0+$/.test(strVal)) {
+ setStrVal('');
+ return;
+ }
+
+ if (
+ isNilOrEmpty(strVal) ||
+ formatPercent(value, precision) !== strVal.replace('.', '')
+ ) {
+ setStrVal(formatPercent(value, precision));
+ }
+ }, [precision, strVal, value]);
+
+ const handleChange = (evt: ChangeEvent) => {
+ if (evt.target.validity.valid) {
+ const val =
+ isNilOrEmpty(evt.target.value) || evt.target.value === '.'
+ ? '0'
+ : evt.target.value.replace(/\.0+$/, '');
+
+ try {
+ const num = Number(val) / 100;
+ if (num <= 1) {
+ setStrVal(evt.target.value === '.' ? '0.' : evt.target.value);
+ if (onChange && num !== value) {
+ onChange(num);
+ }
+ }
+ } catch {}
+ }
+ };
+
+ return (
+
+ );
+ },
+);
+
+PercentInput.displayName = 'PercentInput';
+
+function formatPercent(value: number, precision: number) {
+ return Intl.NumberFormat('en', {
+ useGrouping: false,
+ maximumFractionDigits: precision,
+ }).format(value * 100);
+}
diff --git a/libs/shared/components/src/Inputs/TokenInput.tsx b/libs/shared/components/src/Inputs/TokenInput.tsx
index 671c14aaf..87b6a7372 100644
--- a/libs/shared/components/src/Inputs/TokenInput.tsx
+++ b/libs/shared/components/src/Inputs/TokenInput.tsx
@@ -1,11 +1,21 @@
import { forwardRef } from 'react';
-import { alpha, Box, IconButton, Stack, Typography } from '@mui/material';
-import { formatAmount } from '@origin/shared/utils';
+import {
+ alpha,
+ Box,
+ IconButton,
+ Skeleton,
+ Stack,
+ Typography,
+} from '@mui/material';
+import {
+ currencyFormat,
+ formatAmount,
+ isNilOrEmpty,
+} from '@origin/shared/utils';
import { useIntl } from 'react-intl';
import { formatUnits } from 'viem';
-import { Loader } from '../Loader';
import { BigIntInput } from './BigIntInput';
import type { StackProps } from '@mui/material';
@@ -13,8 +23,6 @@ import type { Token } from '@origin/shared/contracts';
import type { BigintInputProps } from './BigIntInput';
-const styles = { display: 'flex', justifyContent: 'space-between', gap: 2.5 };
-
export type TokenInputProps = {
amount: bigint;
decimals?: number;
@@ -25,9 +33,9 @@ export type TokenInputProps = {
isConnected: boolean;
balance?: bigint;
isBalanceLoading?: boolean;
- disableMaxClick?: boolean;
token: Token;
- onTokenClick: () => void;
+ onTokenClick?: () => void;
+ isTokenClickDisabled?: boolean;
tokenPriceUsd?: number;
isPriceLoading?: boolean;
inputProps?: Omit<
@@ -48,9 +56,9 @@ export const TokenInput = forwardRef(
isConnected,
balance = 0n,
isBalanceLoading,
- disableMaxClick,
token,
onTokenClick,
+ isTokenClickDisabled,
tokenPriceUsd = 0,
isPriceLoading,
inputProps,
@@ -64,7 +72,9 @@ export const TokenInput = forwardRef(
return (
-
+
(
disabled={isAmountDisabled}
isLoading={isAmountLoading}
ref={ref}
- sx={{ flex: 1 }}
/>
-
-
-
+
-
+
{isPriceLoading ? (
-
- ) : tokenPriceUsd !== undefined ? (
+
+ ) : !isNilOrEmpty(tokenPriceUsd) ? (
(
lineHeight: '1.5rem',
}}
>
- {intl.formatNumber(amountUsd, {
- style: 'currency',
- currency: 'usd',
- maximumFractionDigits: 4,
- })}
+ {intl.formatNumber(amountUsd, currencyFormat)}
) : null}
{isConnected ? (
isBalanceLoading ? (
-
+
) : (
-
-
- }
+ onClose={handleCloseClick}
>
{!isNilOrEmpty(title) && (
{title}
diff --git a/libs/shared/providers/src/wagmi/components/ChainScanLink.tsx b/libs/shared/providers/src/wagmi/components/BlockExplorerLink.tsx
similarity index 85%
rename from libs/shared/providers/src/wagmi/components/ChainScanLink.tsx
rename to libs/shared/providers/src/wagmi/components/BlockExplorerLink.tsx
index 783a6c3c5..1d8977f02 100644
--- a/libs/shared/providers/src/wagmi/components/ChainScanLink.tsx
+++ b/libs/shared/providers/src/wagmi/components/BlockExplorerLink.tsx
@@ -5,7 +5,7 @@ import { mainnet } from 'wagmi/chains';
import type { LinkProps } from '@mui/material';
-export type ChainScanLinkProps = {
+export type BlockExplorerLinkProps = {
hash?: string;
blockExplorer?: {
name: string;
@@ -13,15 +13,15 @@ export type ChainScanLinkProps = {
};
} & Omit;
-export const ChainScanLink = ({
+export const BlockExplorerLink = ({
hash,
blockExplorer,
...rest
-}: ChainScanLinkProps) => {
+}: BlockExplorerLinkProps) => {
const intl = useIntl();
const { chain, chains } = useNetwork();
- const base =
+ const baseUrl =
blockExplorer?.url ??
chain?.blockExplorers?.default?.url ??
chains[0].blockExplorers.default.url ??
@@ -35,7 +35,7 @@ export const ChainScanLink = ({
return (
diff --git a/libs/shared/providers/src/wagmi/components/index.ts b/libs/shared/providers/src/wagmi/components/index.ts
index 04b724024..fbdc3012b 100644
--- a/libs/shared/providers/src/wagmi/components/index.ts
+++ b/libs/shared/providers/src/wagmi/components/index.ts
@@ -1,4 +1,4 @@
export * from './AddressLabel';
-export * from './ChainScanLink';
+export * from './BlockExplorerLink';
export * from './ConnectedButton';
export * from './OpenAccountModalButton';
diff --git a/libs/shared/theme/src/theme.tsx b/libs/shared/theme/src/theme.tsx
index 469265639..081fa1b90 100644
--- a/libs/shared/theme/src/theme.tsx
+++ b/libs/shared/theme/src/theme.tsx
@@ -1,3 +1,4 @@
+import { Box } from '@mui/material';
import {
alpha,
experimental_extendTheme as extendTheme,
@@ -89,6 +90,32 @@ export const theme = extendTheme({
'0px 1.7955275774002075px 5.32008171081543px 0px rgba(0, 0, 0, 0.03), 0px 6.030803203582764px 17.869047164916992px 0px rgba(0, 0, 0, 0.04), 0px 27px 80px 0px rgba(0, 0, 0, 0.07)',
],
components: {
+ MuiAlert: {
+ defaultProps: {
+ variant: 'standard',
+ iconMapping: {
+ error: (
+
+ ),
+ info: (
+
+ ),
+ success: (
+
+ ),
+ warning: (
+
+ ),
+ },
+ },
+ styleOverrides: {
+ root: ({ theme }) => ({
+ backgroundColor: theme.palette.grey['900'],
+ color: theme.palette.primary.contrastText,
+ '&&&': { border: 'none' },
+ }),
+ },
+ },
MuiButton: {
styleOverrides: {
root: {
@@ -317,9 +344,13 @@ export const theme = extendTheme({
},
},
MuiSkeleton: {
+ defaultProps: {
+ animation: 'wave',
+ },
styleOverrides: {
text: ({ theme }) => ({
borderRadius: theme.shape.borderRadius * 22,
+ backgroundColor: 'grey.900',
}),
},
},
diff --git a/libs/shared/utils/src/BigDecimal.ts b/libs/shared/utils/src/BigDecimal.ts
deleted file mode 100644
index 4d6ef7d19..000000000
--- a/libs/shared/utils/src/BigDecimal.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { formatUnits, parseUnits } from 'viem';
-
-const DEFAULT_DECIMALS = 18;
-
-export class BigDecimal {
- value: bigint;
- decimals: number;
-
- constructor(num: bigint, decimals = DEFAULT_DECIMALS) {
- this.value = num ? (typeof num === 'bigint' ? num : BigInt(num)) : 0n;
- this.decimals = decimals;
- }
-
- static ZERO(): BigDecimal {
- return new BigDecimal(0n, DEFAULT_DECIMALS);
- }
-
- static ONE(decimals = DEFAULT_DECIMALS): BigDecimal {
- return new BigDecimal(parseUnits('1', decimals), decimals);
- }
-
- static parse(
- amountStr: `${number}`,
- decimals = DEFAULT_DECIMALS,
- ): BigDecimal {
- return new BigDecimal(parseUnits(amountStr, decimals), decimals);
- }
-
- static fromSimple(
- amountNum: number,
- decimals = DEFAULT_DECIMALS,
- ): BigDecimal {
- return new BigDecimal(BigInt(amountNum), decimals);
- }
-
- get string(): string {
- return formatUnits(this.value, this.decimals);
- }
-
- get simple(): number {
- return parseFloat(this.string);
- }
-
- get simpleRounded(): number {
- return parseFloat(this.simple.toFixed(3).slice(0, -1));
- }
-
- toJSON(): string {
- return JSON.stringify({
- decimals: this.decimals,
- value: this.value.toString(),
- });
- }
-
- toFixed(decimalPlaces = 2): number {
- return parseFloat(this.simple.toFixed(decimalPlaces + 1).slice(0, -1));
- }
-
- toPercent(decimalPlaces = 2): number {
- return parseFloat((this.simple * 100).toFixed(decimalPlaces));
- }
-
- format(decimalPlaces = 2): string {
- return Intl.NumberFormat('en', {
- maximumFractionDigits: decimalPlaces,
- }).format(this.simple);
- }
-
- add(other: BigDecimal) {
- this.value += other.value;
- }
-
- sub(other: BigDecimal) {
- this.value -= other.value;
- }
-
- mul(other: BigDecimal) {
- this.value *= other.value;
- }
-
- div(other: BigDecimal) {
- this.value /= other.value;
- }
-
- eq(other: BigDecimal): boolean {
- return this.value === other.value;
- }
-
- gt(other: BigDecimal): boolean {
- return this.value > other.value;
- }
-
- gte(other: BigDecimal): boolean {
- return this.value >= other.value;
- }
-
- lt(other: BigDecimal): boolean {
- return this.value < other.value;
- }
-
- lte(other: BigDecimal): boolean {
- return this.value <= other.value;
- }
-
- isZero(): boolean {
- return this.value === 0n;
- }
-}
diff --git a/libs/shared/utils/src/BigInt.ts b/libs/shared/utils/src/BigInt.ts
new file mode 100644
index 000000000..d96a687fb
--- /dev/null
+++ b/libs/shared/utils/src/BigInt.ts
@@ -0,0 +1,2 @@
+export const jsonStringifyReplacer = (key, value) =>
+ typeof value === 'bigint' ? value.toString() : value;
diff --git a/libs/shared/utils/src/index.ts b/libs/shared/utils/src/index.ts
index 6335db81d..aef480533 100644
--- a/libs/shared/utils/src/index.ts
+++ b/libs/shared/utils/src/index.ts
@@ -1,5 +1,5 @@
export * from './addresses';
-export * from './BigDecimal';
+export * from './BigInt';
export * from './composeContext';
export * from './formatters';
export * from './isNilOrEmpty';
diff --git a/package.json b/package.json
index 6f7252cb0..da31588c3 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"wagmi": "^1.4.1"
},
"devDependencies": {
+ "@babel/core": "^7.14.5",
"@babel/preset-react": "^7.22.15",
"@faker-js/faker": "^8.0.2",
"@formatjs/cli": "^6.1.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 28f6e9b9b..c994ca695 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -73,6 +73,9 @@ dependencies:
version: 1.4.1(@types/react@18.2.21)(immer@10.0.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.10.12)
devDependencies:
+ '@babel/core':
+ specifier: ^7.14.5
+ version: 7.22.17
'@babel/preset-react':
specifier: ^7.22.15
version: 7.22.15(@babel/core@7.22.17)
@@ -338,8 +341,9 @@ packages:
resolution: {integrity: sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/highlight': 7.22.10
+ '@babel/highlight': 7.22.13
chalk: 2.4.2
+ dev: true
/@babel/code-frame@7.22.13:
resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==}
@@ -347,36 +351,12 @@ packages:
dependencies:
'@babel/highlight': 7.22.13
chalk: 2.4.2
- dev: true
/@babel/compat-data@7.22.9:
resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==}
engines: {node: '>=6.9.0'}
dev: true
- /@babel/core@7.22.10:
- resolution: {integrity: sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==}
- engines: {node: '>=6.9.0'}
- dependencies:
- '@ampproject/remapping': 2.2.1
- '@babel/code-frame': 7.22.10
- '@babel/generator': 7.22.10
- '@babel/helper-compilation-targets': 7.22.10
- '@babel/helper-module-transforms': 7.22.9(@babel/core@7.22.10)
- '@babel/helpers': 7.22.10
- '@babel/parser': 7.22.10
- '@babel/template': 7.22.5
- '@babel/traverse': 7.22.10
- '@babel/types': 7.22.10
- convert-source-map: 1.9.0
- debug: 4.3.4
- gensync: 1.0.0-beta.2
- json5: 2.2.3
- semver: 6.3.1
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@babel/core@7.22.17:
resolution: {integrity: sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ==}
engines: {node: '>=6.9.0'}
@@ -434,17 +414,6 @@ packages:
'@babel/types': 7.22.17
dev: true
- /@babel/helper-compilation-targets@7.22.10:
- resolution: {integrity: sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==}
- engines: {node: '>=6.9.0'}
- dependencies:
- '@babel/compat-data': 7.22.9
- '@babel/helper-validator-option': 7.22.15
- browserslist: 4.21.10
- lru-cache: 5.1.1
- semver: 6.3.1
- dev: true
-
/@babel/helper-compilation-targets@7.22.15:
resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==}
engines: {node: '>=6.9.0'}
@@ -510,15 +479,15 @@ packages:
resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/template': 7.22.5
- '@babel/types': 7.22.10
+ '@babel/template': 7.22.15
+ '@babel/types': 7.22.17
dev: true
/@babel/helper-hoist-variables@7.22.5:
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.22.10
+ '@babel/types': 7.22.17
dev: true
/@babel/helper-member-expression-to-functions@7.22.15:
@@ -533,13 +502,6 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.22.17
- dev: true
-
- /@babel/helper-module-imports@7.22.5:
- resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==}
- engines: {node: '>=6.9.0'}
- dependencies:
- '@babel/types': 7.22.10
/@babel/helper-module-transforms@7.22.17(@babel/core@7.22.17):
resolution: {integrity: sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ==}
@@ -555,20 +517,6 @@ packages:
'@babel/helper-validator-identifier': 7.22.15
dev: true
- /@babel/helper-module-transforms@7.22.9(@babel/core@7.22.10):
- resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0
- dependencies:
- '@babel/core': 7.22.10
- '@babel/helper-environment-visitor': 7.22.5
- '@babel/helper-module-imports': 7.22.5
- '@babel/helper-simple-access': 7.22.5
- '@babel/helper-split-export-declaration': 7.22.6
- '@babel/helper-validator-identifier': 7.22.5
- dev: true
-
/@babel/helper-optimise-call-expression@7.22.5:
resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==}
engines: {node: '>=6.9.0'}
@@ -609,7 +557,7 @@ packages:
resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.22.10
+ '@babel/types': 7.22.17
dev: true
/@babel/helper-skip-transparent-expression-wrappers@7.22.5:
@@ -623,7 +571,7 @@ packages:
resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.22.10
+ '@babel/types': 7.22.17
dev: true
/@babel/helper-string-parser@7.22.5:
@@ -633,11 +581,11 @@ packages:
/@babel/helper-validator-identifier@7.22.15:
resolution: {integrity: sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==}
engines: {node: '>=6.9.0'}
- dev: true
/@babel/helper-validator-identifier@7.22.5:
resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==}
engines: {node: '>=6.9.0'}
+ dev: true
/@babel/helper-validator-option@7.22.15:
resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==}
@@ -653,17 +601,6 @@ packages:
'@babel/types': 7.22.17
dev: true
- /@babel/helpers@7.22.10:
- resolution: {integrity: sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==}
- engines: {node: '>=6.9.0'}
- dependencies:
- '@babel/template': 7.22.5
- '@babel/traverse': 7.22.10
- '@babel/types': 7.22.10
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@babel/helpers@7.22.15:
resolution: {integrity: sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==}
engines: {node: '>=6.9.0'}
@@ -675,14 +612,6 @@ packages:
- supports-color
dev: true
- /@babel/highlight@7.22.10:
- resolution: {integrity: sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==}
- engines: {node: '>=6.9.0'}
- dependencies:
- '@babel/helper-validator-identifier': 7.22.5
- chalk: 2.4.2
- js-tokens: 4.0.0
-
/@babel/highlight@7.22.13:
resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==}
engines: {node: '>=6.9.0'}
@@ -690,14 +619,13 @@ packages:
'@babel/helper-validator-identifier': 7.22.15
chalk: 2.4.2
js-tokens: 4.0.0
- dev: true
/@babel/parser@7.22.10:
resolution: {integrity: sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
- '@babel/types': 7.22.10
+ '@babel/types': 7.22.17
dev: true
/@babel/parser@7.22.16:
@@ -1447,16 +1375,6 @@ packages:
'@babel/plugin-transform-react-jsx': 7.22.15(@babel/core@7.22.17)
dev: true
- /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.22.10):
- resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.10
- '@babel/helper-plugin-utils': 7.22.5
- dev: true
-
/@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.22.17):
resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==}
engines: {node: '>=6.9.0'}
@@ -1467,16 +1385,6 @@ packages:
'@babel/helper-plugin-utils': 7.22.5
dev: true
- /@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.22.10):
- resolution: {integrity: sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.22.10
- '@babel/helper-plugin-utils': 7.22.5
- dev: true
-
/@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.22.17):
resolution: {integrity: sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==}
engines: {node: '>=6.9.0'}
@@ -1828,24 +1736,6 @@ packages:
'@babel/types': 7.22.10
dev: true
- /@babel/traverse@7.22.10:
- resolution: {integrity: sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==}
- engines: {node: '>=6.9.0'}
- dependencies:
- '@babel/code-frame': 7.22.10
- '@babel/generator': 7.22.10
- '@babel/helper-environment-visitor': 7.22.5
- '@babel/helper-function-name': 7.22.5
- '@babel/helper-hoist-variables': 7.22.5
- '@babel/helper-split-export-declaration': 7.22.6
- '@babel/parser': 7.22.10
- '@babel/types': 7.22.10
- debug: 4.3.4
- globals: 11.12.0
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@babel/traverse@7.22.17:
resolution: {integrity: sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==}
engines: {node: '>=6.9.0'}
@@ -1871,6 +1761,7 @@ packages:
'@babel/helper-string-parser': 7.22.5
'@babel/helper-validator-identifier': 7.22.5
to-fast-properties: 2.0.0
+ dev: true
/@babel/types@7.22.17:
resolution: {integrity: sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==}
@@ -1879,7 +1770,6 @@ packages:
'@babel/helper-string-parser': 7.22.5
'@babel/helper-validator-identifier': 7.22.15
to-fast-properties: 2.0.0
- dev: true
/@base2/pretty-print-object@1.0.1:
resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==}
@@ -1939,7 +1829,7 @@ packages:
/@emotion/babel-plugin@11.11.0:
resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==}
dependencies:
- '@babel/helper-module-imports': 7.22.5
+ '@babel/helper-module-imports': 7.22.15
'@babel/runtime': 7.22.10
'@emotion/hash': 0.9.1
'@emotion/memoize': 0.8.1
@@ -3037,7 +2927,7 @@ packages:
'@babel/parser': 7.22.16
'@babel/plugin-syntax-import-assertions': 7.22.5(@babel/core@7.22.17)
'@babel/traverse': 7.22.17
- '@babel/types': 7.22.10
+ '@babel/types': 7.22.17
'@graphql-tools/utils': 10.0.6(graphql@16.8.0)
graphql: 16.8.0
tslib: 2.6.2
@@ -6193,13 +6083,13 @@ packages:
file-system-cache: 2.3.0
dev: true
- /@svgr/babel-plugin-add-jsx-attribute@7.0.0(@babel/core@7.22.10):
+ /@svgr/babel-plugin-add-jsx-attribute@7.0.0(@babel/core@7.22.17):
resolution: {integrity: sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.22.10
+ '@babel/core': 7.22.17
dev: true
/@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.22.17):
@@ -6211,13 +6101,13 @@ packages:
'@babel/core': 7.22.17
dev: true
- /@svgr/babel-plugin-remove-jsx-attribute@7.0.0(@babel/core@7.22.10):
+ /@svgr/babel-plugin-remove-jsx-attribute@7.0.0(@babel/core@7.22.17):
resolution: {integrity: sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.22.10
+ '@babel/core': 7.22.17
dev: true
/@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.22.17):
@@ -6229,13 +6119,13 @@ packages:
'@babel/core': 7.22.17
dev: true
- /@svgr/babel-plugin-remove-jsx-empty-expression@7.0.0(@babel/core@7.22.10):
+ /@svgr/babel-plugin-remove-jsx-empty-expression@7.0.0(@babel/core@7.22.17):
resolution: {integrity: sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.22.10
+ '@babel/core': 7.22.17
dev: true
/@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.22.17):
@@ -6247,13 +6137,13 @@ packages:
'@babel/core': 7.22.17
dev: true
- /@svgr/babel-plugin-replace-jsx-attribute-value@7.0.0(@babel/core@7.22.10):
+ /@svgr/babel-plugin-replace-jsx-attribute-value@7.0.0(@babel/core@7.22.17):
resolution: {integrity: sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.22.10
+ '@babel/core': 7.22.17
dev: true
/@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.22.17):
@@ -6265,13 +6155,13 @@ packages:
'@babel/core': 7.22.17
dev: true
- /@svgr/babel-plugin-svg-dynamic-title@7.0.0(@babel/core@7.22.10):
+ /@svgr/babel-plugin-svg-dynamic-title@7.0.0(@babel/core@7.22.17):
resolution: {integrity: sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.22.10
+ '@babel/core': 7.22.17
dev: true
/@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.22.17):
@@ -6283,13 +6173,13 @@ packages:
'@babel/core': 7.22.17
dev: true
- /@svgr/babel-plugin-svg-em-dimensions@7.0.0(@babel/core@7.22.10):
+ /@svgr/babel-plugin-svg-em-dimensions@7.0.0(@babel/core@7.22.17):
resolution: {integrity: sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.22.10
+ '@babel/core': 7.22.17
dev: true
/@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.22.17):
@@ -6301,13 +6191,13 @@ packages:
'@babel/core': 7.22.17
dev: true
- /@svgr/babel-plugin-transform-react-native-svg@7.0.0(@babel/core@7.22.10):
+ /@svgr/babel-plugin-transform-react-native-svg@7.0.0(@babel/core@7.22.17):
resolution: {integrity: sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.22.10
+ '@babel/core': 7.22.17
dev: true
/@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.22.17):
@@ -6319,13 +6209,13 @@ packages:
'@babel/core': 7.22.17
dev: true
- /@svgr/babel-plugin-transform-svg-component@7.0.0(@babel/core@7.22.10):
+ /@svgr/babel-plugin-transform-svg-component@7.0.0(@babel/core@7.22.17):
resolution: {integrity: sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==}
engines: {node: '>=12'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.22.10
+ '@babel/core': 7.22.17
dev: true
/@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.22.17):
@@ -6337,21 +6227,21 @@ packages:
'@babel/core': 7.22.17
dev: true
- /@svgr/babel-preset@7.0.0(@babel/core@7.22.10):
+ /@svgr/babel-preset@7.0.0(@babel/core@7.22.17):
resolution: {integrity: sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.22.10
- '@svgr/babel-plugin-add-jsx-attribute': 7.0.0(@babel/core@7.22.10)
- '@svgr/babel-plugin-remove-jsx-attribute': 7.0.0(@babel/core@7.22.10)
- '@svgr/babel-plugin-remove-jsx-empty-expression': 7.0.0(@babel/core@7.22.10)
- '@svgr/babel-plugin-replace-jsx-attribute-value': 7.0.0(@babel/core@7.22.10)
- '@svgr/babel-plugin-svg-dynamic-title': 7.0.0(@babel/core@7.22.10)
- '@svgr/babel-plugin-svg-em-dimensions': 7.0.0(@babel/core@7.22.10)
- '@svgr/babel-plugin-transform-react-native-svg': 7.0.0(@babel/core@7.22.10)
- '@svgr/babel-plugin-transform-svg-component': 7.0.0(@babel/core@7.22.10)
+ '@babel/core': 7.22.17
+ '@svgr/babel-plugin-add-jsx-attribute': 7.0.0(@babel/core@7.22.17)
+ '@svgr/babel-plugin-remove-jsx-attribute': 7.0.0(@babel/core@7.22.17)
+ '@svgr/babel-plugin-remove-jsx-empty-expression': 7.0.0(@babel/core@7.22.17)
+ '@svgr/babel-plugin-replace-jsx-attribute-value': 7.0.0(@babel/core@7.22.17)
+ '@svgr/babel-plugin-svg-dynamic-title': 7.0.0(@babel/core@7.22.17)
+ '@svgr/babel-plugin-svg-em-dimensions': 7.0.0(@babel/core@7.22.17)
+ '@svgr/babel-plugin-transform-react-native-svg': 7.0.0(@babel/core@7.22.17)
+ '@svgr/babel-plugin-transform-svg-component': 7.0.0(@babel/core@7.22.17)
dev: true
/@svgr/babel-preset@8.1.0(@babel/core@7.22.17):
@@ -6375,8 +6265,8 @@ packages:
resolution: {integrity: sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==}
engines: {node: '>=14'}
dependencies:
- '@babel/core': 7.22.10
- '@svgr/babel-preset': 7.0.0(@babel/core@7.22.10)
+ '@babel/core': 7.22.17
+ '@svgr/babel-preset': 7.0.0(@babel/core@7.22.17)
camelcase: 6.3.0
cosmiconfig: 8.2.0
transitivePeerDependencies:
@@ -6401,7 +6291,7 @@ packages:
resolution: {integrity: sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==}
engines: {node: '>=14'}
dependencies:
- '@babel/types': 7.22.10
+ '@babel/types': 7.22.17
entities: 4.5.0
dev: true
@@ -6417,8 +6307,8 @@ packages:
resolution: {integrity: sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw==}
engines: {node: '>=14'}
dependencies:
- '@babel/core': 7.22.10
- '@svgr/babel-preset': 7.0.0(@babel/core@7.22.10)
+ '@babel/core': 7.22.17
+ '@svgr/babel-preset': 7.0.0(@babel/core@7.22.17)
'@svgr/hast-util-to-babel-ast': 7.0.0
svg-parser: 2.0.4
transitivePeerDependencies:
@@ -6545,7 +6435,7 @@ packages:
resolution: {integrity: sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==}
engines: {node: '>=14'}
dependencies:
- '@babel/code-frame': 7.22.10
+ '@babel/code-frame': 7.22.13
'@babel/runtime': 7.22.10
'@types/aria-query': 5.0.1
aria-query: 5.1.3
@@ -7264,9 +7154,9 @@ packages:
peerDependencies:
vite: ^4.2.0
dependencies:
- '@babel/core': 7.22.10
- '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.22.10)
- '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.22.10)
+ '@babel/core': 7.22.17
+ '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.22.17)
+ '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.22.17)
react-refresh: 0.14.0
vite: 4.4.9(@types/node@20.6.0)
transitivePeerDependencies:
@@ -13102,7 +12992,7 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
dependencies:
- '@babel/code-frame': 7.22.10
+ '@babel/code-frame': 7.22.13
error-ex: 1.3.2
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 1c53af813..4d831345c 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -23,6 +23,9 @@
"@origin/oeth/history": [
"libs/oeth/history/src/index.ts"
],
+ "@origin/oeth/redeem": [
+ "libs/oeth/redeem/src/index.ts"
+ ],
"@origin/oeth/shared": [
"libs/oeth/shared/src/index.ts"
],