From 40fd667c72cc7ff6dab5567421252f1172582e22 Mon Sep 17 00:00:00 2001 From: toniocodo Date: Fri, 26 Jul 2024 11:29:42 +0200 Subject: [PATCH 01/12] feat: add refresher hook --- libs/shared/providers/src/index.ts | 1 + libs/shared/providers/src/refresher/hooks.ts | 87 ++++++++++++++++++++ libs/shared/providers/src/refresher/index.ts | 1 + 3 files changed, 89 insertions(+) create mode 100644 libs/shared/providers/src/refresher/hooks.ts create mode 100644 libs/shared/providers/src/refresher/index.ts diff --git a/libs/shared/providers/src/index.ts b/libs/shared/providers/src/index.ts index e880451bd..12f603e31 100644 --- a/libs/shared/providers/src/index.ts +++ b/libs/shared/providers/src/index.ts @@ -11,6 +11,7 @@ export * from './notifications'; export * from './prices'; export * from './rebaseBanner'; export * from './redeemer'; +export * from './refresher'; export * from './settings'; export * from './slippage'; export * from './swapper'; diff --git a/libs/shared/providers/src/refresher/hooks.ts b/libs/shared/providers/src/refresher/hooks.ts new file mode 100644 index 000000000..3031939a7 --- /dev/null +++ b/libs/shared/providers/src/refresher/hooks.ts @@ -0,0 +1,87 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useCallback, useState } from 'react'; + +import { useIntervalEffect } from '@react-hookz/web'; +import { useQueryClient } from '@tanstack/react-query'; + +import type { QueryFunction, QueryKey } from '@tanstack/react-query'; + +export type RefreshStatus = + | 'idle' + | 'polling' + | 'processed' + | 'timeout' + | 'error'; + +export type UseRefresherProps = { + queryKey: QueryKey; + queryFn: QueryFunction; + isResultProcessed: (prev: QueryResult, next: QueryResult) => boolean; + onSettled?: (status: RefreshStatus, next: QueryResult) => void; + maxRetries?: number; + interval?: number; +}; + +export const useRefresher = ({ + queryKey, + queryFn, + isResultProcessed, + onSettled, + maxRetries = 10, + interval = 2000, +}: UseRefresherProps) => { + const queryClient = useQueryClient(); + const prev = queryClient.getQueryData(queryKey); + const [retries, setRetries] = useState(0); + const [status, setStatus] = useState('idle'); + const [pollInterval, setPollInterval] = useState( + undefined, + ); + + const startRefresh = useCallback(() => { + setPollInterval(interval); + setStatus('polling'); + }, [interval]); + + const stopRefresh = useCallback(() => { + setPollInterval(undefined); + setStatus('idle'); + }, []); + + useIntervalEffect(() => { + (async () => { + const next = await queryClient.fetchQuery({ + queryKey, + queryFn, + staleTime: 0, + }); + + try { + if (!prev || !next) { + setPollInterval(undefined); + setStatus('error'); + onSettled?.('error', next); + } else if (retries > maxRetries) { + setPollInterval(undefined); + setStatus('timeout'); + onSettled?.('timeout', next); + } else if (isResultProcessed(prev, next)) { + setPollInterval(undefined); + setStatus('processed'); + onSettled?.('processed', next); + } + } catch { + setPollInterval(undefined); + setStatus('error'); + onSettled?.('error', next); + } + setRetries((prev) => prev + 1); + })(); + }, pollInterval); + + return { + status, + startRefresh, + stopRefresh, + }; +}; diff --git a/libs/shared/providers/src/refresher/index.ts b/libs/shared/providers/src/refresher/index.ts new file mode 100644 index 000000000..4cc90d02b --- /dev/null +++ b/libs/shared/providers/src/refresher/index.ts @@ -0,0 +1 @@ +export * from './hooks'; From bc7d500933e1ce3e2bb8506b6c22cd1839e96a5e Mon Sep 17 00:00:00 2001 From: toniocodo Date: Sat, 27 Jul 2024 15:51:58 +0200 Subject: [PATCH 02/12] feat: make oeth actions and curve helper chain safe --- libs/oeth/swap/src/actions/mintVault.ts | 13 +++++++++++-- libs/oeth/swap/src/actions/swapCurve/index.ts | 16 +++++++++++++--- libs/oeth/swap/src/actions/swapCurveEth.ts | 6 +++++- libs/oeth/swap/src/actions/swapCurveSfrxeth.ts | 10 ++++++++-- libs/oeth/swap/src/actions/swapZapperEth.ts | 9 +++++++-- libs/oeth/swap/src/actions/swapZapperSfrxeth.ts | 7 ++++++- libs/oeth/swap/src/actions/unwrapWOETH.ts | 6 +++++- libs/oeth/swap/src/actions/wrapOETH.ts | 10 ++++++++-- libs/shared/providers/src/curve/hooks.ts | 4 ++++ 9 files changed, 67 insertions(+), 14 deletions(-) diff --git a/libs/oeth/swap/src/actions/mintVault.ts b/libs/oeth/swap/src/actions/mintVault.ts index 37199188b..a1e752952 100644 --- a/libs/oeth/swap/src/actions/mintVault.ts +++ b/libs/oeth/swap/src/actions/mintVault.ts @@ -33,6 +33,7 @@ const isRouteAvailable: IsRouteAvailable = async (config, { tokenIn }) => { abi: contracts.mainnet.OETHVault.abi, functionName: 'priceUnitMint', args: [tokenIn.address], + chainId: contracts.mainnet.OETHVault.chainId, }); return true; @@ -55,6 +56,7 @@ const estimateAmount: EstimateAmount = async ( abi: contracts.mainnet.OETHVault.abi, functionName: 'priceUnitMint', args: [tokenIn.address], + chainId: contracts.mainnet.OETHVault.chainId, }); return parseUnits( @@ -71,7 +73,9 @@ const estimateGas: EstimateGas = async ( { tokenIn, tokenOut, amountIn, slippage, amountOut }, ) => { let gasEstimate = 0n; - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { + chainId: contracts.mainnet.OETHVault.chainId, + }); if (amountIn === 0n || !publicClient || !tokenIn?.address) { return gasEstimate; @@ -103,11 +107,13 @@ const estimateGas: EstimateGas = async ( address: contracts.mainnet.OETHVault.address, abi: contracts.mainnet.OETHVault.abi, functionName: 'rebaseThreshold', + chainId: contracts.mainnet.OETHVault.chainId, }, { address: contracts.mainnet.OETHVault.address, abi: contracts.mainnet.OETHVault.abi, functionName: 'autoAllocateThreshold', + chainId: contracts.mainnet.OETHVault.chainId, }, ], }), @@ -137,6 +143,7 @@ const allowance: Allowance = async (config, { tokenIn }) => { abi: erc20Abi, functionName: 'allowance', args: [address, contracts.mainnet.OETHVault.address], + chainId: tokenIn.chainId, }); return allowance; @@ -148,7 +155,7 @@ const estimateApprovalGas: EstimateApprovalGas = async ( ) => { let approvalEstimate = 0n; const { address } = getAccount(config); - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { chainId: tokenIn.chainId }); if (amountIn === 0n || !address || !publicClient || !tokenIn?.address) { return approvalEstimate; @@ -219,6 +226,7 @@ const approve: Approve = async (config, { tokenIn, tokenOut, amountIn }) => { abi: erc20Abi, functionName: 'approve', args: [contracts.mainnet.OETHVault.address, amountIn], + chainId: tokenIn.chainId, }); const hash = await writeContract(config, request); @@ -258,6 +266,7 @@ const swap: Swap = async ( functionName: 'mint', args: [tokenIn.address, amountIn, minAmountOut], gas, + chainId: contracts.mainnet.OETHVault.chainId, }); const hash = await writeContract(config, request); diff --git a/libs/oeth/swap/src/actions/swapCurve/index.ts b/libs/oeth/swap/src/actions/swapCurve/index.ts index 1b5b615b1..9fc9a4a9f 100644 --- a/libs/oeth/swap/src/actions/swapCurve/index.ts +++ b/libs/oeth/swap/src/actions/swapCurve/index.ts @@ -62,6 +62,7 @@ const estimateAmount: EstimateAmount = async ( abi: curve.CurveRegistryExchange.abi, functionName: 'get_exchange_multiple_amount', args: [curveConfig.routes, curveConfig.swapParams, amountIn], + chainId: curve.CurveRegistryExchange.chainId, }); return amountOut as unknown as bigint; @@ -72,9 +73,8 @@ const estimateGas: EstimateGas = async ( { tokenIn, tokenOut, amountIn, amountOut, slippage }, ) => { let gasEstimate = 0n; - const publicClient = getPublicClient(config); - if (amountIn === 0n || !publicClient) { + if (amountIn === 0n) { return gasEstimate; } @@ -100,6 +100,13 @@ const estimateGas: EstimateGas = async ( }); const isTokenInNative = isNativeCurrency(tokenIn); + const publicClient = getPublicClient(config, { + chainId: curve.CurveRegistryExchange.chainId, + }); + + if (!publicClient) { + return gasEstimate; + } try { gasEstimate = await publicClient.estimateContractGas({ @@ -143,6 +150,7 @@ const allowance: Allowance = async (config, { tokenIn }) => { abi: erc20Abi, functionName: 'allowance', args: [address, curve.CurveRegistryExchange.address], + chainId: tokenIn.chainId, }); return allowance; @@ -154,7 +162,7 @@ const estimateApprovalGas: EstimateApprovalGas = async ( ) => { let approvalEstimate = 0n; const { address } = getAccount(config); - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { chainId: tokenIn.chainId }); if (amountIn === 0n || !address || !publicClient) { return approvalEstimate; @@ -235,6 +243,7 @@ const approve: Approve = async (config, { tokenIn, amountIn }) => { abi: erc20Abi, functionName: 'approve', args: [curve.CurveRegistryExchange.address, amountIn], + chainId: tokenIn.chainId, }); const hash = await writeContract(config, request); @@ -291,6 +300,7 @@ const swap: Swap = async ( abi: curve.CurveRegistryExchange.abi, functionName: 'exchange_multiple', args: [curveConfig.routes, curveConfig.swapParams, amountIn, minAmountOut], + chainId: curve.CurveRegistryExchange.chainId, gas, ...(isTokenInNative && { value: amountIn }), }); diff --git a/libs/oeth/swap/src/actions/swapCurveEth.ts b/libs/oeth/swap/src/actions/swapCurveEth.ts index c1572807e..ee9ad8ee6 100644 --- a/libs/oeth/swap/src/actions/swapCurveEth.ts +++ b/libs/oeth/swap/src/actions/swapCurveEth.ts @@ -49,6 +49,7 @@ const estimateAmount: EstimateAmount = async ( tokenOut.address ?? ETH_ADDRESS_CURVE, amountIn, ], + chainId: curve.CurveRegistryExchange.chainId, }); return amountOut as unknown as bigint; @@ -59,7 +60,9 @@ const estimateGas: EstimateGas = async ( { tokenIn, tokenOut, amountIn, amountOut, slippage }, ) => { let gasEstimate = 0n; - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { + chainId: contracts.mainnet.OETHCurvePool.chainId, + }); if (amountIn === 0n || !publicClient) { return gasEstimate; @@ -206,6 +209,7 @@ const swap: Swap = async ( minAmountOut, ], gas, + chainId: contracts.mainnet.OETHCurvePool.chainId, ...(isTokenInNative && { value: amountIn }), }); const hash = await writeContract(config, request); diff --git a/libs/oeth/swap/src/actions/swapCurveSfrxeth.ts b/libs/oeth/swap/src/actions/swapCurveSfrxeth.ts index dfb025f52..e6d7c6297 100644 --- a/libs/oeth/swap/src/actions/swapCurveSfrxeth.ts +++ b/libs/oeth/swap/src/actions/swapCurveSfrxeth.ts @@ -59,6 +59,7 @@ const estimateAmount: EstimateAmount = async (config, { amountIn }) => { abi: contracts.mainnet.CurveRouter.abi, functionName: 'get_dy', args: [curveConfig.routes, curveConfig.params, amountIn], + chainId: contracts.mainnet.CurveRouter.chainId, }); return amountOut as unknown as bigint; @@ -70,7 +71,9 @@ const estimateGas: EstimateGas = async ( ) => { let gasEstimate = 0n; const { address } = getAccount(config); - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { + chainId: contracts.mainnet.CurveRouter.chainId, + }); if (amountIn === 0n || !publicClient) { return gasEstimate; @@ -105,6 +108,7 @@ const allowance: Allowance = async (config, { tokenIn }) => { abi: erc20Abi, functionName: 'allowance', args: [address, contracts.mainnet.CurveRouter.address], + chainId: tokenIn.chainId, }); return allowance; @@ -116,7 +120,7 @@ const estimateApprovalGas: EstimateApprovalGas = async ( ) => { let approvalEstimate = 0n; const { address } = getAccount(config); - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { chainId: tokenIn.chainId }); if (amountIn === 0n || !address || !tokenIn?.address || !publicClient) { return approvalEstimate; @@ -187,6 +191,7 @@ const approve: Approve = async (config, { tokenIn, amountIn }) => { abi: erc20Abi, functionName: 'approve', args: [contracts.mainnet.CurveRouter.address, amountIn], + chainId: tokenIn.chainId, }); const hash = await writeContract(config, request); @@ -227,6 +232,7 @@ const swap: Swap = async ( args: [curveConfig.routes, curveConfig.params, amountIn, minAmountOut], account: address, gas, + chainId: contracts.mainnet.CurveRouter.chainId, }); const hash = await writeContract(config, request); diff --git a/libs/oeth/swap/src/actions/swapZapperEth.ts b/libs/oeth/swap/src/actions/swapZapperEth.ts index 360835ca9..a8d5c8086 100644 --- a/libs/oeth/swap/src/actions/swapZapperEth.ts +++ b/libs/oeth/swap/src/actions/swapZapperEth.ts @@ -30,7 +30,9 @@ const estimateGas: EstimateGas = async (config, { amountIn }) => { let gasEstimate = 200000n; const { address } = getAccount(config); - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { + chainId: contracts.mainnet.OETHZapper.chainId, + }); if (amountIn === 0n || !address || !publicClient) { return gasEstimate; @@ -65,6 +67,7 @@ const allowance: Allowance = async (config, { tokenIn, tokenOut }) => { abi: erc20Abi, functionName: 'allowance', args: [address, contracts.mainnet.OETHZapper.address], + chainId: tokenIn.chainId, }); return allowance; @@ -76,7 +79,7 @@ const estimateApprovalGas: EstimateApprovalGas = async ( ) => { let approvalEstimate = 0n; const { address } = getAccount(config); - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { chainId: tokenIn.chainId }); if ( amountIn === 0n || @@ -148,6 +151,7 @@ const approve: Approve = async (config, { tokenIn, tokenOut, amountIn }) => { abi: erc20Abi, functionName: 'approve', args: [contracts.mainnet.OETHZapper.address, amountIn], + chainId: tokenIn.chainId, }); const hash = await writeContract(config, request); @@ -184,6 +188,7 @@ const swap: Swap = async ( functionName: 'deposit', value: amountIn, gas, + chainId: contracts.mainnet.OETHZapper.chainId, }); const hash = await writeContract(config, request); diff --git a/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts b/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts index 7245794ba..cfdcd6adf 100644 --- a/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts +++ b/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts @@ -38,12 +38,14 @@ const estimateAmount: EstimateAmount = async ( abi: tokens.mainnet.sfrxETH.abi, functionName: 'previewRedeem', args: [amountIn], + chainId: tokens.mainnet.sfrxETH.chainId, }, { address: contracts.mainnet.OETHVault.address, abi: contracts.mainnet.OETHVault.abi, functionName: 'priceUnitMint', args: [tokens.mainnet.frxETH.address], + chainId: contracts.mainnet.OETHVault.chainId, }, ], }); @@ -83,6 +85,7 @@ const allowance: Allowance = async (config, { tokenIn, tokenOut }) => { abi: erc20Abi, functionName: 'allowance', args: [address, contracts.mainnet.OETHZapper.address], + chainId: tokenIn.chainId, }); return allowance; @@ -94,7 +97,7 @@ const estimateApprovalGas: EstimateApprovalGas = async ( ) => { let approvalEstimate = 0n; const { address } = getAccount(config); - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { chainId: tokenIn.chainId }); if ( amountIn === 0n || @@ -166,6 +169,7 @@ const approve: Approve = async (config, { tokenIn, tokenOut, amountIn }) => { abi: erc20Abi, functionName: 'approve', args: [contracts.mainnet.OETHZapper.address, amountIn], + chainId: tokenIn.chainId, }); const hash = await writeContract(config, request); @@ -205,6 +209,7 @@ const swap: Swap = async ( functionName: 'depositSFRXETH', args: [amountIn, minAmountOut], gas, + chainId: contracts.mainnet.OETHZapper.chainId, }); const hash = await writeContract(config, request); diff --git a/libs/oeth/swap/src/actions/unwrapWOETH.ts b/libs/oeth/swap/src/actions/unwrapWOETH.ts index 59642c475..7b5bcb5a3 100644 --- a/libs/oeth/swap/src/actions/unwrapWOETH.ts +++ b/libs/oeth/swap/src/actions/unwrapWOETH.ts @@ -28,6 +28,7 @@ const estimateAmount: EstimateAmount = async (config, { amountIn }) => { abi: tokens.mainnet.wOETH.abi, functionName: 'convertToAssets', args: [amountIn], + chainId: tokens.mainnet.wOETH.chainId, }); return data as unknown as bigint; @@ -36,7 +37,9 @@ const estimateAmount: EstimateAmount = async (config, { amountIn }) => { const estimateGas: EstimateGas = async (config, { amountIn }) => { let gasEstimate = 0n; - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { + chainId: tokens.mainnet.wOETH.chainId, + }); if (amountIn === 0n) { return gasEstimate; @@ -137,6 +140,7 @@ const swap: Swap = async (config, { amountIn }) => { abi: tokens.mainnet.wOETH.abi, functionName: 'redeem', args: [amountIn, address, address], + chainId: tokens.mainnet.wOETH.chainId, }); const hash = await writeContract(config, request); diff --git a/libs/oeth/swap/src/actions/wrapOETH.ts b/libs/oeth/swap/src/actions/wrapOETH.ts index e4ae2b348..02a6d3d4c 100644 --- a/libs/oeth/swap/src/actions/wrapOETH.ts +++ b/libs/oeth/swap/src/actions/wrapOETH.ts @@ -30,6 +30,7 @@ const estimateAmount: EstimateAmount = async (config, { amountIn }) => { abi: tokens.mainnet.wOETH.abi, functionName: 'convertToShares', args: [amountIn], + chainId: tokens.mainnet.wOETH.chainId, }); return data as unknown as bigint; @@ -38,7 +39,9 @@ const estimateAmount: EstimateAmount = async (config, { amountIn }) => { const estimateGas: EstimateGas = async (config, { amountIn }) => { let gasEstimate = 0n; - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { + chainId: tokens.mainnet.wOETH.chainId, + }); if (amountIn === 0n) { return gasEstimate; @@ -90,6 +93,7 @@ const allowance: Allowance = async (config, { tokenIn }) => { abi: erc20Abi, functionName: 'allowance', args: [address, tokens.mainnet.wOETH.address], + chainId: tokenIn.chainId, }); return allowance; @@ -106,7 +110,7 @@ const estimateApprovalGas: EstimateApprovalGas = async ( return approvalEstimate; } - const publicClient = getPublicClient(config); + const publicClient = getPublicClient(config, { chainId: tokenIn.chainId }); try { if (publicClient) { @@ -170,6 +174,7 @@ const approve: Approve = async (config, { tokenIn, amountIn }) => { abi: erc20Abi, functionName: 'approve', args: [tokens.mainnet.wOETH.address, amountIn], + chainId: tokenIn.chainId, }); const hash = await writeContract(config, request); @@ -194,6 +199,7 @@ const swap: Swap = async (config, { tokenIn, tokenOut, amountIn }) => { abi: tokens.mainnet.wOETH.abi, functionName: 'deposit', args: [amountIn, address], + chainId: tokens.mainnet.wOETH.chainId, }); const hash = await writeContract(config, request); diff --git a/libs/shared/providers/src/curve/hooks.ts b/libs/shared/providers/src/curve/hooks.ts index cfff70eae..5e3ff0739 100644 --- a/libs/shared/providers/src/curve/hooks.ts +++ b/libs/shared/providers/src/curve/hooks.ts @@ -37,12 +37,14 @@ const fetcher: (config: Config) => QueryFunction< abi: contracts.mainnet.CurveAddressProvider.abi, functionName: 'get_address', args: [2n], + chainId: contracts.mainnet.CurveAddressProvider.chainId, }, { address: contracts.mainnet.CurveAddressProvider.address, abi: contracts.mainnet.CurveAddressProvider.abi, functionName: 'get_address', args: [3n], + chainId: contracts.mainnet.CurveAddressProvider.chainId, }, ], }); @@ -54,12 +56,14 @@ const fetcher: (config: Config) => QueryFunction< abi: CurveFactoryABI, functionName: 'get_coins', args: [contracts.mainnet.OETHCurvePool.address], + chainId: contracts.mainnet.OETHCurvePool.chainId, }, { address: addresses[1].result ?? ZERO_ADDRESS, abi: CurveFactoryABI, functionName: 'get_underlying_coins', args: [contracts.mainnet.OUSDCurveMetaPool.address], + chainId: contracts.mainnet.OUSDCurveMetaPool.chainId, }, ], }); From da74b72db1efe5a4dc6ceff9f792d02a741e97b3 Mon Sep 17 00:00:00 2001 From: toniocodo Date: Sat, 27 Jul 2024 21:09:17 +0200 Subject: [PATCH 03/12] fix: null check at --- .../governance/src/overview/components/CurrentResultsCard.tsx | 2 +- libs/defi/governance/src/overview/hooks.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/defi/governance/src/overview/components/CurrentResultsCard.tsx b/libs/defi/governance/src/overview/components/CurrentResultsCard.tsx index 59ed5f781..cb11f81b0 100644 --- a/libs/defi/governance/src/overview/components/CurrentResultsCard.tsx +++ b/libs/defi/governance/src/overview/components/CurrentResultsCard.tsx @@ -75,7 +75,7 @@ export const CurrentResultsCard = (props: CardProps) => { proposal?.choices?.findIndex( (c) => c!.toLowerCase() === choice.toLowerCase(), ) ?? -1; - const score = idx > -1 ? (proposal?.scores?.at(idx) ?? 0) : 0; + const score = idx > -1 ? (proposal?.scores?.at?.(idx) ?? 0) : 0; return ( diff --git a/libs/defi/governance/src/overview/hooks.ts b/libs/defi/governance/src/overview/hooks.ts index 4846d3a1f..ddc608ba8 100644 --- a/libs/defi/governance/src/overview/hooks.ts +++ b/libs/defi/governance/src/overview/hooks.ts @@ -151,7 +151,8 @@ export const useUserVotes = () => { continue; } const idx = - Number(Array.isArray(v?.choice) ? v.choice.at(0) : v?.choice) - 1; + Number(Array.isArray(v?.choice) ? v.choice?.at?.(0) : v?.choice) - + 1; const choice = v?.proposal?.choices?.at?.(idx) ?? ''; offChainVotes.push({ From 07a36fab4ee7ab5823d19886a0c305c6bbdd4a10 Mon Sep 17 00:00:00 2001 From: toniocodo Date: Sat, 27 Jul 2024 21:31:04 +0200 Subject: [PATCH 04/12] feat: update time remaining format --- .../src/staking/components/LockupsTable.tsx | 17 +++++++++---- .../staking/components/UnstakeLockupModal.tsx | 25 +++++++++++++------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/libs/defi/ogn/src/staking/components/LockupsTable.tsx b/libs/defi/ogn/src/staking/components/LockupsTable.tsx index 8d7073818..4ff0104ae 100644 --- a/libs/defi/ogn/src/staking/components/LockupsTable.tsx +++ b/libs/defi/ogn/src/staking/components/LockupsTable.tsx @@ -26,7 +26,7 @@ import { getPaginationRowModel, useReactTable, } from '@tanstack/react-table'; -import { differenceInDays, formatDistanceToNowStrict } from 'date-fns'; +import { differenceInDays, formatDistanceToNowStrict, isPast } from 'date-fns'; import { useIntl } from 'react-intl'; import { formatUnits } from 'viem'; import { useAccount } from 'wagmi'; @@ -86,10 +86,17 @@ export const LockupsTable = () => { id: 'timeRemaining', header: intl.formatMessage({ defaultMessage: 'Time Remaining' }), cell: (info) => - formatDistanceToNowStrict(new Date(info.row.original.end), { - unit: 'month', - roundingMethod: 'floor', - }), + isPast(new Date(info.row.original.end)) + ? '-' + : differenceInDays(new Date(info.row.original.end), new Date()) > 31 + ? formatDistanceToNowStrict(new Date(info.row.original.end), { + unit: 'month', + roundingMethod: 'floor', + }) + : formatDistanceToNowStrict(new Date(info.row.original.end), { + unit: 'day', + roundingMethod: 'ceil', + }), }), ...(isSm ? [] diff --git a/libs/defi/ogn/src/staking/components/UnstakeLockupModal.tsx b/libs/defi/ogn/src/staking/components/UnstakeLockupModal.tsx index 4a72ab9c7..f16356d05 100644 --- a/libs/defi/ogn/src/staking/components/UnstakeLockupModal.tsx +++ b/libs/defi/ogn/src/staking/components/UnstakeLockupModal.tsx @@ -21,7 +21,12 @@ import { } from '@origin/shared/icons'; import { ConnectedButton, TxButton } from '@origin/shared/providers'; import { getFormatPrecision, isNilOrEmpty } from '@origin/shared/utils'; -import { formatDistanceToNowStrict, getUnixTime } from 'date-fns'; +import { + differenceInDays, + formatDistanceToNowStrict, + getUnixTime, + isPast, +} from 'date-fns'; import { format, from } from 'dnum'; import { useIntl } from 'react-intl'; import { formatUnits } from 'viem'; @@ -80,6 +85,17 @@ export const UnstakeLockupModal = ({ const penaltyPercent = +formatUnits(penalty, tokens.mainnet.OGN.decimals) / +formatUnits(ognLockup, tokens.mainnet.OGN.decimals); + const timeRemaining = isPast(new Date(lockup.end)) + ? '-' + : differenceInDays(new Date(lockup.end), new Date()) > 31 + ? formatDistanceToNowStrict(new Date(lockup.end), { + unit: 'month', + roundingMethod: 'floor', + }) + : formatDistanceToNowStrict(new Date(lockup.end), { + unit: 'day', + roundingMethod: 'ceil', + }); return ( @@ -157,12 +173,7 @@ export const UnstakeLockupModal = ({ }, )} - - {formatDistanceToNowStrict(new Date(lockup.end), { - unit: 'month', - roundingMethod: 'floor', - })} - + {timeRemaining} {intl.formatNumber(penaltyPercent, { style: 'percent', From 3844ad64342d293fcd45e4930cf6c059197b5de2 Mon Sep 17 00:00:00 2001 From: toniocodo Date: Sun, 28 Jul 2024 06:25:13 +0200 Subject: [PATCH 05/12] fix: minAmountOut dnum to bigint --- libs/defi/ousd/src/swap/actions/uniswapV3.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/defi/ousd/src/swap/actions/uniswapV3.ts b/libs/defi/ousd/src/swap/actions/uniswapV3.ts index 3bd261c4d..36b044e50 100644 --- a/libs/defi/ousd/src/swap/actions/uniswapV3.ts +++ b/libs/defi/ousd/src/swap/actions/uniswapV3.ts @@ -330,7 +330,7 @@ const swap: Swap = async ( { path: getPath(tokenIn, tokenOut), amountIn: amountIn, - amountOutMinimum: minAmountOut, + amountOutMinimum: minAmountOut[0], deadline: BigInt(Date.now() + 2 * 60 * 1000), recipient: address, }, From 70d4b2270f650e92f86fc341717ad04522d3e2f0 Mon Sep 17 00:00:00 2001 From: toniocodo Date: Mon, 29 Jul 2024 08:07:38 +0200 Subject: [PATCH 06/12] feat: replace lockup update with extend add modal --- .../staking/components/AddToLockupModal.tsx | 491 ------------------ ...ckupModal.tsx => ExtendAddLockupModal.tsx} | 367 +++++++------ .../src/staking/components/LockupsTable.tsx | 36 +- .../staking/components/UnstakeLockupModal.tsx | 21 +- libs/defi/ogn/src/staking/utils.ts | 21 + 5 files changed, 251 insertions(+), 685 deletions(-) delete mode 100644 libs/defi/ogn/src/staking/components/AddToLockupModal.tsx rename libs/defi/ogn/src/staking/components/{ExtendLockupModal.tsx => ExtendAddLockupModal.tsx} (64%) create mode 100644 libs/defi/ogn/src/staking/utils.ts diff --git a/libs/defi/ogn/src/staking/components/AddToLockupModal.tsx b/libs/defi/ogn/src/staking/components/AddToLockupModal.tsx deleted file mode 100644 index 659829d91..000000000 --- a/libs/defi/ogn/src/staking/components/AddToLockupModal.tsx +++ /dev/null @@ -1,491 +0,0 @@ -import { useEffect, useState } from 'react'; - -import { - Accordion, - AccordionDetails, - AccordionSummary, - Button, - CardContent, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - Divider, - FormControlLabel, - IconButton, - Stack, - Switch, - Typography, - useMediaQuery, - useTheme, -} from '@mui/material'; -import { - SectionCard, - TokenChip, - useOgnInfo, - useTxButton, - useXOgnStakingApy, -} from '@origin/defi/shared'; -import { - BigIntInput, - InfoTooltipLabel, - LoadingLabel, - TokenIcon, - ValueLabel, -} from '@origin/shared/components'; -import { tokens } from '@origin/shared/contracts'; -import { DefaultWallet, FaXmarkRegular } from '@origin/shared/icons'; -import { ConnectedButton, TxButton, useFormat } from '@origin/shared/providers'; -import { - getMonthDurationToSeconds, - isNilOrEmpty, - ZERO_ADDRESS, -} from '@origin/shared/utils'; -import { useDebouncedEffect, useMountEffect } from '@react-hookz/web'; -import { differenceInMonths, formatDistanceToNowStrict } from 'date-fns'; -import { useIntl } from 'react-intl'; -import { formatUnits } from 'viem'; -import { useAccount } from 'wagmi'; - -import { useStartLockupPolling } from '../hooks'; - -import type { DialogProps } from '@mui/material'; -import type { ConnectedButtonProps } from '@origin/shared/providers'; -import type { ChangeEvent, MouseEvent } from 'react'; - -import type { Lockup } from '../types'; - -export type AddToLockupModalProps = { - lockup: Lockup; -} & DialogProps; - -export const AddToLockupModal = ({ - lockup, - ...rest -}: AddToLockupModalProps) => { - const monthDuration = Math.max( - 0, - differenceInMonths(new Date(lockup.end), new Date()), - ); - - const intl = useIntl(); - const { formatQuantity, formatAmount } = useFormat(); - const theme = useTheme(); - const fullScreen = useMediaQuery(theme.breakpoints.down('md')); - const { isConnected, address } = useAccount(); - const startPolling = useStartLockupPolling(); - const { data: info, isLoading: isInfoLoading } = useOgnInfo(); - const [amount, setAmount] = useState(0n); - const [addRewards, setAddRewards] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const { - data: staking, - isLoading: isStakingApyLoading, - refetch, - } = useXOgnStakingApy( - amount + (addRewards && info?.xOgnRewards ? info.xOgnRewards : 0n), - monthDuration, - { - enabled: false, - }, - ); - const { params: writeParams, callbacks: writeCallbacks } = useTxButton({ - params: { - contract: tokens.mainnet.xOGN, - functionName: 'stake', - args: [ - amount, - getMonthDurationToSeconds(monthDuration), - address ?? ZERO_ADDRESS, - addRewards, - BigInt(lockup.lockupId), - ], - }, - activity: { - type: 'extend-stake', - status: 'idle', - amountIn: amount, - tokenIdIn: tokens.mainnet.OGN.id, - monthDuration, - lockupId: lockup.lockupId, - }, - callbacks: { - onWriteSuccess: () => { - startPolling(lockup.lockupId); - rest?.onClose?.({}, 'backdropClick'); - }, - }, - }); - - useMountEffect(() => { - refetch(); - }); - - useDebouncedEffect( - () => { - if (amount > 0n || (addRewards && (info?.xOgnRewards ?? 0n) > 0n)) { - refetch(); - } - }, - [amount, addRewards], - 800, - ); - - useEffect(() => { - if (isLoading && !isNilOrEmpty(staking?.xOgnApyPercentage)) { - setIsLoading(false); - } - }, [ - addRewards, - amount, - isLoading, - isStakingApyLoading, - staking?.xOgnApyPercentage, - ]); - - const handleAmountChange = (val: bigint) => { - setIsLoading(true); - setAmount(val); - }; - - const handleMaxClick = (evt: MouseEvent) => { - evt.stopPropagation(); - if (amount !== info?.ognBalance) { - setIsLoading(true); - setAmount(info?.ognBalance ?? 0n); - } - }; - - const handleToggleAddRewards = (evt: ChangeEvent) => { - evt.stopPropagation(); - if (info?.xOgnRewards) { - setIsLoading(true); - setAddRewards(evt.target.checked); - } - }; - - const xOgnReceived = amount === 0n ? 0 : staking?.xOgnPreview; - const votingPowerPercent = - (xOgnReceived ?? 0) / - +formatUnits( - (info?.xOgnTotalSupply as unknown as bigint) ?? 1n, - tokens.mainnet.OGN.decimals, - ); - const isStakeDisabled = - !isConnected || isInfoLoading || isLoading || amount === 0n; - - return ( - - - {intl.formatMessage({ defaultMessage: 'Add to lockup' })} - { - rest?.onClose?.(evt, 'backdropClick'); - }} - > - - - - - - - - {intl.formatMessage({ - defaultMessage: 'Amount to add', - })} - - - - - } - inputProps={{ sx: { p: 0, height: 40 } }} - sx={{ - p: 3, - borderRadius: 3, - backgroundColor: 'background.highlight', - border: '1px solid', - borderColor: 'divider', - ...theme.typography.h6, - }} - /> - - - - } - label={ - - {intl.formatMessage( - { - defaultMessage: 'Add unclaimed rewards ({rewards} OGN)', - }, - { - rewards: formatAmount( - info?.xOgnRewards ?? 0n, - tokens.mainnet.OGN.decimals, - ), - }, - )} - - } - /> - - - - - {formatAmount( - info?.xOgnRewards ?? 0n, - tokens.mainnet.OGN.decimals, - )} - - - - - - - - {intl.formatMessage({ defaultMessage: 'Adding to lockup' })} - - - - - {intl.formatMessage({ defaultMessage: 'OGN' })} - - - {intl.formatMessage({ defaultMessage: 'Time remaining' })} - - - {intl.formatMessage({ defaultMessage: 'Voting power' })} - - - - - - {intl.formatNumber( - +formatUnits( - BigInt(lockup.amount), - tokens.mainnet.OGN.decimals, - ), - { - minimumFractionDigits: 0, - maximumFractionDigits: 0, - }, - )} - - - {formatDistanceToNowStrict(new Date(lockup.end), { - unit: 'month', - roundingMethod: 'floor', - })} - - - {intl.formatNumber( - +formatUnits( - BigInt(lockup.points) ?? 0n, - tokens.mainnet.xOGN.decimals, - ) / - +formatUnits( - info?.xOgnTotalSupply ?? 1n, - tokens.mainnet.xOGN.decimals, - ), - { - style: 'percent', - minimumFractionDigits: 2, - maximumFractionDigits: 6, - }, - )} - - - - - - - - - {formatQuantity( - staking?.xOgnPreview, - tokens.mainnet.xOGN.decimals, - '0.00', - )} - - - - {tokens.mainnet.xOGN.symbol} - - - 0n} - value={intl.formatMessage( - { defaultMessage: '{tilt}{value}' }, - { - tilt: - votingPowerPercent <= 1e-6 && votingPowerPercent > 0 - ? `~ ` - : '', - value: - amount > 0n || addRewards - ? intl.formatNumber(votingPowerPercent, { - style: 'percent', - minimumFractionDigits: 2, - maximumFractionDigits: 5, - }) - : '-', - }, - )} - valueProps={{ variant: 'body3', fontWeight: 'medium' }} - sx={{ alignItems: 'flex-end' }} - /> - - - - - - - - - ); -}; - -export type AddButtonProps = { - lockup: Lockup; -} & ConnectedButtonProps; - -export const AddButton = ({ lockup, ...rest }: AddButtonProps) => { - const [open, setOpen] = useState(false); - - return ( - <> - { - setOpen(true); - rest?.onClick?.(e); - }} - /> - { - setOpen(false); - }} - /> - - ); -}; diff --git a/libs/defi/ogn/src/staking/components/ExtendLockupModal.tsx b/libs/defi/ogn/src/staking/components/ExtendAddLockupModal.tsx similarity index 64% rename from libs/defi/ogn/src/staking/components/ExtendLockupModal.tsx rename to libs/defi/ogn/src/staking/components/ExtendAddLockupModal.tsx index 6bc0f7c6c..1badcd5da 100644 --- a/libs/defi/ogn/src/staking/components/ExtendLockupModal.tsx +++ b/libs/defi/ogn/src/staking/components/ExtendAddLockupModal.tsx @@ -1,50 +1,66 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { Box, + Button, CardContent, + Collapse, Dialog, DialogActions, DialogContent, DialogTitle, Divider, + FormControlLabel, IconButton, Slider, Stack, + Switch, Typography, useMediaQuery, useTheme, } from '@mui/material'; import { SectionCard, + TokenButton, + useApprovalButton, useOgnInfo, useTxButton, useXOgnStakingApy, } from '@origin/defi/shared'; -import { LoadingLabel, TokenIcon, ValueLabel } from '@origin/shared/components'; +import { + BigIntInput, + LoadingLabel, + TokenIcon, + ValueLabel, +} from '@origin/shared/components'; import { tokens } from '@origin/shared/contracts'; import { FaCircleExclamationRegular, FaXmarkRegular, + WalletFilled, } from '@origin/shared/icons'; import { ConnectedButton, TxButton, useFormat } from '@origin/shared/providers'; -import { isNilOrEmpty, ZERO_ADDRESS } from '@origin/shared/utils'; -import { useDebouncedEffect, useMountEffect } from '@react-hookz/web'; +import { getFormatPrecision, ZERO_ADDRESS } from '@origin/shared/utils'; import { + addDays, addMonths, - differenceInMonths, - formatDistanceToNowStrict, + differenceInDays, formatDuration, isPast, } from 'date-fns'; +import { add, eq, format, from, toNumber } from 'dnum'; import { useIntl } from 'react-intl'; import { formatUnits } from 'viem'; import { useAccount } from 'wagmi'; import { useStartLockupPolling } from '../hooks'; +import { formatTimeRemaining } from '../utils'; import type { DialogProps } from '@mui/material'; +import type { SectionCardProps } from '@origin/defi/shared'; import type { ConnectedButtonProps } from '@origin/shared/providers'; +import type { Dnum } from 'dnum'; +import type { ChangeEvent } from 'react'; import type { Lockup } from '../types'; @@ -52,46 +68,77 @@ export type ExtendLockupModalProps = { lockup: Lockup; } & DialogProps; -export const ExtendLockupModal = ({ +const MIN_MONTH_DURATION = 1; + +export const ExtendAddLockupModal = ({ lockup, ...rest }: ExtendLockupModalProps) => { - const amount = BigInt(lockup.amount); - const initialMonthDuration = Math.max( - 0, - differenceInMonths(new Date(lockup.end), new Date()), + const initialAmount = [ + BigInt(lockup.amount), + tokens.mainnet.OGN.decimals, + ] as Dnum; + const minEndDate = isPast(new Date(lockup.end)) + ? addMonths(new Date(), MIN_MONTH_DURATION) + : differenceInDays(new Date(lockup.end), new Date()) < 365 / 12 + ? addDays( + new Date(lockup.end), + Math.floor( + 365 / 12 - differenceInDays(new Date(lockup.end), new Date()), + ), + ) + : new Date(lockup.end); + const initialMonthDuration = Math.floor( + differenceInDays(minEndDate, new Date()) / Math.floor(365 / 12), ); + const intl = useIntl(); - const { formatQuantity, formatAmount } = useFormat(); + const { formatAmount } = useFormat(); const theme = useTheme(); const fullScreen = useMediaQuery(theme.breakpoints.down('md')); const startPolling = useStartLockupPolling(); const { isConnected, address } = useAccount(); const { data: info, isLoading: isInfoLoading } = useOgnInfo(); + const [amountToAdd, setAmountToAdd] = useState( + from(0, tokens.mainnet.OGN.decimals), + ); const [duration, setDuration] = useState(initialMonthDuration); - const [isLoading, setIsLoading] = useState(false); - const { data: staking, refetch } = useXOgnStakingApy(amount, duration, { - enabled: false, - }); + const [addRewards, setAddRewards] = useState(true); + const totalAmount = add(initialAmount, amountToAdd); + const { data: staking, isLoading: isStakingLoading } = useXOgnStakingApy( + totalAmount[0], + duration, + ); const durationSeconds = BigInt( Math.min(duration * 60 * 60 * 24 * (365 / 12), 31_536_000), ); + const { + allowance, + params: approvalParams, + callbacks: approvalCallbacks, + label: approvalLabel, + } = useApprovalButton({ + token: tokens.mainnet.OGN, + spender: tokens.mainnet.xOGN.address, + amount: add(totalAmount, [1n, tokens.mainnet.OGN.decimals])[0], + enableAllowance: true, + }); const { params: writeParams, callbacks: writeCallbacks } = useTxButton({ params: { contract: tokens.mainnet.xOGN, functionName: 'stake', args: [ - 0n, + totalAmount[0], durationSeconds, address ?? ZERO_ADDRESS, - true, + addRewards, BigInt(lockup.lockupId), ], }, activity: { type: 'extend-stake', status: 'pending', - amountIn: amount, + amountIn: totalAmount[0], tokenIdIn: tokens.mainnet.xOGN.id, monthDuration: duration, lockupId: lockup.lockupId, @@ -104,60 +151,50 @@ export const ExtendLockupModal = ({ }, }); - useMountEffect(() => { - refetch(); - }); + const handleMaxClick = () => { + setAmountToAdd([info?.ognBalance ?? 0n, tokens.mainnet.OGN.decimals]); + }; - useDebouncedEffect( - () => { - if (duration > initialMonthDuration) { - refetch(); - } - }, - [duration], - 800, - ); + const handleAmountChange = (val: bigint) => { + setAmountToAdd([val, tokens.mainnet.OGN.decimals]); + }; - useEffect(() => { - if ( - isLoading && - (!isNilOrEmpty(staking?.xOgnApyPercentage) || - duration === initialMonthDuration) - ) { - setIsLoading(false); - } - }, [duration, initialMonthDuration, isLoading, staking?.xOgnApyPercentage]); + const handleToggleAddRewards = (event: ChangeEvent) => { + setAddRewards(event.target.checked); + }; const handleDurationChange = (_: Event, newValue: number | number[]) => { const val = newValue as number; - if (val >= (initialMonthDuration ?? 0)) { - setIsLoading(true); + if (val >= initialMonthDuration) { setDuration(val); } }; - const xOgnReceived = - duration === initialMonthDuration - ? +formatUnits(BigInt(lockup.points), tokens.mainnet.xOGN.decimals) - : staking?.xOgnPreview; + const bal = [info?.ognBalance ?? 0n, tokens.mainnet.OGN.decimals] as Dnum; + const xOgnReceived = from( + staking?.xOgnPreview ?? 0, + tokens.mainnet.xOGN.decimals, + ); const votingPowerPercent = - (xOgnReceived ?? 0) / + toNumber(from(staking?.xOgnPreview ?? 0, tokens.mainnet.xOGN.decimals)) / +formatUnits( (info?.xOgnTotalSupply as unknown as bigint) ?? 1n, tokens.mainnet.OGN.decimals, ); - const showRewardLabel = ((info?.xOgnRewards as unknown as bigint) ?? 0n) > 0n; + const extendLockupEnd = + duration === initialMonthDuration + ? minEndDate + : addMonths(minEndDate, duration - initialMonthDuration); + const showApprove = + isConnected && + !isInfoLoading && + amountToAdd[0] <= (info?.ognBalance ?? 0n) && + totalAmount[0] >= (allowance ?? 0n); const isStakeDisabled = !isConnected || isInfoLoading || - isLoading || - duration <= initialMonthDuration; - const extendLockupEnd = - duration === initialMonthDuration - ? isPast(new Date(lockup.end)) - ? new Date() - : new Date(lockup.end) - : addMonths(new Date(), duration); + showApprove || + (duration === initialMonthDuration && eq(amountToAdd, initialAmount)); return ( @@ -166,7 +203,7 @@ export const ExtendLockupModal = ({ justifyContent="space-between" alignItems="center" > - {intl.formatMessage({ defaultMessage: 'Extend Stake' })} + {intl.formatMessage({ defaultMessage: 'Manage Stake' })} { rest?.onClose?.(evt, 'backdropClick'); @@ -177,19 +214,89 @@ export const ExtendLockupModal = ({ - + + + {intl.formatMessage({ defaultMessage: 'Amount to add' })} + + + + } + sx={{ + px: 3, + py: 2, + borderRadius: 3, + backgroundColor: 'background.highlight', + border: '1px solid', + borderColor: 'divider', + ...theme.typography.h6, + }} + /> + + + } + label={intl.formatMessage( + { + defaultMessage: 'Add unclaimed rewards to stake ({rewards} OGN)', + }, + { + rewards: formatAmount( + info?.xOgnRewards ?? 0n, + tokens.mainnet.OGN.decimals, + ), + }, + )} + sx={{ mb: 3 }} + /> + + + + + {intl.formatMessage({ + defaultMessage: + 'Any unclaimed rewards will transferred to your wallet immediately when you extend your stake.', + })} + + + + - - {intl.formatMessage({ defaultMessage: 'Selected lockup' })} - - {intl.formatMessage({ defaultMessage: 'OGN' })} @@ -215,12 +322,7 @@ export const ExtendLockupModal = ({ }, )} - - {formatDistanceToNowStrict(new Date(lockup.end), { - unit: 'month', - roundingMethod: 'floor', - })} - + {formatTimeRemaining(lockup.end)} {intl.formatNumber( +formatUnits( @@ -239,17 +341,14 @@ export const ExtendLockupModal = ({ )} - + @@ -340,11 +439,11 @@ export const ExtendLockupModal = ({ ~ {intl.formatNumber(staking?.xOgnApyPercentage ?? 0, { @@ -369,9 +468,9 @@ export const ExtendLockupModal = ({ 0n} + isLoading={isStakingLoading || isInfoLoading} sWidth={60} > - {amount > 0n ? formatQuantity(staking?.xOgnPreview) : '0.00'} + {format(xOgnReceived, getFormatPrecision(xOgnReceived))} 0n} + isLoading={isStakingLoading} value={intl.formatMessage( { defaultMessage: '{tilt}{value}' }, { @@ -424,14 +523,11 @@ export const ExtendLockupModal = ({ votingPowerPercent <= 1e-6 && votingPowerPercent > 0 ? `~ ` : '', - value: - amount > 0n - ? intl.formatNumber(votingPowerPercent, { - style: 'percent', - minimumFractionDigits: 2, - maximumFractionDigits: 5, - }) - : '0.00%', + value: intl.formatNumber(votingPowerPercent, { + style: 'percent', + minimumFractionDigits: 2, + maximumFractionDigits: 5, + }), }, )} valueProps={{ variant: 'body3', fontWeight: 'medium' }} @@ -440,53 +536,23 @@ export const ExtendLockupModal = ({ - {showRewardLabel && ( - - - - - {intl.formatMessage({ - defaultMessage: 'OGN Rewards Will be Collected', - })} - - - {intl.formatMessage( - { - defaultMessage: - 'You have accrued {reward} OGN in staking rewards. This OGN will be transferred to your wallet immediately when you extend your stake.', - }, - { - reward: formatAmount( - info?.xOgnRewards, - tokens.mainnet.OGN.decimals, - undefined, - { notation: 'compact', maximumSignificantDigits: 4 }, - ), - }, - )} - - - - )} + + + = { + titleProps: { color: 'text.secondary', fontWeight: 'medium' }, + cardProps: { + sx: { backgroundColor: 'background.highlight' }, + }, +}; + export type ExtendButtonProps = { lockup: Lockup; } & ConnectedButtonProps; -export const ExtendButton = ({ lockup, ...rest }: ExtendButtonProps) => { +export const ExtendAddButton = ({ lockup, ...rest }: ExtendButtonProps) => { const [open, setOpen] = useState(false); return ( @@ -512,14 +585,16 @@ export const ExtendButton = ({ lockup, ...rest }: ExtendButtonProps) => { rest?.onClick?.(e); }} /> - { - setOpen(false); - }} - /> + {open && ( + { + setOpen(false); + }} + /> + )} ); }; diff --git a/libs/defi/ogn/src/staking/components/LockupsTable.tsx b/libs/defi/ogn/src/staking/components/LockupsTable.tsx index 4ff0104ae..d302e7129 100644 --- a/libs/defi/ogn/src/staking/components/LockupsTable.tsx +++ b/libs/defi/ogn/src/staking/components/LockupsTable.tsx @@ -26,15 +26,14 @@ import { getPaginationRowModel, useReactTable, } from '@tanstack/react-table'; -import { differenceInDays, formatDistanceToNowStrict, isPast } from 'date-fns'; import { useIntl } from 'react-intl'; import { formatUnits } from 'viem'; import { useAccount } from 'wagmi'; import { useOgnLockupsQuery } from '../queries.generated'; import { useLockupPolling } from '../state'; -import { AddButton } from './AddToLockupModal'; -import { ExtendButton } from './ExtendLockupModal'; +import { formatTimeRemaining } from '../utils'; +import { ExtendAddButton } from './ExtendAddLockupModal'; import { UnstakeLockupButton } from './UnstakeLockupModal'; import type { Lockup } from '../types'; @@ -85,18 +84,7 @@ export const LockupsTable = () => { columnHelper.display({ id: 'timeRemaining', header: intl.formatMessage({ defaultMessage: 'Time Remaining' }), - cell: (info) => - isPast(new Date(info.row.original.end)) - ? '-' - : differenceInDays(new Date(info.row.original.end), new Date()) > 31 - ? formatDistanceToNowStrict(new Date(info.row.original.end), { - unit: 'month', - roundingMethod: 'floor', - }) - : formatDistanceToNowStrict(new Date(info.row.original.end), { - unit: 'day', - roundingMethod: 'ceil', - }), + cell: (info) => formatTimeRemaining(info.row.original.end), }), ...(isSm ? [] @@ -160,9 +148,6 @@ export const LockupsTable = () => { ); } - const addDisabled = - differenceInDays(new Date(info.row.original.end), new Date()) < 30; - return ( { alignItems="stretch" justifyContent="flex-end" > - - {intl.formatMessage({ defaultMessage: 'Extend' })} - + {intl.formatMessage({ defaultMessage: 'Extend/Add' })} + { > {intl.formatMessage({ defaultMessage: 'Unlock' })} - - {intl.formatMessage({ defaultMessage: 'Add' })} -