diff --git a/.yarn/cache/@synthetixio-contracts-npm-1.2.3-6fb48cb664-db21ac9403.zip b/.yarn/cache/@synthetixio-contracts-npm-1.2.3-6fb48cb664-db21ac9403.zip new file mode 100644 index 000000000..fad1b8d0a Binary files /dev/null and b/.yarn/cache/@synthetixio-contracts-npm-1.2.3-6fb48cb664-db21ac9403.zip differ diff --git a/contracts/importers/importLegacyMarket.js b/contracts/importers/importLegacyMarket.js new file mode 100644 index 000000000..5eb14226d --- /dev/null +++ b/contracts/importers/importLegacyMarket.js @@ -0,0 +1,32 @@ +export async function importLegacyMarket(chainId, preset) { + if (!preset) { + throw new Error(`Missing preset`); + } + const deployment = `${Number(chainId).toFixed(0)}-${preset}`; + switch (deployment) { + case '1-main': { + const [{ default: meta }, { default: abi }] = await Promise.all([ + import('@synthetixio/v3-contracts/1-main/meta.json'), + import('@synthetixio/v3-contracts/1-main/LegacyMarketProxy.readable.json'), + ]); + return { address: meta.contracts.LegacyMarketProxy, abi }; + } + case '11155111-main': { + const [{ default: meta }, { default: abi }] = await Promise.all([ + import('@synthetixio/v3-contracts/11155111-main/meta.json'), + import('@synthetixio/v3-contracts/11155111-main/LegacyMarketProxy.readable.json'), + ]); + return { address: meta.contracts.LegacyMarketProxy, abi }; + } + /*case '10-main': { + const [{ default: meta }, { default: abi }] = await Promise.all([ + import('@synthetixio/v3-contracts/10-main/meta.json'), + import('@synthetixio/v3-contracts/10-main/LegacyMarketProxy.readable.json'), + ]); + return { address: meta.contracts.LegacyMarketProxy, abi }; + }*/ + default: { + throw new Error(`Unsupported deployment ${deployment} for Extras`); + } + } +} diff --git a/contracts/importers/importPythERC7412Wrapper.js b/contracts/importers/importPythERC7412Wrapper.js index 443a7ddfd..71ae399f8 100644 --- a/contracts/importers/importPythERC7412Wrapper.js +++ b/contracts/importers/importPythERC7412Wrapper.js @@ -18,6 +18,13 @@ export async function importPythERC7412Wrapper(chainId, preset) { ]); return { address: meta.contracts.PythERC7412Wrapper, abi }; } + case '11155111-main': { + const [{ default: meta }, { default: abi }] = await Promise.all([ + import('@synthetixio/v3-contracts/11155111-main/meta.json'), + import('@synthetixio/v3-contracts/11155111-main/PythERC7412Wrapper.readable.json'), + ]); + return { address: meta.contracts.PythERC7412Wrapper, abi }; + } case '42161-main': { const [{ default: meta }, { default: abi }] = await Promise.all([ import('@synthetixio/v3-contracts/42161-main/meta.json'), diff --git a/contracts/importers/importSpotMarketProxy.js b/contracts/importers/importSpotMarketProxy.js index 03f615e30..61caa47e6 100644 --- a/contracts/importers/importSpotMarketProxy.js +++ b/contracts/importers/importSpotMarketProxy.js @@ -11,13 +11,13 @@ export async function importSpotMarketProxy(chainId, preset) { ]); return { address: meta.contracts.SpotMarketProxy, abi }; } - // case '11155111-main': { - // const [{default: meta}, {default: abi}] = await Promise.all([ - // import('@synthetixio/v3-contracts/11155111-main/meta.json'), - // import('@synthetixio/v3-contracts/11155111-main/SpotMarketProxy.readable.json'), - // ]); - // return { address: meta.contracts.SpotMarketProxy, abi }; - // } + case '11155111-main': { + const [{ default: meta }, { default: abi }] = await Promise.all([ + import('@synthetixio/v3-contracts/11155111-main/meta.json'), + import('@synthetixio/v3-contracts/11155111-main/SpotMarketProxy.readable.json'), + ]); + return { address: meta.contracts.SpotMarketProxy, abi }; + } case '10-main': { const [{ default: meta }, { default: abi }] = await Promise.all([ import('@synthetixio/v3-contracts/10-main/meta.json'), diff --git a/contracts/importers/importV2x.js b/contracts/importers/importV2x.js new file mode 100644 index 000000000..e67d0d5d6 --- /dev/null +++ b/contracts/importers/importV2x.js @@ -0,0 +1,32 @@ +export async function importV2x(chainId, preset) { + if (!preset) { + throw new Error(`Missing preset`); + } + const deployment = `${Number(chainId).toFixed(0)}-${preset}`; + switch (deployment) { + case '1-main': { + const [{ default: meta }, { default: abi }] = await Promise.all([ + import('@synthetixio/v3-contracts/1-main/meta.json'), + import('@synthetixio/v3-contracts/1-main/V2x.readable.json'), + ]); + return { address: meta.contracts.V2x, abi }; + } + case '11155111-main': { + const [{ default: meta }, { default: abi }] = await Promise.all([ + import('@synthetixio/v3-contracts/11155111-main/meta.json'), + import('@synthetixio/v3-contracts/11155111-main/V2x.readable.json'), + ]); + return { address: meta.contracts.V2x, abi }; + } + /*case '10-main': { + const [{ default: meta }, { default: abi }] = await Promise.all([ + import('@synthetixio/v3-contracts/10-main/meta.json'), + import('@synthetixio/v3-contracts/10-main/V2x.readable.json'), + ]); + return { address: meta.contracts.V2x, abi }; + }*/ + default: { + throw new Error(`Unsupported deployment ${deployment} for V2x`); + } + } +} diff --git a/contracts/index.js b/contracts/index.js index a3d1195df..c084cc4a7 100644 --- a/contracts/index.js +++ b/contracts/index.js @@ -4,6 +4,8 @@ export * from './importers/importCoreProxy'; export * from './importers/importAccountProxy'; export * from './importers/importUSDProxy'; export * from './importers/importOracleManagerProxy'; +export * from './importers/importLegacyMarket'; +export * from './importers/importV2x'; export * from './importers/importMulticall3'; export * from './importers/importRewardDistributor'; export * from './importers/importSpotMarketProxy'; diff --git a/liquidity/components/Multistep/Multistep.tsx b/liquidity/components/Multistep/Multistep.tsx index bc175f01c..506ee91d0 100644 --- a/liquidity/components/Multistep/Multistep.tsx +++ b/liquidity/components/Multistep/Multistep.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren, ReactNode } from 'react'; -import { Box, Checkbox, CheckboxProps, Flex, Text } from '@chakra-ui/react'; +import { Box, Checkbox, CheckboxProps, Flex, FlexProps, Text } from '@chakra-ui/react'; import { Step } from './Step'; import { statusColor } from './statusColor'; import { MultistepStatus } from './MultistepStatus'; @@ -15,6 +15,15 @@ function StepCheckbox({ children, ...props }: PropsWithChildren) ); } +interface Props extends Omit { + step: number; + title: string | ReactNode; + subtitle?: string | ReactNode; + checkboxLabel?: string; + checkboxProps?: CheckboxProps; + status: MultistepStatus; + children?: ReactNode | undefined; +} export function Multistep({ step, @@ -24,14 +33,8 @@ export function Multistep({ checkboxProps, status, children, -}: PropsWithChildren<{ - step: number; - title: string | ReactNode; - subtitle?: string | ReactNode; - checkboxLabel?: string; - checkboxProps?: CheckboxProps; - status: MultistepStatus; -}>) { + ...props +}: Props) { return ( {step} diff --git a/liquidity/components/NumberInput/NumberInput.tsx b/liquidity/components/NumberInput/NumberInput.tsx index 83516b573..34388c2ab 100644 --- a/liquidity/components/NumberInput/NumberInput.tsx +++ b/liquidity/components/NumberInput/NumberInput.tsx @@ -40,7 +40,7 @@ export function NumberInput({ const onInputChange = useCallback( (e: ChangeEvent) => { // Define max length here - if (e.target.value.length > 15) return; + if (e.target.value.length > 24) return; let _value = e.target.value; diff --git a/liquidity/components/TermsModal/TermsModal.tsx b/liquidity/components/TermsModal/TermsModal.tsx index 493117849..8fc7b774b 100644 --- a/liquidity/components/TermsModal/TermsModal.tsx +++ b/liquidity/components/TermsModal/TermsModal.tsx @@ -26,7 +26,7 @@ export const TermsModal = ({ defaultOpen = true }: TermsModalProps) => { const onSubmit = () => { if (enabled) { - sessionStorage.setItem(SESSION_STORAGE_KEYS.TERMS_CONDITIONS_ACCEPTED, JSON.stringify(true)); + localStorage.setItem(SESSION_STORAGE_KEYS.TERMS_CONDITIONS_ACCEPTED, JSON.stringify(true)); setOpen(false); } }; diff --git a/liquidity/components/WithdrawIncrease/WithdrawIncrease.tsx b/liquidity/components/WithdrawIncrease/WithdrawIncrease.tsx index f003c06e1..5ac0ab1a8 100644 --- a/liquidity/components/WithdrawIncrease/WithdrawIncrease.tsx +++ b/liquidity/components/WithdrawIncrease/WithdrawIncrease.tsx @@ -2,7 +2,7 @@ import { Alert, AlertIcon, Text } from '@chakra-ui/react'; export function WithdrawIncrease() { return ( - + This action will reset the withdrawal waiting period to 24 hours diff --git a/liquidity/lib/constants/constants.ts b/liquidity/lib/constants/constants.ts index 147393c93..454c1b1ee 100644 --- a/liquidity/lib/constants/constants.ts +++ b/liquidity/lib/constants/constants.ts @@ -28,6 +28,8 @@ export const getSubgraphUrl = (networkName = 'optimism-mainnet') => { export const getAprUrl = (networkId = 8453) => { switch (networkId) { + case 1: + return 'https://api.synthetix.io/v3/mainnet/sc-pool-apy-all'; case 8453: return 'https://api.synthetix.io/v3/base/sc-pool-apy-all'; case 42161: diff --git a/liquidity/lib/useApr/useApr.ts b/liquidity/lib/useApr/useApr.ts index 8d665a8ce..6be1dd026 100644 --- a/liquidity/lib/useApr/useApr.ts +++ b/liquidity/lib/useApr/useApr.ts @@ -1,5 +1,5 @@ import { getAprUrl } from '@snx-v3/constants'; -import { ARBITRUM, BASE_ANDROMEDA, BASE_SEPOLIA, Network, useNetwork } from '@snx-v3/useBlockchain'; +import { ARBITRUM, BASE_ANDROMEDA, MAINNET, Network, useNetwork } from '@snx-v3/useBlockchain'; import { useQuery } from '@tanstack/react-query'; export function useApr(customNetwork?: Network) { @@ -20,12 +20,14 @@ export function useApr(customNetwork?: Network) { }); } -const supportedAprNetworks = [BASE_ANDROMEDA.id, BASE_SEPOLIA.id, ARBITRUM.id]; +const supportedAprNetworks = [BASE_ANDROMEDA.id, ARBITRUM.id, MAINNET.id]; export async function fetchApr(networkId?: number) { try { const isSupported = networkId && supportedAprNetworks.includes(networkId); - if (!isSupported) throw new Error('Apr endpoint not supported for this network'); + if (!isSupported) { + throw new Error('Apr endpoint not supported for this network'); + } const response = await fetch(getAprUrl(networkId)); diff --git a/liquidity/lib/useBlockchain/useBlockchain.tsx b/liquidity/lib/useBlockchain/useBlockchain.tsx index a8ad76367..c213dbd2f 100644 --- a/liquidity/lib/useBlockchain/useBlockchain.tsx +++ b/liquidity/lib/useBlockchain/useBlockchain.tsx @@ -287,7 +287,10 @@ export const appMetadata = { }; export function useProviderForChain(network?: Network | null) { - return network ? new ethers.providers.JsonRpcProvider(network.rpcUrl()) : undefined; + return useMemo( + () => (network ? new ethers.providers.JsonRpcProvider(network.rpcUrl()) : undefined), + [network] + ); } export function useDefaultProvider() { @@ -301,23 +304,25 @@ export function useWallet() { const connect = useCallback(conn, [conn]); const disconnect = useCallback(disconn, [disconn]); - if (!wallet) { + return useMemo(() => { + if (!wallet) { + return { + activeWallet: null, + walletsInfo: null, + connect, + disconnect, + }; + } + + const activeWallet = wallet?.accounts[0]; + return { - activeWallet: null, - walletsInfo: null, + activeWallet: activeWallet, + walletsInfo: wallet, connect, disconnect, }; - } - - const activeWallet = wallet?.accounts[0]; - - return { - activeWallet: activeWallet, - walletsInfo: wallet, - connect, - disconnect, - }; + }, [connect, disconnect, wallet]); } export function useGetNetwork(chainId: string) { @@ -362,23 +367,27 @@ export function useIsConnected(): boolean { export function useSigner() { const [{ wallet }] = useConnectWallet(); - if (!wallet) { - return null; - } + return useMemo(() => { + if (!wallet) { + return null; + } - const provider = new ethers.providers.Web3Provider(wallet.provider, 'any'); + const provider = new ethers.providers.Web3Provider(wallet.provider, 'any'); - return provider.getSigner(); + return provider.getSigner(); + }, [wallet]); } export function useProvider() { const [{ wallet }] = useConnectWallet(); - if (!wallet) { - return null; - } + return useMemo(() => { + if (!wallet) { + return null; + } - const provider = new ethers.providers.Web3Provider(wallet.provider, 'any'); + const provider = new ethers.providers.Web3Provider(wallet.provider, 'any'); - return provider; + return provider; + }, [wallet]); } diff --git a/liquidity/lib/useCollateralPriceUpdates/useCollateralPriceUpdates.ts b/liquidity/lib/useCollateralPriceUpdates/useCollateralPriceUpdates.ts index 492167fc4..6d3cba3a6 100644 --- a/liquidity/lib/useCollateralPriceUpdates/useCollateralPriceUpdates.ts +++ b/liquidity/lib/useCollateralPriceUpdates/useCollateralPriceUpdates.ts @@ -75,8 +75,6 @@ const getPriceUpdates = async ( stalenessTolerance: number, network: Network | null ) => { - // network.id - const signedOffchainData = await priceService.getPriceFeedsUpdateData(priceIds); const updateType = 1; const data = ethers.utils.defaultAbiCoder.encode( @@ -108,6 +106,9 @@ export const useAllCollateralPriceUpdates = (customNetwork?: Network) => { const stalenessTolerance = 1; const pythFeedIds = (await getPythFeedIds(targetNetwork)) as string[]; + if (!pythFeedIds.length) { + return null; + } const tx = await getPriceUpdates(pythFeedIds, stalenessTolerance, targetNetwork); return { diff --git a/liquidity/lib/useCollateralTypes/useCollateralTypes.ts b/liquidity/lib/useCollateralTypes/useCollateralTypes.ts index 7c7e11237..6cec6ded8 100644 --- a/liquidity/lib/useCollateralTypes/useCollateralTypes.ts +++ b/liquidity/lib/useCollateralTypes/useCollateralTypes.ts @@ -43,7 +43,7 @@ async function loadCollateralTypes(chainId: number, preset: string): Promise ({ ...config, - displaySymbol: config.symbol === 'WETH' ? 'ETH' : config.symbol, + displaySymbol: config.symbol, })); } diff --git a/liquidity/lib/useLegacyMarket/index.ts b/liquidity/lib/useLegacyMarket/index.ts new file mode 100644 index 000000000..fee19472f --- /dev/null +++ b/liquidity/lib/useLegacyMarket/index.ts @@ -0,0 +1 @@ +export * from './useLegacyMarket'; diff --git a/liquidity/lib/useLegacyMarket/package.json b/liquidity/lib/useLegacyMarket/package.json new file mode 100644 index 000000000..656e9cf9b --- /dev/null +++ b/liquidity/lib/useLegacyMarket/package.json @@ -0,0 +1,13 @@ +{ + "name": "@snx-v3/useLegacyMarket", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@ethersproject/contracts": "^5.7.0", + "@snx-v3/contracts": "workspace:*", + "@snx-v3/useBlockchain": "workspace:*", + "@tanstack/react-query": "^5.8.3", + "react": "^18.2.0" + } +} diff --git a/liquidity/lib/useLegacyMarket/useLegacyMarket.ts b/liquidity/lib/useLegacyMarket/useLegacyMarket.ts new file mode 100644 index 000000000..ea0621c66 --- /dev/null +++ b/liquidity/lib/useLegacyMarket/useLegacyMarket.ts @@ -0,0 +1,45 @@ +import { Contract } from '@ethersproject/contracts'; +import { useQuery } from '@tanstack/react-query'; +import { + Network, + useNetwork, + useProvider, + useProviderForChain, + useSigner, +} from '@snx-v3/useBlockchain'; +import { importLegacyMarket } from '@snx-v3/contracts'; + +export function useLegacyMarket(customNetwork?: Network | null) { + const providerForChain = useProviderForChain(customNetwork); + const { network } = useNetwork(); + const provider = useProvider(); + const signer = useSigner(); + const targetNetwork = customNetwork || network; + + const withSigner = Boolean(signer); + + return useQuery({ + queryKey: [`${targetNetwork?.id}-${targetNetwork?.preset}`, 'LegacyMarket', { withSigner }], + queryFn: async function () { + if (providerForChain && customNetwork) { + const { address: lmAddress, abi: lmAbi } = await importLegacyMarket( + customNetwork.id, + customNetwork.preset + ); + return new Contract(lmAddress, lmAbi, providerForChain); + } + + const signerOrProvider = signer || provider; + if (!signerOrProvider || !network) throw new Error('Should be disabled CP'); + + const { address: lmAddress, abi: lmAbi } = await importLegacyMarket( + network?.id, + network?.preset + ); + + return new Contract(lmAddress, lmAbi, signerOrProvider); + }, + enabled: Boolean(signer || provider || providerForChain), + staleTime: Infinity, + }); +} diff --git a/liquidity/lib/useLiquidityPosition/useLiquidityPosition.ts b/liquidity/lib/useLiquidityPosition/useLiquidityPosition.ts index c5e7ab391..b8746a79a 100644 --- a/liquidity/lib/useLiquidityPosition/useLiquidityPosition.ts +++ b/liquidity/lib/useLiquidityPosition/useLiquidityPosition.ts @@ -104,7 +104,7 @@ export const useLiquidityPosition = ({ { priceUpdateTxHash }, ], staleTime: 60000 * 5, - enabled: !!tokenAddress, + enabled: !!tokenAddress && !!accountId, queryFn: async () => { if ( !(CoreProxy && accountId && poolId && tokenAddress && systemToken && network && provider) diff --git a/liquidity/lib/useLiquidityPositions/useLiquidityPositions.ts b/liquidity/lib/useLiquidityPositions/useLiquidityPositions.ts index 1f44bbc9f..65074e551 100644 --- a/liquidity/lib/useLiquidityPositions/useLiquidityPositions.ts +++ b/liquidity/lib/useLiquidityPositions/useLiquidityPositions.ts @@ -61,6 +61,7 @@ export const useLiquidityPositions = ({ accountId }: { accountId?: string }) => }, ], staleTime: 60000 * 5, + enabled: !!accountId, queryFn: async () => { if (!pools || !collateralTypes || !CoreProxy || !accountId || !network || !provider) { throw Error('Query should not be enabled'); diff --git a/liquidity/lib/useMigrate/index.ts b/liquidity/lib/useMigrate/index.ts new file mode 100644 index 000000000..5c2229ea6 --- /dev/null +++ b/liquidity/lib/useMigrate/index.ts @@ -0,0 +1 @@ +export * from './useMigrate'; diff --git a/liquidity/lib/useMigrate/package.json b/liquidity/lib/useMigrate/package.json new file mode 100644 index 000000000..8ad17525d --- /dev/null +++ b/liquidity/lib/useMigrate/package.json @@ -0,0 +1,16 @@ +{ + "name": "@snx-v3/useMigrate", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@snx-v3/useBlockchain": "workspace:*", + "@snx-v3/useGasOptions": "workspace:*", + "@snx-v3/useGasPrice": "workspace:*", + "@snx-v3/useGasSpeed": "workspace:*", + "@snx-v3/useLegacyMarket": "workspace:*", + "@synthetixio/wei": "^2.74.4", + "@tanstack/react-query": "^5.8.3", + "react": "^18.2.0" + } +} diff --git a/liquidity/lib/useMigrate/useMigrate.ts b/liquidity/lib/useMigrate/useMigrate.ts new file mode 100644 index 000000000..b0c0a9b6a --- /dev/null +++ b/liquidity/lib/useMigrate/useMigrate.ts @@ -0,0 +1,119 @@ +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useDefaultProvider, useNetwork, useSigner } from '@snx-v3/useBlockchain'; +import { useCallback, useMemo, useState } from 'react'; +import { getGasPrice } from '@snx-v3/useGasPrice'; +import { formatGasPriceForTransaction } from '@snx-v3/useGasOptions'; +import { ZEROWEI } from '../../ui/src/utils/constants'; +import { wei } from '@synthetixio/wei'; +import { useGasSpeed } from '@snx-v3/useGasSpeed'; +import { parseTxError } from '../parser'; +import { useLegacyMarket } from '@snx-v3/useLegacyMarket'; + +export function useMigrate() { + const [isLoading, setIsLoading] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const { network } = useNetwork(); + const provider = useDefaultProvider(); + const signer = useSigner(); + const { data: legacyMarket } = useLegacyMarket(); + const { gasSpeed } = useGasSpeed(); + const queryClient = useQueryClient(); + + const accountId = useMemo(() => Math.floor(Math.random() * 1000000000000).toString(), []); + + const { data: transaction } = useQuery({ + queryKey: [`${network?.id}-${network?.preset}`, 'MigrateTxn'], + queryFn: async function () { + if (!legacyMarket || !signer) { + return; + } + const signerAddress = await signer!.getAddress(); + const populateTransaction = await legacyMarket.populateTransaction.migrate(accountId, { + from: signerAddress, + }); + try { + const [gasLimit, feeData] = await Promise.all([ + await provider?.estimateGas(populateTransaction), + await provider?.getFeeData(), + ]); + + const gasPrices = await getGasPrice({ provider: signer!.provider }); + const gasOptionsForTransaction = formatGasPriceForTransaction({ + gasLimit: wei(gasLimit || ZEROWEI).toBN(), + gasPrices, + gasSpeed, + }); + + return { + ...populateTransaction, + gasLimit: gasOptionsForTransaction.gasLimit, + gasPrice: feeData?.gasPrice, + }; + } catch (error) { + const parsedError = parseTxError(error); + const errorResult = legacyMarket.interface.parseError(parsedError as string); + console.error('error:', errorResult); + + return null; + } + }, + enabled: Boolean(signer && !!legacyMarket), + staleTime: 60 * 1000, + }); + + const migrate = useCallback(async () => { + try { + if (!legacyMarket || !transaction) { + return; + } + setIsLoading(true); + setIsSuccess(false); + const gasPrices = await getGasPrice({ provider: signer!.provider }); + const signerAddress = await signer!.getAddress(); + + const populateTransaction = await legacyMarket.populateTransaction.migrate(accountId, { + from: signerAddress, + }); + const gasLimit = await provider?.estimateGas(populateTransaction); + + const gasOptionsForTransaction = formatGasPriceForTransaction({ + gasLimit: wei(gasLimit || ZEROWEI).toBN(), + gasPrices, + gasSpeed, + }); + + const txn = await legacyMarket + .connect(signer!) + .migrate(accountId, { ...gasOptionsForTransaction }); + await txn.wait(); + + setIsLoading(false); + setIsSuccess(true); + + queryClient.invalidateQueries({ + queryKey: [`${network?.id}-${network?.preset}`, 'Accounts'], + }); + } catch (error) { + setIsLoading(false); + throw error; + } + }, [ + accountId, + gasSpeed, + legacyMarket, + network?.id, + network?.preset, + provider, + queryClient, + signer, + transaction, + ]); + + return { + migrate, + transaction, + isLoading, + isSuccess, + accountId, + }; +} diff --git a/liquidity/lib/useMigrateUSD/index.ts b/liquidity/lib/useMigrateUSD/index.ts new file mode 100644 index 000000000..351e3f563 --- /dev/null +++ b/liquidity/lib/useMigrateUSD/index.ts @@ -0,0 +1 @@ +export * from './useMigrateUSD'; diff --git a/liquidity/lib/useMigrateUSD/package.json b/liquidity/lib/useMigrateUSD/package.json new file mode 100644 index 000000000..6235f40c3 --- /dev/null +++ b/liquidity/lib/useMigrateUSD/package.json @@ -0,0 +1,16 @@ +{ + "name": "@snx-v3/useMigrateUSD", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@snx-v3/useBlockchain": "workspace:*", + "@snx-v3/useGasOptions": "workspace:*", + "@snx-v3/useGasPrice": "workspace:*", + "@snx-v3/useGasSpeed": "workspace:*", + "@snx-v3/useLegacyMarket": "workspace:*", + "@synthetixio/wei": "^2.74.4", + "@tanstack/react-query": "^5.8.3", + "react": "^18.2.0" + } +} diff --git a/liquidity/lib/useMigrateUSD/useMigrateUSD.ts b/liquidity/lib/useMigrateUSD/useMigrateUSD.ts new file mode 100644 index 000000000..9664147ff --- /dev/null +++ b/liquidity/lib/useMigrateUSD/useMigrateUSD.ts @@ -0,0 +1,64 @@ +import { useDefaultProvider, useNetwork, useSigner } from '@snx-v3/useBlockchain'; +import { useLegacyMarket } from '@snx-v3/useLegacyMarket'; +import { useCallback, useState } from 'react'; +import { getGasPrice } from '@snx-v3/useGasPrice'; +import { formatGasPriceForTransaction } from '@snx-v3/useGasOptions'; +import { ZEROWEI } from '../../ui/src/utils/constants'; +import Wei, { wei } from '@synthetixio/wei'; +import { useGasSpeed } from '@snx-v3/useGasSpeed'; +import { parseTxError } from '../parser'; +import { useQueryClient } from '@tanstack/react-query'; + +export function useMigrateUSD({ amount }: { amount: Wei }) { + const [isLoading, setIsLoading] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const signer = useSigner(); + const { data: legacyMarket } = useLegacyMarket(); + const { gasSpeed } = useGasSpeed(); + const provider = useDefaultProvider(); + const queryClient = useQueryClient(); + const { network } = useNetwork(); + + const migrate = useCallback(async () => { + try { + if (!legacyMarket || !signer) { + return; + } + setIsLoading(true); + setIsSuccess(false); + const gasPrices = await getGasPrice({ provider: signer!.provider }); + + const transaction = await legacyMarket.populateTransaction.convertUSD(amount.toBN()); + const gasLimit = await provider?.estimateGas(transaction); + + const gasOptionsForTransaction = formatGasPriceForTransaction({ + gasLimit: wei(gasLimit || ZEROWEI).toBN(), + gasPrices, + gasSpeed, + }); + + const txn = await signer.sendTransaction({ ...transaction, ...gasOptionsForTransaction }); + + await txn.wait(); + + setIsLoading(false); + setIsSuccess(true); + + queryClient.invalidateQueries({ + queryKey: [`${network?.id}-${network?.preset}`, 'TokenBalance'], + }); + } catch (error) { + const parsedError = parseTxError(error); + const errorResult = legacyMarket?.interface.parseError(parsedError as string); + console.error('error:', errorResult); + setIsLoading(false); + throw error; + } + }, [amount, gasSpeed, legacyMarket, network?.id, network?.preset, provider, queryClient, signer]); + + return { + migrate, + isLoading, + isSuccess, + }; +} diff --git a/liquidity/lib/useOraclePrice/useOraclePrice.ts b/liquidity/lib/useOraclePrice/useOraclePrice.ts index cf1f33bc7..ccab55680 100644 --- a/liquidity/lib/useOraclePrice/useOraclePrice.ts +++ b/liquidity/lib/useOraclePrice/useOraclePrice.ts @@ -1,6 +1,6 @@ import { useQuery } from '@tanstack/react-query'; import { Network, useNetwork, useProviderForChain } from '@snx-v3/useBlockchain'; -import { erc7412Call } from '@snx-v3/withERC7412'; +import { erc7412Call, getDefaultFromAddress } from '@snx-v3/withERC7412'; import { importOracleManagerProxy } from '@snx-v3/contracts'; import { Contract } from 'ethers'; import { Wei } from '@synthetixio/wei'; @@ -28,7 +28,7 @@ export function useOraclePrice(nodeId?: string, customNetwork?: Network) { const price = [await OracleManagerProxy.populateTransaction.process(nodeId)]; - price[0].from = '0x4200000000000000000000000000000000000006'; + price[0].from = getDefaultFromAddress(targetNetwork?.name || ''); return await erc7412Call( targetNetwork, diff --git a/liquidity/lib/usePoolsList/usePoolsList.ts b/liquidity/lib/usePoolsList/usePoolsList.ts index 3761afb8c..6b7973e42 100644 --- a/liquidity/lib/usePoolsList/usePoolsList.ts +++ b/liquidity/lib/usePoolsList/usePoolsList.ts @@ -1,5 +1,5 @@ import { getSubgraphUrl } from '@snx-v3/constants'; -import { ARBITRUM, BASE_ANDROMEDA, NETWORKS } from '@snx-v3/useBlockchain'; +import { ARBITRUM, BASE_ANDROMEDA, MAINNET, NETWORKS } from '@snx-v3/useBlockchain'; import { compactInteger } from 'humanize-plus'; import { fetchApr } from '@snx-v3/useApr'; import { useQuery } from '@tanstack/react-query'; @@ -41,8 +41,7 @@ export function usePool(networkId: number, poolId: string) { }; } -// TODO: Add 1 and 10 to support Mainnet and Optimism -const supportedNetworks = [BASE_ANDROMEDA.id, ARBITRUM.id]; +const supportedNetworks = [MAINNET.id, BASE_ANDROMEDA.id, ARBITRUM.id]; async function fetchTorosPool() { return fetch('https://api-v2.dhedge.org/graphql', { diff --git a/liquidity/lib/useRates/index.ts b/liquidity/lib/useRates/index.ts new file mode 100644 index 000000000..3ac0c3b4d --- /dev/null +++ b/liquidity/lib/useRates/index.ts @@ -0,0 +1 @@ +export * from './useRates'; diff --git a/liquidity/lib/useRates/package.json b/liquidity/lib/useRates/package.json new file mode 100644 index 000000000..37605a4e5 --- /dev/null +++ b/liquidity/lib/useRates/package.json @@ -0,0 +1,14 @@ +{ + "name": "@snx-v3/useRates", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@snx-v3/useBlockchain": "workspace:*", + "@synthetixio/contracts": "^1.2.3", + "@synthetixio/wei": "^2.74.4", + "@tanstack/react-query": "^5.8.3", + "ethers": "^5.7.2", + "react": "^18.2.0" + } +} diff --git a/liquidity/lib/useRates/useRates.ts b/liquidity/lib/useRates/useRates.ts new file mode 100644 index 000000000..423310468 --- /dev/null +++ b/liquidity/lib/useRates/useRates.ts @@ -0,0 +1,30 @@ +import { useQuery } from '@tanstack/react-query'; +import { MAINNET, useProviderForChain } from '@snx-v3/useBlockchain'; +import { Contract } from 'ethers'; +import { formatBytes32String } from 'ethers/lib/utils'; +import { wei } from '@synthetixio/wei'; + +export function useRates() { + const mainnetProvider = useProviderForChain(MAINNET); + + return useQuery({ + queryKey: ['rates-mainnet'], + queryFn: async function () { + const { address, abi } = await import( + '@synthetixio/contracts/build/mainnet/deployment/ExchangeRates' + ); + const ExchangeRates = new Contract(address, abi, mainnetProvider); + + const result = await ExchangeRates.ratesForCurrencies([ + formatBytes32String('SNX'), + formatBytes32String('ETH'), + ]); + return { + snx: wei(result[0] || 0), + eth: wei(result[1] || 0), + }; + }, + staleTime: Infinity, + refetchInterval: 60000, + }); +} diff --git a/liquidity/lib/useSpotMarketProxy/useSpotMarketProxy.ts b/liquidity/lib/useSpotMarketProxy/useSpotMarketProxy.ts index 023724139..93efcb1d4 100644 --- a/liquidity/lib/useSpotMarketProxy/useSpotMarketProxy.ts +++ b/liquidity/lib/useSpotMarketProxy/useSpotMarketProxy.ts @@ -47,7 +47,7 @@ export function useSpotMarketProxy(customNetwork?: Network) { return new Contract(address, abi, signerOrProvider); }, - enabled: Boolean(signerOrProvider), + enabled: Boolean(signerOrProvider && ![1, 10].includes(targetNetwork?.id || 0)), staleTime: Infinity, }); } diff --git a/liquidity/lib/useTeleport/useTeleport.tsx b/liquidity/lib/useTeleport/useTeleport.tsx index db910062e..691ecb9bc 100644 --- a/liquidity/lib/useTeleport/useTeleport.tsx +++ b/liquidity/lib/useTeleport/useTeleport.tsx @@ -32,6 +32,7 @@ export const estimateTeleport = async ({ }); return fee; }; + export const useTeleport = ({ toNetworkId, amount, diff --git a/liquidity/lib/useTokenBalance/useTokenBalance.ts b/liquidity/lib/useTokenBalance/useTokenBalance.ts index f153c35cd..3f3b89968 100644 --- a/liquidity/lib/useTokenBalance/useTokenBalance.ts +++ b/liquidity/lib/useTokenBalance/useTokenBalance.ts @@ -1,9 +1,16 @@ import { assertAddressType } from '@snx-v3/assertAddressType'; import { wei } from '@synthetixio/wei'; import { useQuery } from '@tanstack/react-query'; -import { Network, useDefaultProvider, useNetwork, useWallet } from '@snx-v3/useBlockchain'; +import { + Network, + useDefaultProvider, + useNetwork, + useProviderForChain, + useWallet, +} from '@snx-v3/useBlockchain'; import { ethers, providers } from 'ethers'; import { ZodBigNumber } from '@snx-v3/zod'; +import { ZEROWEI } from '../../ui/src/utils/constants'; export const BalanceSchema = ZodBigNumber.transform((x) => wei(x)); @@ -14,10 +21,10 @@ export const abi = [ export const useTokenBalance = (address?: string, customNetwork?: Network) => { const { activeWallet } = useWallet(); - const provider = useDefaultProvider(); const { network } = useNetwork(); const targetNetwork = customNetwork || network; + const provider = useProviderForChain(targetNetwork); const tokenAddress = assertAddressType(address) ? address : undefined; return useQuery({ @@ -61,11 +68,14 @@ export const useTokenBalances = (addresses: string[], customNetwork?: Network) = }); }; -async function fetchTokenBalance( +export async function fetchTokenBalance( tokenAddress: string, walletAddress: string, provider: providers.JsonRpcProvider ) { + if (!tokenAddress) { + return ZEROWEI; + } const contract = new ethers.Contract(tokenAddress, abi, provider); const balance = wei(await contract.balanceOf(walletAddress), await contract.decimals()); return balance; diff --git a/liquidity/lib/useTokenBalance/useTokenBalanceForChain.ts b/liquidity/lib/useTokenBalance/useTokenBalanceForChain.ts index 18ae21177..d69f272d2 100644 --- a/liquidity/lib/useTokenBalance/useTokenBalanceForChain.ts +++ b/liquidity/lib/useTokenBalance/useTokenBalanceForChain.ts @@ -1,9 +1,7 @@ import { assertAddressType } from '@snx-v3/assertAddressType'; import { useQuery } from '@tanstack/react-query'; import { Network, useProviderForChain, useWallet } from '@snx-v3/useBlockchain'; -import { Contract } from 'ethers'; - -import { abi, BalanceSchema } from './useTokenBalance'; +import { fetchTokenBalance } from './useTokenBalance'; export const useTokenBalanceForChain = (address?: string, network?: Network) => { const { activeWallet } = useWallet(); @@ -19,8 +17,7 @@ export const useTokenBalanceForChain = (address?: string, network?: Network) => ], queryFn: async () => { if (activeWallet?.address && tokenAddress && provider) { - const contract = new Contract(tokenAddress, abi, provider); - return BalanceSchema.parse(await contract.balanceOf(activeWallet?.address)); + return await fetchTokenBalance(tokenAddress, activeWallet?.address, provider); } }, enabled: Boolean(activeWallet?.address && tokenAddress && provider), diff --git a/liquidity/lib/useV2Position/index.ts b/liquidity/lib/useV2Position/index.ts new file mode 100644 index 000000000..3ab04b59d --- /dev/null +++ b/liquidity/lib/useV2Position/index.ts @@ -0,0 +1 @@ +export * from './useV2Position'; diff --git a/liquidity/lib/useV2Position/package.json b/liquidity/lib/useV2Position/package.json new file mode 100644 index 000000000..ec4b8bee5 --- /dev/null +++ b/liquidity/lib/useV2Position/package.json @@ -0,0 +1,14 @@ +{ + "name": "@snx-v3/useV2Position", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@snx-v3/useBlockchain": "workspace:*", + "@snx-v3/useV2xSynthetix": "workspace:*", + "@synthetixio/wei": "^2.74.4", + "@tanstack/react-query": "^5.8.3", + "ethers": "^5.7.2", + "react": "^18.2.0" + } +} diff --git a/liquidity/lib/useV2Position/useV2Position.ts b/liquidity/lib/useV2Position/useV2Position.ts new file mode 100644 index 000000000..1600c0cfe --- /dev/null +++ b/liquidity/lib/useV2Position/useV2Position.ts @@ -0,0 +1,46 @@ +import { useQuery } from '@tanstack/react-query'; +import { Network, useNetwork, useWallet } from '@snx-v3/useBlockchain'; +import { useV2xSynthetix } from '@snx-v3/useV2xSynthetix'; +import { wei } from '@synthetixio/wei'; +import { utils } from 'ethers'; + +export function useV2Position(customNetwork?: Network | null) { + const { data: v2xSynthetix } = useV2xSynthetix(customNetwork); + const { network } = useNetwork(); + const { activeWallet } = useWallet(); + const targetNetwork = customNetwork || network; + + return useQuery({ + queryKey: [ + `${targetNetwork?.id}-${targetNetwork?.preset}`, + 'V2Position', + { + wallet: activeWallet?.address, + }, + ], + queryFn: async function () { + if (!v2xSynthetix) { + return; + } + const [collateral, balance, debt, cratio, transferableSynthetix] = await Promise.all([ + wei(await v2xSynthetix.collateral(activeWallet?.address)), + wei(await v2xSynthetix.balanceOf(activeWallet?.address)), + wei( + await v2xSynthetix.debtBalanceOf(activeWallet?.address, utils.formatBytes32String('sUSD')) + ), + wei(await v2xSynthetix.collateralisationRatio(activeWallet?.address)), + wei(await v2xSynthetix.transferableSynthetix(activeWallet?.address)), + ]); + + return { + collateral, + balance, + debt, + cratio, + transferableSynthetix, + }; + }, + enabled: Boolean(v2xSynthetix && activeWallet?.address), + staleTime: Infinity, + }); +} diff --git a/liquidity/lib/useV2sUSD/index.ts b/liquidity/lib/useV2sUSD/index.ts new file mode 100644 index 000000000..7602fc8ac --- /dev/null +++ b/liquidity/lib/useV2sUSD/index.ts @@ -0,0 +1 @@ +export * from './useV2sUSD'; diff --git a/liquidity/lib/useV2sUSD/package.json b/liquidity/lib/useV2sUSD/package.json new file mode 100644 index 000000000..1535ea0ec --- /dev/null +++ b/liquidity/lib/useV2sUSD/package.json @@ -0,0 +1,13 @@ +{ + "name": "@snx-v3/useV2sUSD", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@snx-v3/useBlockchain": "workspace:*", + "@snx-v3/useV2xSynthetix": "workspace:*", + "@tanstack/react-query": "^5.8.3", + "ethers": "^5.7.2", + "react": "^18.2.0" + } +} diff --git a/liquidity/lib/useV2sUSD/useV2sUSD.ts b/liquidity/lib/useV2sUSD/useV2sUSD.ts new file mode 100644 index 000000000..edbf4a65a --- /dev/null +++ b/liquidity/lib/useV2sUSD/useV2sUSD.ts @@ -0,0 +1,21 @@ +import { useQuery } from '@tanstack/react-query'; +import { Network, useNetwork } from '@snx-v3/useBlockchain'; +import { useV2xSynthetix } from '@snx-v3/useV2xSynthetix'; +import { utils } from 'ethers'; + +export function useV2sUSD(customNetwork?: Network | null) { + const { data: v2xSynthetix } = useV2xSynthetix(customNetwork); + const { network } = useNetwork(); + const targetNetwork = customNetwork || network; + + return useQuery({ + queryKey: [`${targetNetwork?.id}-${targetNetwork?.preset}`, 'v2-sUSD'], + queryFn: async function () { + if (!v2xSynthetix) { + return; + } + return (await v2xSynthetix.synths(utils.formatBytes32String('sUSD'))) as string; + }, + enabled: Boolean(v2xSynthetix), + }); +} diff --git a/liquidity/lib/useV2xSynthetix/index.ts b/liquidity/lib/useV2xSynthetix/index.ts new file mode 100644 index 000000000..dbd223e33 --- /dev/null +++ b/liquidity/lib/useV2xSynthetix/index.ts @@ -0,0 +1 @@ +export * from './useV2xSynthetix'; diff --git a/liquidity/lib/useV2xSynthetix/package.json b/liquidity/lib/useV2xSynthetix/package.json new file mode 100644 index 000000000..f635f2acf --- /dev/null +++ b/liquidity/lib/useV2xSynthetix/package.json @@ -0,0 +1,13 @@ +{ + "name": "@snx-v3/useV2xSynthetix", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@ethersproject/contracts": "^5.7.0", + "@snx-v3/contracts": "workspace:*", + "@snx-v3/useBlockchain": "workspace:*", + "@tanstack/react-query": "^5.8.3", + "react": "^18.2.0" + } +} diff --git a/liquidity/lib/useV2xSynthetix/useV2xSynthetix.ts b/liquidity/lib/useV2xSynthetix/useV2xSynthetix.ts new file mode 100644 index 000000000..f8c89a873 --- /dev/null +++ b/liquidity/lib/useV2xSynthetix/useV2xSynthetix.ts @@ -0,0 +1,41 @@ +import { Contract } from '@ethersproject/contracts'; +import { useQuery } from '@tanstack/react-query'; +import { + Network, + useNetwork, + useProvider, + useProviderForChain, + useSigner, +} from '@snx-v3/useBlockchain'; +import { importV2x } from '@snx-v3/contracts'; + +export function useV2xSynthetix(customNetwork?: Network | null) { + const providerForChain = useProviderForChain(customNetwork); + const { network } = useNetwork(); + const provider = useProvider(); + const signer = useSigner(); + const targetNetwork = customNetwork || network; + + const withSigner = Boolean(signer); + + return useQuery({ + queryKey: [`${targetNetwork?.id}-${targetNetwork?.preset}`, 'V2xSynthetix', { withSigner }], + queryFn: async function () { + if (providerForChain && customNetwork) { + const { address: v2xAddress, abi: v2xAbi } = await importV2x( + customNetwork.id, + customNetwork.preset + ); + return new Contract(v2xAddress, v2xAbi, providerForChain); + } + + const signerOrProvider = signer || provider; + if (!signerOrProvider || !network) throw new Error('Should be disabled CP'); + + const { address: v2xAddress, abi: v2xAbi } = await importV2x(network?.id, network?.preset); + return new Contract(v2xAddress, v2xAbi, signerOrProvider); + }, + enabled: Boolean(signer || provider || providerForChain), + staleTime: Infinity, + }); +} diff --git a/liquidity/ui/package.json b/liquidity/ui/package.json index df0514f15..ac918d81a 100644 --- a/liquidity/ui/package.json +++ b/liquidity/ui/package.json @@ -56,15 +56,19 @@ "@snx-v3/useEthBalance": "workspace:*", "@snx-v3/useGasSpeed": "workspace:*", "@snx-v3/useGetUSDTokens": "workspace:*", + "@snx-v3/useLegacyMarket": "workspace:*", "@snx-v3/useLiquidityPosition": "workspace:*", "@snx-v3/useLiquidityPositions": "workspace:*", "@snx-v3/useLocalStorage": "workspace:*", "@snx-v3/useManagePermissions": "workspace:*", + "@snx-v3/useMigrate": "workspace:*", + "@snx-v3/useMigrateUSD": "workspace:*", "@snx-v3/useOraclePrice": "workspace:*", "@snx-v3/useParams": "workspace:*", "@snx-v3/usePoolConfiguration": "workspace:*", "@snx-v3/usePoolData": "workspace:*", "@snx-v3/usePoolsList": "workspace:*", + "@snx-v3/useRates": "workspace:*", "@snx-v3/useRepay": "workspace:*", "@snx-v3/useRewards": "workspace:^", "@snx-v3/useRewardsDistributors": "workspace:*", @@ -72,8 +76,11 @@ "@snx-v3/useTokenBalance": "workspace:*", "@snx-v3/useTransferAccountId": "workspace:*", "@snx-v3/useTransferableSynthetix": "workspace:*", + "@snx-v3/useUSDProxy": "workspace:*", "@snx-v3/useUndelegate": "workspace:*", "@snx-v3/useUndelegateBaseAndromeda": "workspace:*", + "@snx-v3/useV2Position": "workspace:*", + "@snx-v3/useV2sUSD": "workspace:*", "@snx-v3/useVaultsData": "workspace:*", "@snx-v3/validatePosition": "workspace:*", "@synthetixio/safe-import": "workspace:*", diff --git a/liquidity/ui/public/Migrate Launch.png b/liquidity/ui/public/Migrate Launch.png new file mode 100644 index 000000000..fbf3c4652 Binary files /dev/null and b/liquidity/ui/public/Migrate Launch.png differ diff --git a/liquidity/ui/public/Rocket.png b/liquidity/ui/public/Rocket.png new file mode 100644 index 000000000..313bf72b8 Binary files /dev/null and b/liquidity/ui/public/Rocket.png differ diff --git a/liquidity/ui/public/synthetix-rocket.png b/liquidity/ui/public/synthetix-rocket.png new file mode 100644 index 000000000..c9f3fea57 Binary files /dev/null and b/liquidity/ui/public/synthetix-rocket.png differ diff --git a/liquidity/ui/src/App.tsx b/liquidity/ui/src/App.tsx index 52e71902a..ac14bec3b 100644 --- a/liquidity/ui/src/App.tsx +++ b/liquidity/ui/src/App.tsx @@ -55,7 +55,7 @@ function ColorMode() { export const App = () => { const TERMS_CONDITIONS_ACCEPTED = - sessionStorage.getItem(SESSION_STORAGE_KEYS.TERMS_CONDITIONS_ACCEPTED) === 'true'; + localStorage.getItem(SESSION_STORAGE_KEYS.TERMS_CONDITIONS_ACCEPTED) === 'true'; return ( <> @@ -85,7 +85,9 @@ export const App = () => { - + diff --git a/liquidity/ui/src/components/Assets/AssetsList.tsx b/liquidity/ui/src/components/Assets/AssetsList.tsx index a7c7151dd..94eb91a15 100644 --- a/liquidity/ui/src/components/Assets/AssetsList.tsx +++ b/liquidity/ui/src/components/Assets/AssetsList.tsx @@ -33,8 +33,9 @@ export const AssetsList = () => { : [usdTokens?.USDC] : accountCollaterals?.map((collateral) => collateral.tokenAddress) || []; - const { data: userTokenBalances, isLoading: tokenBalancesIsLoading } = - useTokenBalances(collateralAddresses); + const { data: userTokenBalances, isLoading: tokenBalancesIsLoading } = useTokenBalances( + collateralAddresses.filter((a) => !!a) + ); const associatedUserBalances = userTokenBalances?.map((balance, index) => { return { diff --git a/liquidity/ui/src/components/Borrow/Borrow.tsx b/liquidity/ui/src/components/Borrow/Borrow.tsx index 5fad43f00..b7dd0028d 100644 --- a/liquidity/ui/src/components/Borrow/Borrow.tsx +++ b/liquidity/ui/src/components/Borrow/Borrow.tsx @@ -70,7 +70,7 @@ const BorrowUi: FC<{ - + As a security precaution, borrowed assets can only be withdrawn to your wallet after 24 diff --git a/liquidity/ui/src/components/CRatioBar/CRatioAmount.tsx b/liquidity/ui/src/components/CRatioBar/CRatioAmount.tsx new file mode 100644 index 000000000..73f3fea59 --- /dev/null +++ b/liquidity/ui/src/components/CRatioBar/CRatioAmount.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { ratioIsMaxUInt } from './CRatioBar.utils'; +import { Amount } from '@snx-v3/Amount'; +import { wei } from '@synthetixio/wei'; + +export const CRatioAmount: FC<{ + value: number; +}> = ({ value }) => { + if (!value || value < 0) { + return <>N/A; + } + + if (ratioIsMaxUInt(value)) { + return <>Infinite; + } + + return ; +}; diff --git a/liquidity/ui/src/components/CRatioBar/CRatioBar.tsx b/liquidity/ui/src/components/CRatioBar/CRatioBar.tsx index 971dbf524..20d7313dd 100644 --- a/liquidity/ui/src/components/CRatioBar/CRatioBar.tsx +++ b/liquidity/ui/src/components/CRatioBar/CRatioBar.tsx @@ -4,6 +4,7 @@ import { FC } from 'react'; import { LineWithText } from './LineWithText'; import { getHealthVariant, getProgressSize, ratioIsMaxUInt } from './CRatioBar.utils'; import { CRatioBadge } from './CRatioBadge'; +import { CRatioAmount } from './CRatioAmount'; export const CRatioBarUi: FC<{ liquidationCratio: number; @@ -57,17 +58,13 @@ export const CRatioBarUi: FC<{ alignItems="center" gap={2} > - {!currentCRatio ? ( - N/A - ) : ( - {ratioIsMaxUInt(currentCRatio) ? 'Infinite' : `${currentCRatio.toFixed(2)}%`} - )} + {!!hasChanges && ( <> - {!newCratio + {!newCratio || newCratio < 0 ? 'N/A' : ratioIsMaxUInt(newCratio) ? 'Infinite' diff --git a/liquidity/ui/src/components/CRatioBar/CRatioBar.utils.ts b/liquidity/ui/src/components/CRatioBar/CRatioBar.utils.ts index 21ab7d504..5528079b3 100644 --- a/liquidity/ui/src/components/CRatioBar/CRatioBar.utils.ts +++ b/liquidity/ui/src/components/CRatioBar/CRatioBar.utils.ts @@ -22,7 +22,7 @@ export const getHealthVariant = ({ return 'success'; }; -export const ratioIsMaxUInt = (ratio: number) => ratio >= Number.MAX_SAFE_INTEGER || ratio < 0; +export const ratioIsMaxUInt = (ratio: number) => ratio >= Number.MAX_SAFE_INTEGER; export const getProgressSize = ({ targetCratio, diff --git a/liquidity/ui/src/components/ChangeStat/ChangeStat.tsx b/liquidity/ui/src/components/ChangeStat/ChangeStat.tsx new file mode 100644 index 000000000..767382aed --- /dev/null +++ b/liquidity/ui/src/components/ChangeStat/ChangeStat.tsx @@ -0,0 +1,65 @@ +import { FC, ReactNode } from 'react'; +import { Flex, Text } from '@chakra-ui/react'; +import Wei from '@synthetixio/wei'; +import { ArrowForwardIcon } from '@chakra-ui/icons'; + +const styles = { + sm: { + fontSize: '12px', + fontWeight: '700', + lineHeight: '14px', + }, + md: { + fontSize: '16px', + fontWeight: '400', + lineHeight: '16px', + }, + lg: { + fontSize: '18px', + fontWeight: '800', + lineHeight: '32px', + }, +}; +export const ChangeStat: FC<{ + value: Wei; + newValue: Wei; + hasChanges: boolean; + dataTestId?: string; + formatFn: (val: Wei) => ReactNode; + withColor?: boolean; + size?: 'sm' | 'md' | 'lg'; +}> = ({ formatFn, value, newValue, hasChanges, dataTestId, withColor, size = 'lg' }) => { + return ( + + + {formatFn(value)} + + {hasChanges && !value.eq(newValue) ? ( + <> + + + {formatFn(newValue)} + + + ) : null} + + ); +}; diff --git a/liquidity/ui/src/components/ChangeStat/index.tsx b/liquidity/ui/src/components/ChangeStat/index.tsx new file mode 100644 index 000000000..b1e112131 --- /dev/null +++ b/liquidity/ui/src/components/ChangeStat/index.tsx @@ -0,0 +1 @@ +export * from './ChangeStat'; diff --git a/liquidity/ui/src/components/Claim/Claim.tsx b/liquidity/ui/src/components/Claim/Claim.tsx index 84281ecac..3d4996fa9 100644 --- a/liquidity/ui/src/components/Claim/Claim.tsx +++ b/liquidity/ui/src/components/Claim/Claim.tsx @@ -58,7 +58,7 @@ const ClaimUi: FC<{ color="cyan.500" fontWeight={700} > -   Max +  Max )} @@ -83,7 +83,7 @@ const ClaimUi: FC<{ - + Positive market performance has credited your position. Claim up to{' '} @@ -105,7 +105,7 @@ const ClaimUi: FC<{ - + Assets will be available to withdraw 24 hours after your last interaction with this @@ -114,7 +114,7 @@ const ClaimUi: FC<{ - + You can take an interest-free loan up to   @@ -138,7 +138,7 @@ const ClaimUi: FC<{ in={!debtChange.gt(max) && debtChange.gt(0) && debtChange.gt(maxClaimble) && !isBase} animateOpacity > - + You are about to take a {' '} diff --git a/liquidity/ui/src/components/CollateralAlert/CollateralAlert.tsx b/liquidity/ui/src/components/CollateralAlert/CollateralAlert.tsx index bef128658..51a08c851 100644 --- a/liquidity/ui/src/components/CollateralAlert/CollateralAlert.tsx +++ b/liquidity/ui/src/components/CollateralAlert/CollateralAlert.tsx @@ -1,4 +1,4 @@ -import { Alert, AlertIcon, AlertProps, Link, Text } from '@chakra-ui/react'; +import { Alert, AlertIcon, AlertProps, Text } from '@chakra-ui/react'; import Wei from '@synthetixio/wei'; interface CollateralAlertProps extends AlertProps { @@ -10,11 +10,10 @@ export const CollateralAlert = ({ tokenBalance, ...props }: CollateralAlertProps - You have a {tokenBalance.toString(2)} SNX active staking position on V2. You‘ll need - to unstake on V2 before being able to deposit on V3.{' '} - - Go to V2 - + You have a {tokenBalance.toString(2)} SNX active staking position on V2. + +  Migrate to V3 + ); diff --git a/liquidity/ui/src/components/Deposit/Deposit.tsx b/liquidity/ui/src/components/Deposit/Deposit.tsx index 9cdbd0b2a..59354c5ae 100644 --- a/liquidity/ui/src/components/Deposit/Deposit.tsx +++ b/liquidity/ui/src/components/Deposit/Deposit.tsx @@ -178,7 +178,7 @@ export const DepositUi: FC<{ color="cyan.500" fontWeight={700} > -   Max +  Max )} @@ -215,7 +215,7 @@ export const DepositUi: FC<{ in={collateralChange.gt(0) && collateralChange.add(currentCollateral).lt(minDelegation)} animateOpacity > - + Your deposit must be {formatNumber(minDelegation.toString())} {symbol} or higher @@ -223,7 +223,7 @@ export const DepositUi: FC<{ - + You cannot Deposit & Lock more Collateral than your Balance amount diff --git a/liquidity/ui/src/components/InitialDeposit/InitialDeposit.tsx b/liquidity/ui/src/components/InitialDeposit/InitialDeposit.tsx index b5d34bb9c..82bbad4cc 100644 --- a/liquidity/ui/src/components/InitialDeposit/InitialDeposit.tsx +++ b/liquidity/ui/src/components/InitialDeposit/InitialDeposit.tsx @@ -22,9 +22,9 @@ import Wei from '@synthetixio/wei'; import { FC, useContext, useMemo, useState } from 'react'; import { useParams } from '@snx-v3/useParams'; import { useTransferableSynthetix } from '@snx-v3/useTransferableSynthetix'; -import { CollateralAlert, TokenIcon } from '..'; +import { TokenIcon } from '..'; import { useTokenBalance } from '@snx-v3/useTokenBalance'; -import { useNetwork } from '@snx-v3/useBlockchain'; +import { MAINNET, SEPOLIA, useNetwork } from '@snx-v3/useBlockchain'; import { getSpotMarketId, isBaseAndromeda } from '@snx-v3/isBaseAndromeda'; import { useGetWrapperToken } from '@snx-v3/useGetUSDTokens'; import { WithdrawIncrease } from '@snx-v3/WithdrawIncrease'; @@ -33,6 +33,7 @@ import { ArrowBackIcon } from '@chakra-ui/icons'; import { LiquidityPosition } from '@snx-v3/useLiquidityPosition'; import { ZEROWEI } from '../../utils/constants'; import { useTokenPrice } from '../../../../lib/useTokenPrice'; +import { MigrationBanner } from '../Migration/MigrationBanner'; export const InitialDepositUi: FC<{ collateralChange: Wei; @@ -65,6 +66,7 @@ export const InitialDepositUi: FC<{ const [step, setStep] = useState(0); const price = useTokenPrice(symbol); + const { network } = useNetwork(); const combinedTokenBalance = useMemo(() => { if (symbol === 'SNX') { @@ -99,7 +101,7 @@ export const InitialDepositUi: FC<{ - + -   Max +  Max @@ -181,8 +183,8 @@ export const InitialDepositUi: FC<{ - {snxBalance?.collateral && snxBalance?.collateral.gt(0) && symbol === 'SNX' && ( - + {symbol === 'SNX' && network && [MAINNET.id, SEPOLIA.id].includes(network.id) && ( + )} - + Your deposit must be {formatNumber(minDelegation.toString())} {symbol} or higher @@ -206,7 +208,7 @@ export const InitialDepositUi: FC<{ - + You cannot Deposit & Lock more Collateral than your Balance amount diff --git a/liquidity/ui/src/components/Manage/DebtStats.tsx b/liquidity/ui/src/components/Manage/DebtStats.tsx index 52fb0160a..788ee2c3e 100644 --- a/liquidity/ui/src/components/Manage/DebtStats.tsx +++ b/liquidity/ui/src/components/Manage/DebtStats.tsx @@ -63,7 +63,7 @@ export const DebtStats: FC<{ } + formatFn={(val: Wei) => } hasChanges={hasChanges} dataTestId="manage-stats-debt-value" /> diff --git a/liquidity/ui/src/components/Manage/LiquidityPositionUpdated.tsx b/liquidity/ui/src/components/Manage/LiquidityPositionUpdated.tsx index 312d4f55e..99711aec9 100644 --- a/liquidity/ui/src/components/Manage/LiquidityPositionUpdated.tsx +++ b/liquidity/ui/src/components/Manage/LiquidityPositionUpdated.tsx @@ -30,7 +30,7 @@ export function LiquidityPositionUpdated({ {subline} )} - + diff --git a/liquidity/ui/src/components/Manage/ManageActions.tsx b/liquidity/ui/src/components/Manage/ManageActions.tsx index d2d5bcc87..fcb954f73 100644 --- a/liquidity/ui/src/components/Manage/ManageActions.tsx +++ b/liquidity/ui/src/components/Manage/ManageActions.tsx @@ -68,16 +68,12 @@ const ManageActionUi: FC<{ const debtActions = DEBTACTIONS(isBase); useEffect(() => { - if (tab === 'collateral' && !COLLATERALACTIONS.find((aciton) => aciton.link === manageAction)) { - setActiveAction(COLLATERALACTIONS[0].link); - } else if (tab === 'debt' && !debtActions.find((aciton) => aciton.link === manageAction)) { - setActiveAction(debtActions[0].link); - } - }, [debtActions, manageAction, setActiveAction, tab]); + setTab(getInitialTab(manageAction)); + }, [manageAction]); return ( - + { - setTab('collateral'); + if (tab !== 'collateral') { + setActiveAction(COLLATERALACTIONS[0].link); + } }} > Manage Collateral @@ -96,7 +94,9 @@ const ManageActionUi: FC<{ fontSize={['12px', '16px']} data-cy="tab-button-debt" onClick={() => { - setTab('debt'); + if (tab !== 'debt') { + setActiveAction(debtActions[0].link); + } }} > {`Manage ${isBase ? 'PnL' : 'Debt'}`} diff --git a/liquidity/ui/src/components/MigrateUSD/MigrateUSDButton.tsx b/liquidity/ui/src/components/MigrateUSD/MigrateUSDButton.tsx new file mode 100644 index 000000000..fa8633b9c --- /dev/null +++ b/liquidity/ui/src/components/MigrateUSD/MigrateUSDButton.tsx @@ -0,0 +1,58 @@ +import { Button } from '@chakra-ui/react'; +import { FC, useEffect, useState } from 'react'; +import { Network, useNetwork, useWallet } from '@snx-v3/useBlockchain'; +import { MigrateUSDModal } from './MigrateUSDModal'; +import { TokenIcon } from '../TokenIcon'; +import { useLocation } from 'react-router-dom'; + +interface Props { + network: Network; +} + +export const MigrateUSDButton: FC = ({ network }) => { + const location = useLocation(); + + const [isOpen, setIsOpen] = useState(false); + const { network: currentNetwork } = useNetwork(); + + const { activeWallet } = useWallet(); + + useEffect(() => { + const queryParams = new URLSearchParams(location.search); + + const convert = queryParams.get('convert'); + + if (convert && convert.toLowerCase() === 'snxusd') { + setIsOpen(true); + } + }, [location.search]); + + if (!activeWallet || currentNetwork?.id !== network.id) { + return null; + } + + return ( + <> + setIsOpen(false)} + isOpen={isOpen} + /> + + + ); +}; diff --git a/liquidity/ui/src/components/MigrateUSD/MigrateUSDModal.tsx b/liquidity/ui/src/components/MigrateUSD/MigrateUSDModal.tsx new file mode 100644 index 000000000..b9f349614 --- /dev/null +++ b/liquidity/ui/src/components/MigrateUSD/MigrateUSDModal.tsx @@ -0,0 +1,103 @@ +import { + CloseButton, + Divider, + Flex, + Heading, + Modal, + ModalBody, + ModalContent, + ModalOverlay, +} from '@chakra-ui/react'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { StepIntro } from './StepIntro'; +import { ZEROWEI } from '../../utils/constants'; +import { MigrateUSDTransaction } from './MigrateUSDTransaction'; +import { Network } from '@snx-v3/useBlockchain'; +import { StepSuccessFinal } from '../Migration/StepSuccessFinal'; +import { generatePath, useLocation, useNavigate } from 'react-router-dom'; + +interface Props { + onClose: () => void; + isOpen: boolean; + network: Network; + type: 'migration' | 'convert'; + accountId?: string; +} + +export const MigrateUSDModal: FC = ({ onClose, isOpen, network, type, accountId }) => { + const [step, setStep] = useState(0); + const [amount, setAmount] = useState(ZEROWEI); + const location = useLocation(); + const navigate = useNavigate(); + + useEffect(() => { + if (!isOpen) { + setStep(0); + setAmount(ZEROWEI); + } + }, [isOpen]); + + const handleConfirm = useCallback(() => { + if (accountId) { + const queryParams = new URLSearchParams(location.search); + + queryParams.set('accountId', accountId); + + navigate( + { + pathname: generatePath('/dashboard'), + search: queryParams.toString(), + }, + { replace: true } + ); + } + onClose(); + }, [accountId, location.search, navigate, onClose]); + + return ( + + + + + + {step === 2 ? 'Migration successful' : 'Convert your sUSD to V3'} + + + + + + + + {isOpen && ( + <> + {step === 0 && ( + setStep(1)} + network={network} + /> + )} + {step === 1 && ( + { + if (type === 'migration') { + setStep(2); + } else { + onClose(); + } + }} + onBack={() => setStep(0)} + amount={amount} + /> + )} + {step === 2 && } + + )} + + + + ); +}; diff --git a/liquidity/ui/src/components/MigrateUSD/MigrateUSDTransaction.tsx b/liquidity/ui/src/components/MigrateUSD/MigrateUSDTransaction.tsx new file mode 100644 index 000000000..dcf80d95b --- /dev/null +++ b/liquidity/ui/src/components/MigrateUSD/MigrateUSDTransaction.tsx @@ -0,0 +1,185 @@ +import { Button, Text, useToast, VStack } from '@chakra-ui/react'; +import { Amount } from '@snx-v3/Amount'; +import { Multistep } from '@snx-v3/Multistep'; +import { useApprove } from '@snx-v3/useApprove'; +import { Wei } from '@synthetixio/wei'; +import { FC, useCallback, useState } from 'react'; +import { Network } from '@snx-v3/useBlockchain'; +import { useV2sUSD } from '@snx-v3/useV2sUSD'; +import { useLegacyMarket } from '@snx-v3/useLegacyMarket'; +import { useMigrateUSD } from '@snx-v3/useMigrateUSD'; +import { StepSuccess } from './StepSuccess'; +import { ZEROWEI } from '../../utils/constants'; +import { useTokenBalance } from '@snx-v3/useTokenBalance'; +import { useUSDProxyForChain } from '@snx-v3/useUSDProxy'; + +type Props = FC<{ + amount: Wei; + network: Network; + onSuccess: () => void; + onBack: () => void; +}>; + +export const MigrateUSDTransaction: Props = ({ onSuccess, amount, network, onBack }) => { + const { data: legacyMarket } = useLegacyMarket(); + + const { data: v2_sUSD } = useV2sUSD(network); + const { data: v2_balance } = useTokenBalance(v2_sUSD, network); + const { data: v3_sUSD } = useUSDProxyForChain(network); + const { data: v3_balance } = useTokenBalance(v3_sUSD?.address, network); + + const [infiniteApproval, setInfiniteApproval] = useState(false); + const [txState, setTxState] = useState({ + step: 1, + status: 'idle', + }); + const [txSummary, setTxSummary] = useState({ + amount: ZEROWEI, + v2Balance: ZEROWEI, + v3Balance: ZEROWEI, + }); + + const { approve, refetchAllowance, requireApproval } = useApprove({ + contractAddress: v2_sUSD, + amount: amount.toBN(), + spender: legacyMarket?.address, + }); + + const toast = useToast({ isClosable: true, duration: 9000 }); + + const { migrate, isSuccess } = useMigrateUSD({ + amount, + }); + + const onSubmit = useCallback(async () => { + try { + if (txState.step > 2) { + onSuccess(); + return; + } + + if (txState.step === 1 && requireApproval) { + setTxState({ + step: 1, + status: 'pending', + }); + + await approve(infiniteApproval); + refetchAllowance(); + } + + setTxState({ + step: 2, + status: 'pending', + }); + + setTxSummary({ + amount, + v2Balance: v2_balance || ZEROWEI, + v3Balance: v3_balance || ZEROWEI, + }); + await migrate(); + + setTxState({ + step: 2, + status: 'success', + }); + + toast.closeAll(); + toast({ + title: 'Success', + description: 'Migration executed.', + status: 'success', + duration: 5000, + variant: 'left-accent', + }); + } catch (error) { + setTxState((state) => ({ + step: state.step, + status: 'error', + })); + toast({ + title: 'Migration failed', + description: 'Please try again.', + status: 'error', + variant: 'left-accent', + }); + } + }, [ + amount, + approve, + infiniteApproval, + migrate, + onSuccess, + refetchAllowance, + requireApproval, + toast, + txState.step, + v2_balance, + v3_balance, + ]); + + if (isSuccess) { + return ; + } + + return ( + + 1, + loading: txState.step === 1 && txState.status === 'pending', + }} + checkboxLabel="Approve unlimited sUSD transfers to Synthetix" + checkboxProps={{ + isChecked: infiniteApproval, + onChange: (e) => setInfiniteApproval(e.target.checked), + }} + mt={0} + /> + + + This will convert to v3 sUSD + + } + status={{ + failed: txState.step === 2 && txState.status === 'error', + success: txState.step === 2 && txState.status === 'sucess', + loading: txState.step === 2 && txState.status === 'pending', + }} + /> + + + + {txState.status !== 'pending' && ( + + )} + + ); +}; diff --git a/liquidity/ui/src/components/MigrateUSD/StepIntro.tsx b/liquidity/ui/src/components/MigrateUSD/StepIntro.tsx new file mode 100644 index 000000000..666a63417 --- /dev/null +++ b/liquidity/ui/src/components/MigrateUSD/StepIntro.tsx @@ -0,0 +1,154 @@ +import React, { useEffect, useState } from 'react'; +import { + VStack, + Text, + Button, + Flex, + Collapse, + Alert, + AlertIcon, + AlertDescription, +} from '@chakra-ui/react'; +import { NumberInput } from '@snx-v3/NumberInput'; +import { Network } from '@snx-v3/useBlockchain'; +import { useTokenBalance } from '@snx-v3/useTokenBalance'; +import Wei from '@synthetixio/wei'; +import { ZEROWEI } from '../../utils/constants'; +import { Amount } from '@snx-v3/Amount'; +import { BorderBox } from '@snx-v3/BorderBox'; +import { TokenIcon } from '../TokenIcon'; +import { useUSDProxyForChain } from '@snx-v3/useUSDProxy'; +import { useV2sUSD } from '@snx-v3/useV2sUSD'; + +export const StepIntro = ({ + onClose, + onConfirm, + setAmount, + amount, + network, +}: { + onClose: () => void; + onConfirm: () => void; + setAmount: (val: Wei) => void; + amount: Wei; + network: Network; +}) => { + const [loaded, setLoaded] = useState(false); + const { data: v2_sUSD } = useV2sUSD(network); + const { data: v2_balance } = useTokenBalance(v2_sUSD, network); + const { data: v3_sUSD } = useUSDProxyForChain(network); + const { data: v3_balance } = useTokenBalance(v3_sUSD?.address, network); + + useEffect(() => { + if (v2_balance && amount.eq(0) && !loaded) { + setAmount(v2_balance); + setLoaded(true); + } + }, [amount, loaded, setAmount, v2_balance]); + + return ( + + + Convert your sUSD to V3 compatible sUSD. You will need V3 compatible sUSD to interact with + the new Synthetix products. + + + + + + + + + + V2 sUSD + + + + Balance: + { + if (!v2_balance) { + return; + } + setAmount(v2_balance); + }} + color={v2_balance?.eq(amount) ? 'gray.600' : 'cyan.500'} + fontWeight={700} + > +  Max + + + + + + setAmount(val)} + min={ZEROWEI} + /> + + + + + + + + + + + V3 sUSD + + + + Balance: + + + + + + + + + + + + You cannot convert more than your v2 sUSD balance + + + + + + + ); +}; diff --git a/liquidity/ui/src/components/MigrateUSD/StepSuccess.tsx b/liquidity/ui/src/components/MigrateUSD/StepSuccess.tsx new file mode 100644 index 000000000..c668fd4d2 --- /dev/null +++ b/liquidity/ui/src/components/MigrateUSD/StepSuccess.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { VStack, Text, Button, Alert, Flex } from '@chakra-ui/react'; +import { ArrowUpIcon, CheckIcon } from '@chakra-ui/icons'; +import { TransactionSummary } from '../TransactionSummary/TransactionSummary'; +import Wei from '@synthetixio/wei'; +import { currency } from '@snx-v3/format'; +import { ChangeStat } from '../ChangeStat'; + +export const StepSuccess = ({ + onConfirm, + v2Balance, + v3Balance, + amount, +}: { + onConfirm: () => void; + v2Balance: Wei; + v3Balance: Wei; + amount: Wei; +}) => { + return ( + + + Your V2 sUSD has been converted to V3 sUSD + + + + + + + + sUSD successfully converted + + + + currency(val)} + hasChanges + size="sm" + /> + ), + }, + { + label: 'Total V3 sUSD', + value: ( + currency(val)} + hasChanges + size="sm" + /> + ), + }, + ]} + /> + + + + + + ); +}; diff --git a/liquidity/ui/src/components/Migration/MigrationBanner.tsx b/liquidity/ui/src/components/Migration/MigrationBanner.tsx new file mode 100644 index 000000000..b5f5f982e --- /dev/null +++ b/liquidity/ui/src/components/Migration/MigrationBanner.tsx @@ -0,0 +1,151 @@ +import { + Alert, + AlertIcon, + Button, + Divider, + Fade, + Flex, + Heading, + Image, + Link, + Text, +} from '@chakra-ui/react'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { Network, useNetwork, useWallet } from '@snx-v3/useBlockchain'; +import { Amount } from '@snx-v3/Amount'; +import { MigrationDialog } from './MigrationDialog'; +import { MigrateUSDModal } from '../MigrateUSD/MigrateUSDModal'; +import { useV2Position } from '@snx-v3/useV2Position'; +import { useLocation, useNavigate } from 'react-router-dom'; + +interface Props { + network: Network; + type?: 'banner' | 'alert'; +} + +export const MigrationBanner: FC = ({ network, type = 'banner' }) => { + const [isOpen, setIsOpen] = useState(false); + const [accountId, setAccountId] = useState(''); + const [isUSDModalOpen, setIsUSDModalOpen] = useState(false); + const { data } = useV2Position(network); + const { network: currentNetwork, setNetwork } = useNetwork(); + const { connect, activeWallet } = useWallet(); + const location = useLocation(); + const navigate = useNavigate(); + + const onClick = useCallback(async () => { + try { + if (!activeWallet) { + connect(); + return; + } + + if (!currentNetwork || currentNetwork.id !== network.id) { + if (!(await setNetwork(network.id))) { + return; + } + } + + setIsOpen(true); + } catch (error) {} + }, [activeWallet, connect, currentNetwork, network.id, setNetwork]); + + useEffect(() => { + const queryParams = new URLSearchParams(location.search); + + const convert = queryParams.get('migrate'); + + if (convert && convert.toLowerCase() === 'snx') { + setIsOpen(true); + + const queryParams = new URLSearchParams(location.search); + queryParams.delete('migrate'); + navigate({ + pathname: location.pathname, + search: queryParams.toString(), + }); + } + }, [location.pathname, location.search, navigate]); + + return ( + <> + { + setAccountId(accountId); + setIsUSDModalOpen(true); + }} + network={network} + onClose={() => setIsOpen(false)} + isOpen={isOpen} + /> + setIsUSDModalOpen(false)} + isOpen={isUSDModalOpen} + type="migration" + accountId={accountId} + /> + + {!!data && data?.collateral.gt(0) && ( + + {type === 'banner' && ( + <> + + + + + + + + You have a SNX position on Synthetix V2 + + + Migrate your SNX to Synthetix V3 to earn fees from both V2 and V3 markets and + much more. + + + + + + + + + + + + )} + + {type === 'alert' && ( + + + + You have a SNX active staking position on V2. + +  Migrate to V3 + + + + )} + + )} + + ); +}; diff --git a/liquidity/ui/src/components/Migration/MigrationDialog.tsx b/liquidity/ui/src/components/Migration/MigrationDialog.tsx new file mode 100644 index 000000000..09345c5c7 --- /dev/null +++ b/liquidity/ui/src/components/Migration/MigrationDialog.tsx @@ -0,0 +1,66 @@ +import { + CloseButton, + Divider, + Flex, + Heading, + Modal, + ModalBody, + ModalContent, + ModalOverlay, +} from '@chakra-ui/react'; +import { FC, useEffect, useState } from 'react'; +import { Network } from '@snx-v3/useBlockchain'; +import { StepIntro } from './StepIntro'; +import { StepExplain } from './StepExplain'; +import { StepSummary } from './StepSummary'; +import { useQueryClient } from '@tanstack/react-query'; +interface Props { + network: Network; + onClose: () => void; + onSuccess: (accountId: string) => void; + isOpen: boolean; +} + +export const MigrationDialog: FC = ({ network, onClose, isOpen, onSuccess }) => { + const queryClient = useQueryClient(); + const [step, setStep] = useState(0); + + useEffect(() => { + if (!isOpen) { + setStep(0); + } + }, [isOpen]); + + return ( + + + + + Migrate to Synthetix V3 + + + + + + + {step === 0 && setStep(1)} onClose={onClose} />} + {step === 1 && setStep(2)} onClose={() => setStep(0)} />} + {step === 2 && ( + { + queryClient.invalidateQueries({ + queryKey: [`${network?.id}-${network?.preset}`, 'V2Position'], + }); + + onSuccess(accountId); + onClose(); + }} + onClose={onClose} + network={network} + /> + )} + + + + ); +}; diff --git a/liquidity/ui/src/components/Migration/StepExplain.tsx b/liquidity/ui/src/components/Migration/StepExplain.tsx new file mode 100644 index 000000000..c23e3c40e --- /dev/null +++ b/liquidity/ui/src/components/Migration/StepExplain.tsx @@ -0,0 +1,154 @@ +import React from 'react'; +import { VStack, Alert, Text, Button, AlertIcon, Link, Flex } from '@chakra-ui/react'; + +export const StepExplain = ({ + onClose, + onConfirm, +}: { + onClose: () => void; + onConfirm: () => void; +}) => { + return ( + + Migrating to Synthetix V3 consists of: + + + + + + + + + Creation of an Account on Synthetix V3 + + + + + + + + + + + Migration of your SNX Collateral (including escrowed SNX) and your debt to a New + Liquidity Position on the Liquidity App + + + + + + + + + + Lock of funds for 7 days + + + + + + + + + + (optional) Conversion of your sUSD into V3 compatible sUSD. You can + +  convert your sUSD  + + at anytime + + + + + + + + Migration to V3 is currently only available on Ethereum Mainnet. Learn more about + +  migrating to V3. + + + + + + + + ); +}; diff --git a/liquidity/ui/src/components/Migration/StepIntro.tsx b/liquidity/ui/src/components/Migration/StepIntro.tsx new file mode 100644 index 000000000..60648a7cc --- /dev/null +++ b/liquidity/ui/src/components/Migration/StepIntro.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { + Flex, + Spacer, + VStack, + Heading, + Text, + ListItem, + Button, + Image, + ListIcon, + List, + Link, +} from '@chakra-ui/react'; +import { CheckIcon } from '@chakra-ui/icons'; + +export const StepIntro = ({ + onClose, + onConfirm, +}: { + onClose: () => void; + onConfirm: () => void; +}) => { + return ( + + + + Synthetix V3 is now live! + + + Migrate to Synthetix V3 to earn fees from both V2 and V3 markets and much more: + + + + + + V2 Legacy Market Fees + + + + V3 SC Pool Fees + + + + LP Incentives + + + + Improved LP experience + + + + Learn more about{' '} + + Synthetix V3 migration process + + + + + Synthetix V3 Launch + + + + + + + + + + + + ); +}; diff --git a/liquidity/ui/src/components/Migration/StepSuccess.tsx b/liquidity/ui/src/components/Migration/StepSuccess.tsx new file mode 100644 index 000000000..30805d894 --- /dev/null +++ b/liquidity/ui/src/components/Migration/StepSuccess.tsx @@ -0,0 +1,73 @@ +import React, { useState } from 'react'; +import { VStack, Text, Button, Alert, Flex, Tooltip } from '@chakra-ui/react'; +import { CheckIcon, CopyIcon } from '@chakra-ui/icons'; +import { TransactionSummary } from '../TransactionSummary/TransactionSummary'; + +export const StepSuccess = ({ + onConfirm, + collateral, + cRatio, + accountId, +}: { + onConfirm: () => void; + cRatio: string; + collateral: string; + accountId: string; +}) => { + const [toolTipLabel, setTooltipLabel] = useState('Copy'); + return ( + + + Your Collateral has been migrated to Synthetix V3 System + + + + + + + + Collateral successfully migrated + + + + + {`#${accountId}`} + + { + navigator.clipboard.writeText(accountId); + setTooltipLabel('Copied'); + setTimeout(() => { + setTooltipLabel('Copy'); + }, 10000); + }} + cursor="pointer" + /> + + + ), + }, + ]} + /> + + + + ); +}; diff --git a/liquidity/ui/src/components/Migration/StepSuccessFinal.tsx b/liquidity/ui/src/components/Migration/StepSuccessFinal.tsx new file mode 100644 index 000000000..001e72f82 --- /dev/null +++ b/liquidity/ui/src/components/Migration/StepSuccessFinal.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { VStack, Text, Button, Alert, Flex, Image } from '@chakra-ui/react'; +import { CheckIcon } from '@chakra-ui/icons'; +import { Network } from '@snx-v3/useBlockchain'; + +export const StepSuccessFinal = ({ + onConfirm, + network, +}: { + onConfirm: () => void; + network: Network; +}) => { + return ( + + + Your migration to Synthetix V3 on {network.name} has been successfully Completed. + + + + Synthetix V3 Launch + + + + + + + Migration successfully Completed + + + + + + + ); +}; diff --git a/liquidity/ui/src/components/Migration/StepSummary.tsx b/liquidity/ui/src/components/Migration/StepSummary.tsx new file mode 100644 index 000000000..f37897b7a --- /dev/null +++ b/liquidity/ui/src/components/Migration/StepSummary.tsx @@ -0,0 +1,242 @@ +import React, { useCallback, useState } from 'react'; +import { + VStack, + Text, + Heading, + Box, + HStack, + Tooltip, + Checkbox, + Button, + Spinner, + Link, + Flex, +} from '@chakra-ui/react'; +import { wei } from '@synthetixio/wei'; +import { useV2Position } from '@snx-v3/useV2Position'; +import { Network } from '@snx-v3/useBlockchain'; +import { InfoIcon } from '@chakra-ui/icons'; +import { useMigrate } from '@snx-v3/useMigrate'; +import { StepSuccess } from './StepSuccess'; +import { formatEther } from 'ethers/lib/utils'; +import { Amount } from '@snx-v3/Amount'; +import { CRatioBadge } from '../CRatioBar/CRatioBadge'; +import { useRates } from '@snx-v3/useRates'; +import { ZEROWEI } from '../../utils/constants'; + +export const StepSummary = ({ + onClose, + network, + onConfirm, +}: { + onClose: () => void; + onConfirm: (accountId: string) => void; + network: Network; +}) => { + const [isUnderstanding, setIsUnderstanding] = useState(false); + const { data } = useV2Position(network); + const { migrate, transaction, isLoading, isSuccess, accountId } = useMigrate(); + + const { data: rates } = useRates(); + const snxPrice = rates?.snx || ZEROWEI; + const ethPrice = rates?.eth || ZEROWEI; + + const [txSummary, setTxSummary] = useState({ + collateral: '0', + cRatio: '0', + accountId: '', + }); + + const cRatio = data?.cratio.eq(0) + ? '0' + : wei(1) + .div(data?.cratio || wei(1)) + .mul(100) + .toString(2); + + const handleSubmit = useCallback(() => { + setTxSummary({ + cRatio, + collateral: data?.collateral?.toString(2) || '0', + accountId, + }); + + migrate(); + }, [accountId, cRatio, data?.collateral, migrate]); + + if (isSuccess) { + return ( + onConfirm(txSummary.accountId)} + cRatio={txSummary.cRatio} + collateral={txSummary.collateral} + accountId={txSummary.accountId} + /> + ); + } + + return ( + + Summary of your migration + + + + C-Ratio + + {cRatio}% + + {/* HEALTHY */} + + + + + + Warning: if your c-ratio is below V3 liquidation ratio (105%), your account will be{' '} + + liquidated + {' '} + during the migration. We recommend to commence this migration only if you have a healthy + c-ratio. + + + + + + + SNX Collateral{' '} + + + + + + {data?.collateral?.toString(2)} SNX + {snxPrice?.gt(0) && <> (${snxPrice.mul(data?.collateral).toString(2)})} + + + + Balance + + {data?.balance?.toString(2)} SNX + {snxPrice?.gt(0) && <> (${snxPrice.mul(data?.balance).toString(2)})} + + + + + Escrowed{' '} + + + + + + {data?.collateral?.sub(data?.balance)?.toString(2)} SNX + {snxPrice?.gt(0) && ( + <> (${snxPrice.mul(data?.collateral?.sub(data?.balance)).toString(2)}) + )} + + + + + Debt{' '} + + Your debt amount will be the same on V3. Debt is however now determined by the + collateral deposited. + + } + > + + + + ${data?.debt?.toString(2)} + + + + + setIsUnderstanding(e.currentTarget.checked)}> + I understand that this action cannot be undone + + + + + Estimated Gas + + {transaction !== undefined && ( + <> + {transaction?.gasLimit.gt(0) ? ( + + + + {ethPrice?.gt(0) && ( + <> +  ($ + {ethPrice + .mul( + formatEther( + transaction?.gasLimit.mul(transaction.gasPrice || 1).toString() || 0 + ) + ) + .toString(2)} + ) + + )} + + ) : ( + Transaction error occured, please seek support + )} + + )} + + + + {!isLoading ? ( + <> + + + + ) : ( + + + Loading + + )} + + ); +}; diff --git a/liquidity/ui/src/components/Permissions/TransferOwnershipModal.tsx b/liquidity/ui/src/components/Permissions/TransferOwnershipModal.tsx index afbbec56e..ae8bb9778 100644 --- a/liquidity/ui/src/components/Permissions/TransferOwnershipModal.tsx +++ b/liquidity/ui/src/components/Permissions/TransferOwnershipModal.tsx @@ -69,7 +69,7 @@ export function TransferOwnershipModal({ /> - + This action cannot be undone diff --git a/liquidity/ui/src/components/Pools/Balloon.tsx b/liquidity/ui/src/components/Pools/Balloon.tsx new file mode 100644 index 000000000..b2882ae01 --- /dev/null +++ b/liquidity/ui/src/components/Pools/Balloon.tsx @@ -0,0 +1,494 @@ +import { Icon, IconProps } from '@chakra-ui/react'; + +export const Balloon = ({ ...props }: IconProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/liquidity/ui/src/components/Pools/PoolCards/PoolCard.tsx b/liquidity/ui/src/components/Pools/PoolCards/PoolCard.tsx index cf252d5b0..46e442e16 100644 --- a/liquidity/ui/src/components/Pools/PoolCards/PoolCard.tsx +++ b/liquidity/ui/src/components/Pools/PoolCards/PoolCard.tsx @@ -1,38 +1,7 @@ -import { - Flex, - Button, - Text, - Heading, - Fade, - Tag, - Tr, - Table, - TableContainer, - Tbody, - Td, - Th, - Thead, - Link, -} from '@chakra-ui/react'; -import { useNavigate, useSearchParams, Link as ReactRouterLink } from 'react-router-dom'; -import { ZEROWEI } from '../../../utils/constants'; -import { - ARBITRUM, - MAINNET, - Network, - NetworkIcon, - useNetwork, - useWallet, -} from '@snx-v3/useBlockchain'; -import { compactInteger } from 'humanize-plus'; -import Wei, { wei } from '@synthetixio/wei'; +import { Network } from '@snx-v3/useBlockchain'; import { BigNumberish } from 'ethers'; -import { TokenIcon } from '../../TokenIcon'; import { CollateralType } from '@snx-v3/useCollateralTypes'; -import { Sparkles } from '@snx-v3/icons'; -import { formatNumber, formatNumberToUsd } from '@snx-v3/formatters'; -import { formatApr } from '../CollateralSection'; -import { Tooltip } from '@snx-v3/Tooltip'; +import { PoolRow } from './PoolRow'; interface CollateralTypeWithDeposited extends CollateralType { collateralDeposited: string; @@ -54,7 +23,6 @@ export interface PoolCardProps { cumulativePnl: number; collateralAprs: any[]; }; - balances?: Wei[]; rewardsPayoutTokens?: string[]; } @@ -64,385 +32,21 @@ export const PoolCard = ({ apr, collateralTypes, collateralPrices, - balances, - rewardsPayoutTokens, }: PoolCardProps) => { - const navigate = useNavigate(); - const [queryParams] = useSearchParams(); - - const { network: currentNetwork, setNetwork } = useNetwork(); - const { connect } = useWallet(); - - const vaultTVL = collateralTypes?.reduce((acc, type) => { - const price = wei( - collateralPrices?.find((price) => price.symbol.toUpperCase() === type.symbol.toUpperCase()) - ?.price ?? 0 - ); - const amount = wei(type.collateralDeposited, Number(type.decimals), true); - const value = price.mul(amount); - return acc.add(value); - }, ZEROWEI); - return ( - - - - - navigate(`/pools/${network.id}/${pool.id}`)} - > - - {pool?.name} - - - - {network.label} Network - - - {[MAINNET.id, ARBITRUM.id].includes(network.id) && ( - - Borrow Interest-Free - - )} - - - - - TVL - - - {(vaultTVL?.toNumber() && `$${compactInteger(vaultTVL.toNumber(), 2)}`) || '-'} - - - - - APR - - - {apr.combinedApr > 0 - ? `${network.id === ARBITRUM.id ? 'Up to ' : ''}${apr.combinedApr - .toFixed(2) - ?.concat('%')}` - : '-'} - - - - Rewards Available - - {rewardsPayoutTokens?.map((token) => ( - - - - {token} - - - ))} - - } - > - - - - - - - Details - - - - - - - - - - - - - - - - {collateralTypes?.map((type, index) => { - const price = wei( - collateralPrices?.find( - (price) => price.symbol.toUpperCase() === type.symbol.toUpperCase() - )?.price ?? 0 - ); - - const collateralApr = apr.collateralAprs.find( - (apr) => apr.collateralType === type.tokenAddress.toLowerCase() - ) || { apr28d: 0, apr28dRewards: 0, apr28dPnl: 0 }; - - const { apr28d, apr28dRewards, apr28dPnl } = collateralApr; - - const onClick = async () => { - try { - if (!currentNetwork) { - connect(); - return; - } - - if (currentNetwork.id !== network.id) { - if (!(await setNetwork(network.id))) { - return; - } - } - - queryParams.set('manageAction', 'deposit'); - navigate({ - pathname: `/positions/${type.symbol}/${pool.id}`, - search: queryParams.toString(), - }); - } catch (error) {} - }; - - const buttonText = !currentNetwork - ? 'Connect Wallet' - : currentNetwork.id !== network.id - ? 'Switch Network' - : 'Deposit'; - - return ( - - - - - - - - ); - })} - -
- Collateral - - Wallet Balance - - TVL - - APR -
- - - - - {type.symbol} - - - {type.name} - - - - - - - {balances && balances[index] - ? formatNumberToUsd(balances[index].mul(price).toNumber()) - : '-'} - - - {balances && balances[index] - ? formatNumber(balances[index].toNumber()) - : ''}{' '} - {type.symbol} - - - - - {price - ? formatNumberToUsd( - wei(type.collateralDeposited, Number(type.decimals), true) - .mul(price) - .toNumber() - ) - : 0} - - - - {formatApr(apr28d * 100, network?.id)} - - - - Total APR: - - {formatApr(apr28d * 100, network?.id)} - - - Performance: - {formatApr(apr28dPnl * 100, network?.id)} - - - Rewards: - {formatApr(apr28dRewards * 100, network?.id)} - - - } - > - - - - - - - -
-
-
- + <> + {collateralTypes?.map((collateralType) => { + return ( + + ); + })} + ); }; diff --git a/liquidity/ui/src/components/Pools/PoolCards/PoolCardsLoading.tsx b/liquidity/ui/src/components/Pools/PoolCards/PoolCardsLoading.tsx index 156e2593a..edef36b0e 100644 --- a/liquidity/ui/src/components/Pools/PoolCards/PoolCardsLoading.tsx +++ b/liquidity/ui/src/components/Pools/PoolCards/PoolCardsLoading.tsx @@ -1,87 +1,45 @@ -import { Flex, Skeleton, Heading, Divider, SkeletonCircle, Text } from '@chakra-ui/react'; -import { NetworkIcon } from '@snx-v3/useBlockchain'; +import { Flex, Skeleton } from '@chakra-ui/react'; -export const PoolCardsLoading = () => { - return ( - <> - {[1, 2, 3].map((i) => ( - - - - - - {/* Used for loading sizing */} - USDC Andromeda Yield - - - - - - Ethirum Network - - - - - - - - TVL - - - 100M - - - - - APR - - - 42% - - - - - - - - {[1, 2, 3, 4, 5].map((i) => ( - - {i === 1 && ( - - )} - - - ))} +export const PoolCardsLoading = () => ( + <> + {Array.from(Array(6).keys()).map((i) => ( + + + + + + - ))} - - ); -}; + + + + + + + + + + + + + + + + + + + + ))} + +); diff --git a/liquidity/ui/src/components/Pools/PoolCards/PoolRow.tsx b/liquidity/ui/src/components/Pools/PoolCards/PoolRow.tsx new file mode 100644 index 000000000..9e06d2dbf --- /dev/null +++ b/liquidity/ui/src/components/Pools/PoolCards/PoolRow.tsx @@ -0,0 +1,253 @@ +import { Flex, Button, Text, Fade } from '@chakra-ui/react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { + MAINNET, + Network, + NetworkIcon, + SEPOLIA, + useNetwork, + useWallet, +} from '@snx-v3/useBlockchain'; +import { wei } from '@synthetixio/wei'; +import { BigNumberish } from 'ethers'; +import { TokenIcon } from '../../TokenIcon'; +import { CollateralType } from '@snx-v3/useCollateralTypes'; +import { Sparkles } from '@snx-v3/icons'; +import { formatNumber, formatNumberToUsd } from '@snx-v3/formatters'; +import { formatApr } from '../CollateralSection'; +import { Tooltip } from '@snx-v3/Tooltip'; +import { useTokenBalanceForChain } from '@snx-v3/useTokenBalance'; +import { getSpotMarketId, isBaseAndromeda } from '@snx-v3/isBaseAndromeda'; +import { useGetWrapperToken } from '@snx-v3/useGetUSDTokens'; +import { ZEROWEI } from '../../../utils/constants'; +import { MigrationBanner } from '../../Migration/MigrationBanner'; +import { Specifics } from './Specifics'; + +interface CollateralTypeWithDeposited extends CollateralType { + collateralDeposited: string; +} + +export interface Props { + collateralType: CollateralTypeWithDeposited; + pool: { + name: string; + id: string; + }; + network: Network; + collateralPrices?: { + symbol: string; + price: BigNumberish; + }[]; + apr: { + combinedApr: number; + cumulativePnl: number; + collateralAprs: any[]; + }; +} + +export const PoolRow = ({ pool, network, apr, collateralType, collateralPrices }: Props) => { + const { data: wrapperToken } = useGetWrapperToken( + getSpotMarketId(collateralType.symbol), + network + ); + const isBase = isBaseAndromeda(network?.id, network?.preset); + // TODO: This will need refactoring + const balanceAddress = isBase ? wrapperToken : collateralType?.tokenAddress; + + const { data: balance } = useTokenBalanceForChain(balanceAddress, network); + const navigate = useNavigate(); + const [queryParams] = useSearchParams(); + + const { network: currentNetwork, setNetwork } = useNetwork(); + const { connect } = useWallet(); + + const price = wei( + collateralPrices?.find( + (price) => price.symbol.toUpperCase() === collateralType.symbol.toUpperCase() + )?.price || ZEROWEI + ); + + const collateralApr = apr.collateralAprs.find( + (apr) => apr.collateralType === collateralType.tokenAddress.toLowerCase() + ) || { apr28d: 0, apr28dRewards: 0, apr28dPnl: 0 }; + + const { apr28d, apr28dRewards, apr28dPnl } = collateralApr; + + const onClick = async () => { + try { + if (!currentNetwork) { + connect(); + return; + } + + if (currentNetwork.id !== network.id) { + if (!(await setNetwork(network.id))) { + return; + } + } + + queryParams.set('manageAction', 'deposit'); + navigate({ + pathname: `/positions/${collateralType.symbol}/${pool.id}`, + search: queryParams.toString(), + }); + } catch (error) {} + }; + + const buttonText = !currentNetwork ? 'Connect Wallet' : 'Deposit'; + + return ( + + + + + + + + + + + {collateralType.symbol} + + + {network.name} Network + + + + + + {balance ? formatNumberToUsd(balance.mul(price).toNumber()) : '-'} + + + {balance ? formatNumber(balance.toNumber()) : ''} {collateralType.symbol} + + + + + SC Pool + + + Spartan Council + + + + + {price + ? formatNumberToUsd( + wei(collateralType.collateralDeposited, Number(collateralType.decimals), true) + .mul(price) + .toNumber() + ) + : 0} + + + + + {formatApr(apr28d * 100, network?.id)} + + + + Total APR: + + {formatApr(apr28d * 100, network?.id)} + + + Performance: + {formatApr(apr28dPnl * 100, network?.id)} + + + Rewards: + {formatApr(apr28dRewards * 100, network?.id)} + + + } + > + + + + + + + + + + + + + + + {[MAINNET.id, SEPOLIA.id].includes(network.id) && ( + + )} + + + ); +}; diff --git a/liquidity/ui/src/components/Pools/PoolCards/Specifics.tsx b/liquidity/ui/src/components/Pools/PoolCards/Specifics.tsx new file mode 100644 index 000000000..c215f2569 --- /dev/null +++ b/liquidity/ui/src/components/Pools/PoolCards/Specifics.tsx @@ -0,0 +1,132 @@ +import { Flex, Text, Tooltip } from '@chakra-ui/react'; +import { isBaseAndromeda } from '@snx-v3/isBaseAndromeda'; +import { Network } from '@snx-v3/useBlockchain'; +import { CollateralType } from '@snx-v3/useCollateralTypes'; + +export const Specifics: React.FC<{ + network?: Network; + isToros?: boolean; + collateralType?: CollateralType; +}> = ({ network, isToros, collateralType }) => { + const isBase = isBaseAndromeda(network?.id, network?.preset); + + if (isToros) { + return ( + + + + + + + + + + + + + + + + + + ); + } + + if (isBase) { + if (collateralType?.symbol.toUpperCase() === 'stataUSDC'.toUpperCase()) { + return ( + + + + + + ); + } + + return ( + + N/A + + ); + } + + return ( + + + + + + ); +}; diff --git a/liquidity/ui/src/components/Pools/PoolCards/TorosPoolCard.tsx b/liquidity/ui/src/components/Pools/PoolCards/TorosPoolCard.tsx index aa4403241..1fcbbf613 100644 --- a/liquidity/ui/src/components/Pools/PoolCards/TorosPoolCard.tsx +++ b/liquidity/ui/src/components/Pools/PoolCards/TorosPoolCard.tsx @@ -1,25 +1,12 @@ -import { - Flex, - Heading, - Tag, - Text, - Button, - Box, - Link, - Fade, - TableContainer, - Th, - Table, - Thead, - Tr, - Tbody, - Td, - Icon, - IconProps, -} from '@chakra-ui/react'; -import { NetworkIcon } from '@snx-v3/useBlockchain'; +import { Flex, Text, Button, Link, Fade, Icon, IconProps } from '@chakra-ui/react'; +import { BASE_ANDROMEDA, NetworkIcon } from '@snx-v3/useBlockchain'; import { TokenIcon } from '../../TokenIcon'; -import { Tooltip } from '@snx-v3/Tooltip'; +import { useGetWrapperToken } from '@snx-v3/useGetUSDTokens'; +import { getSpotMarketId } from '@snx-v3/isBaseAndromeda'; +import { useTokenBalanceForChain } from '@snx-v3/useTokenBalance'; +import { formatNumberToUsd } from '@snx-v3/formatters'; +import { formatNumber } from 'humanize-plus'; +import { Specifics } from './Specifics'; interface TorosPoolCardProps { tvl: string; @@ -27,219 +14,137 @@ interface TorosPoolCardProps { } export function TorosPoolCard({ tvl, apy }: TorosPoolCardProps) { + const { data: wrapperToken } = useGetWrapperToken(getSpotMarketId('USDC'), BASE_ANDROMEDA); + const { data: balance } = useTokenBalanceForChain(wrapperToken, BASE_ANDROMEDA); + return ( - - {/* For overlay to not show gradient */} - - - - - - - USDC Andromeda Yield - - - - - Base Network - - - - - Auto Compound - - - - - - - - - - - TVL - - - ${tvl} - - - - - APY - - - Up to {apy}% - - - + + + + - - - - - - - - - - - - - - - - -
- Collateral - - APY -
- - - - - USDC - - - USD Coin - - - - - - {apy}% - - - - - -
-
- - - by + + + USDC + + + Base Network - -
+ + + {balance ? formatNumberToUsd(balance.toNumber()) : '-'} + + + {balance ? formatNumber(balance.toNumber()) : ''} USDC + + + + + Toros Yield Vault + + + Toros + + + + + ${tvl} + + + + + {apy}% + + + + + + + + + + +
); } -const TorosIcon = () => ( - - - - -); - const LinkOffIcon = ({ ...props }: IconProps) => ( @@ -255,27 +160,3 @@ const LinkOffIcon = ({ ...props }: IconProps) => ( ); - -const OpSuperfestIcon = ({ ...props }: IconProps) => ( - - - - - - - - - - - - -); diff --git a/liquidity/ui/src/components/Pools/PoolsList.tsx b/liquidity/ui/src/components/Pools/PoolsList.tsx index 2c974047c..1ea60d05b 100644 --- a/liquidity/ui/src/components/Pools/PoolsList.tsx +++ b/liquidity/ui/src/components/Pools/PoolsList.tsx @@ -1,22 +1,20 @@ import { useReducer, useMemo } from 'react'; -import { Flex, Heading, Text, Icon, IconProps } from '@chakra-ui/react'; +import { Flex, Heading, Text, Divider } from '@chakra-ui/react'; import { ChainFilter, CollateralFilter, PoolCard } from './'; import { TorosPoolCard } from './PoolCards/TorosPoolCard'; import { usePoolsList } from '@snx-v3/usePoolsList'; import { PoolCardsLoading } from './PoolCards/PoolCardsLoading'; import { useOfflinePrices } from '@snx-v3/useCollateralPriceUpdates'; import { CollateralType, useCollateralTypes } from '@snx-v3/useCollateralTypes'; -import { ARBITRUM, BASE_ANDROMEDA } from '@snx-v3/useBlockchain'; -import { STATA_BASE_MARKET, USDC_BASE_MARKET, isBaseAndromeda } from '@snx-v3/isBaseAndromeda'; -import { useTokenBalances } from '@snx-v3/useTokenBalance'; -import { useGetUSDTokens, useGetWrapperToken } from '@snx-v3/useGetUSDTokens'; +import { ARBITRUM, BASE_ANDROMEDA, MAINNET } from '@snx-v3/useBlockchain'; +import { isBaseAndromeda } from '@snx-v3/isBaseAndromeda'; import { useRewardsDistributors } from '@snx-v3/useRewardsDistributors'; import { useOraclePrice } from '@snx-v3/useOraclePrice'; +import { Balloon } from './Balloon'; export const PoolsList = () => { const [state, dispatch] = useReducer(poolsReducer, { collateral: [], chain: [] }); const { data, isLoading: isPoolsListLoading } = usePoolsList(); - const { data: usdTokens } = useGetUSDTokens(BASE_ANDROMEDA); const { data: BaseCollateralTypes, isLoading: isBaseCollateralLoading } = useCollateralTypes( false, @@ -28,17 +26,22 @@ export const PoolsList = () => { ARBITRUM ); + const { data: MainnetCollateralTypes, isLoading: isMainCollateralLoading } = useCollateralTypes( + false, + MAINNET + ); + const allCollaterals: CollateralType[] = useMemo(() => { - if (!BaseCollateralTypes || !ArbitrumCollateralTypes) { + if (!BaseCollateralTypes || !ArbitrumCollateralTypes || !MainnetCollateralTypes) { return []; } // We want to filter out assets that don't have a pyth price feed - return BaseCollateralTypes.concat(ArbitrumCollateralTypes).filter( - (item) => item.displaySymbol !== 'stataUSDC' - ); - }, [ArbitrumCollateralTypes, BaseCollateralTypes]); + return BaseCollateralTypes.concat(ArbitrumCollateralTypes) + .concat(MainnetCollateralTypes) + .filter((item) => item.displaySymbol !== 'stataUSDC'); + }, [ArbitrumCollateralTypes, BaseCollateralTypes, MainnetCollateralTypes]); const { data: collateralPrices, isLoading: isLoadingCollateralPrices } = useOfflinePrices( allCollaterals.map((item) => ({ @@ -56,21 +59,6 @@ export const PoolsList = () => { BASE_ANDROMEDA ); - // Arb Balances - const { data: ArbitrumTokenBalances, isLoading: isArbitrumBalancesLoading } = useTokenBalances( - ArbitrumCollateralTypes?.map((item) => item.tokenAddress) || [], - ARBITRUM - ); - - const { data: USDC_Address } = useGetWrapperToken(USDC_BASE_MARKET); - const { data: STATA_Address } = useGetWrapperToken(STATA_BASE_MARKET); - - // Base Balances - const { data: BaseTokenBalances, isLoading: isBaseBalancesLoading } = useTokenBalances( - usdTokens?.USDC && stata ? [USDC_Address, STATA_Address] : [], - BASE_ANDROMEDA - ); - // Arb Rewards const { data: ArbitrumRewards, isLoading: isArbitrumRewardsLoading } = useRewardsDistributors(ARBITRUM); @@ -79,6 +67,9 @@ export const PoolsList = () => { const { data: BaseRewards, isLoading: isBaseRewardsLoading } = useRewardsDistributors(BASE_ANDROMEDA); + // Mainnet Rewards + const { data: MainRewards, isLoading: isMainRewardsLoading } = useRewardsDistributors(MAINNET); + const { collateral, chain } = state; const showToros = @@ -90,10 +81,10 @@ export const PoolsList = () => { isLoadingCollateralPrices || isBaseCollateralLoading || isArbCollateralLoading || - isArbitrumBalancesLoading || - isBaseBalancesLoading || + isMainCollateralLoading || isArbitrumRewardsLoading || isBaseRewardsLoading || + isMainRewardsLoading || isStataPriceLoading; const filteredPools = useMemo(() => { @@ -105,9 +96,21 @@ export const PoolsList = () => { tokenAddress: collateral_type.id, })); - const collateralTypes = ( - network.id === ARBITRUM.id ? ArbitrumCollateralTypes : BaseCollateralTypes - )?.map((item) => ({ + let collaterals: typeof ArbitrumCollateralTypes = []; + let rewardsDistributors: any = {}; + + if (network.id === ARBITRUM.id) { + collaterals = ArbitrumCollateralTypes; + rewardsDistributors = ArbitrumRewards; + } else if (network.id === BASE_ANDROMEDA.id) { + collaterals = BaseCollateralTypes; + rewardsDistributors = BaseRewards; + } else if (network.id === MAINNET.id) { + collaterals = MainnetCollateralTypes; + rewardsDistributors = MainRewards; + } + + const collateralTypes = collaterals?.map((item) => ({ ...item, collateralDeposited: collateralDeposited.find( @@ -115,16 +118,12 @@ export const PoolsList = () => { )?.collateralDeposited || '0', })); - const balances = network.id === ARBITRUM.id ? ArbitrumTokenBalances : BaseTokenBalances; - const rewardsDistributors = network.id === ARBITRUM.id ? ArbitrumRewards : BaseRewards; - return { network, poolInfo, apr, collateralDeposited, collateralTypes, - balances, rewardsDistributors, }; }) @@ -156,15 +155,15 @@ export const PoolsList = () => { }) || [] ); }, [ + data?.synthetixPools, ArbitrumCollateralTypes, + ArbitrumRewards, BaseCollateralTypes, + BaseRewards, + MainnetCollateralTypes, + MainRewards, chain, collateral, - data?.synthetixPools, - ArbitrumTokenBalances, - BaseTokenBalances, - ArbitrumRewards, - BaseRewards, ]); const allCollateralPrices = useMemo(() => { @@ -182,38 +181,112 @@ export const PoolsList = () => { - + + + + + Collateral/Network + + + + Wallet Balance + + + + Pool / Owner + + + + TVL + + + + APY/APR + + + Specifics + + + {isLoading && } {!isLoading && showToros && ( )} {!isLoading && - filteredPools.map( - ({ network, poolInfo, apr, collateralTypes, balances, rewardsDistributors }) => { - const { pool } = poolInfo[0]; - - const rewardsPayoutTokens = [ - ...new Set( - rewardsDistributors?.map(({ payoutToken }: any) => - payoutToken.symbol.toUpperCase() - ) - ), - ] as string[]; - - return ( - - ); - } - )} + filteredPools.map(({ network, poolInfo, apr, collateralTypes, rewardsDistributors }) => { + const { pool } = poolInfo[0]; + + const rewardsPayoutTokens = [ + ...new Set( + rewardsDistributors?.map(({ payoutToken }: any) => payoutToken.symbol.toUpperCase()) + ), + ] as string[]; + + return ( + + ); + })} {!isLoading && !filteredPools.length && ( @@ -307,496 +380,3 @@ function poolsReducer(state: PoolsFilterState, action: PoolsFilterAction): Pools return state; } } - -const Balloon = ({ ...props }: IconProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/liquidity/ui/src/components/Positions/PositionsTable/PositionsRow.tsx b/liquidity/ui/src/components/Positions/PositionsTable/PositionsRow.tsx index 403e619d8..789d87865 100644 --- a/liquidity/ui/src/components/Positions/PositionsTable/PositionsRow.tsx +++ b/liquidity/ui/src/components/Positions/PositionsTable/PositionsRow.tsx @@ -10,6 +10,7 @@ import { useTokenPrice } from '../../../../../lib/useTokenPrice'; import { DebtAmount } from './DebtAmount'; import { useRewards } from '@snx-v3/useRewards'; import { useMemo } from 'react'; +import { CRatioAmount } from '../../CRatioBar/CRatioAmount'; interface PositionRow extends LiquidityPositionType { final: boolean; isBase: boolean; @@ -176,7 +177,7 @@ export function PositionRow({ - {debt.gt(0) ? (cRatio.toNumber() * 100).toFixed(2) + '%' : 'Infinite'} + -   Max +  Max )} diff --git a/liquidity/ui/src/components/Stats/StatsList.tsx b/liquidity/ui/src/components/Stats/StatsList.tsx index 0c2bc2cc1..9c185f0e6 100644 --- a/liquidity/ui/src/components/Stats/StatsList.tsx +++ b/liquidity/ui/src/components/Stats/StatsList.tsx @@ -45,8 +45,9 @@ export const StatsList = () => { [] : accountCollaterals?.map((collateral) => collateral.tokenAddress) || []; - const { data: userTokenBalances, isLoading: tokenBalancesIsLoading } = - useTokenBalances(collateralAddresses); + const { data: userTokenBalances, isLoading: tokenBalancesIsLoading } = useTokenBalances( + collateralAddresses.filter((a) => !!a) + ); const associatedUserBalances = userTokenBalances?.map((balance, index) => { return { diff --git a/liquidity/ui/src/components/TransactionSummary/TransactionSummary.tsx b/liquidity/ui/src/components/TransactionSummary/TransactionSummary.tsx index abaf26c4a..a1f47169f 100644 --- a/liquidity/ui/src/components/TransactionSummary/TransactionSummary.tsx +++ b/liquidity/ui/src/components/TransactionSummary/TransactionSummary.tsx @@ -12,10 +12,10 @@ export const TransactionSummary = ({ items, ...props }: Props) => ( {items.map(({ value, label }, i) => ( - + {label} - + {value} diff --git a/liquidity/ui/src/components/Undelegate/Undelegate.tsx b/liquidity/ui/src/components/Undelegate/Undelegate.tsx index 84e1ce8de..300cd7116 100644 --- a/liquidity/ui/src/components/Undelegate/Undelegate.tsx +++ b/liquidity/ui/src/components/Undelegate/Undelegate.tsx @@ -88,7 +88,8 @@ export const UndelegateUi: FC<{ isAnyMarketLocked === true || collateralChange.gte(0) || !isValidLeftover || - overAvailableBalance; + overAvailableBalance || + (currentDebt?.gt(0) && isBase); const txSummaryItems = useMemo(() => { const items = [ @@ -188,7 +189,7 @@ export const UndelegateUi: FC<{ - + Credit capacity reached @@ -201,7 +202,7 @@ export const UndelegateUi: FC<{ - + @@ -228,7 +229,7 @@ export const UndelegateUi: FC<{ in={collateralChange.abs().gt(0) && isValidLeftover && !isRunning && maxWithdrawable?.gt(0)} animateOpacity > - + You already have unlocked. @@ -246,6 +247,24 @@ export const UndelegateUi: FC<{ + + + + + To Unlock this amount, you need to   + navigate('repay')} + cursor="pointer" + as="span" + textDecoration="underline" + > + repay + {' '} + to your position + + + + @@ -322,7 +341,7 @@ export const Undelegate = ({ liquidityPosition }: { liquidityPosition?: Liquidit // if debt is negative it's actually credit, which means we can undelegate all collateral if (newDebt.lte(0)) return collateralAmount; - const minCollateralRequired = newDebt.mul(collateralType.issuanceRatioD18); + const minCollateralRequired = newDebt.mul(collateralType.liquidationRatioD18); if (collateralValue.lt(minCollateralRequired)) // If you're below issuance ratio, you can't withdraw anything diff --git a/liquidity/ui/src/components/WatchAccountBanner/WatchAccountBanner.tsx b/liquidity/ui/src/components/WatchAccountBanner/WatchAccountBanner.tsx index 43141e454..d484a4b46 100644 --- a/liquidity/ui/src/components/WatchAccountBanner/WatchAccountBanner.tsx +++ b/liquidity/ui/src/components/WatchAccountBanner/WatchAccountBanner.tsx @@ -26,7 +26,7 @@ export const WatchAccountBanner: FC = () => { if (!activeWallet) { return ( - +
@@ -54,7 +54,7 @@ export const WatchAccountBanner: FC = () => { !accounts.find((a) => a === accountId) ) { return ( - + You are currently watching Account #{accountId} diff --git a/liquidity/ui/src/components/Withdraw/Withdraw.tsx b/liquidity/ui/src/components/Withdraw/Withdraw.tsx index 16a4cc2d1..87b74c815 100644 --- a/liquidity/ui/src/components/Withdraw/Withdraw.tsx +++ b/liquidity/ui/src/components/Withdraw/Withdraw.tsx @@ -84,7 +84,7 @@ const WithdrawUi: FC<{ - + You will be able to withdraw assets in {hours}H{minutes}M. Any account activity will @@ -94,7 +94,7 @@ const WithdrawUi: FC<{ - + You can now withdraw @@ -103,7 +103,7 @@ const WithdrawUi: FC<{ - + You cannot Withdraw more {!isDebtWithdrawal ? 'Collateral' : ''} than your Unlocked diff --git a/liquidity/ui/src/layouts/Default/Header.tsx b/liquidity/ui/src/layouts/Default/Header.tsx index 69176c07b..cdd575bae 100644 --- a/liquidity/ui/src/layouts/Default/Header.tsx +++ b/liquidity/ui/src/layouts/Default/Header.tsx @@ -3,8 +3,11 @@ import { NavLink as RouterLink, useLocation } from 'react-router-dom'; import { NetworkController } from './NetworkController'; import { useEffect } from 'react'; import { Logo, LogoIcon } from '@snx-v3/icons'; +import { MigrateUSDButton } from '../../components/MigrateUSD/MigrateUSDButton'; +import { MAINNET, SEPOLIA, useNetwork } from '@snx-v3/useBlockchain'; export default function Header() { + const { network } = useNetwork(); const { onClose } = useDisclosure(); const location = useLocation(); @@ -85,6 +88,7 @@ export default function Header() { {/* Hide balance */} {/* */} + {network && [MAINNET.id, SEPOLIA.id] && } diff --git a/liquidity/ui/src/pages/Account/Settings.tsx b/liquidity/ui/src/pages/Account/Settings.tsx index 49aee587f..19c4ca77c 100644 --- a/liquidity/ui/src/pages/Account/Settings.tsx +++ b/liquidity/ui/src/pages/Account/Settings.tsx @@ -42,7 +42,7 @@ export function Settings() { mb={6} color="gray.50" fontSize="1.5rem" - data-cy="liquidity-dashboard" + data-cy="account-setting" > Account Settings diff --git a/liquidity/ui/src/pages/Dashboard.tsx b/liquidity/ui/src/pages/Dashboard.tsx index 205c635b5..89ace50d2 100644 --- a/liquidity/ui/src/pages/Dashboard.tsx +++ b/liquidity/ui/src/pages/Dashboard.tsx @@ -2,26 +2,33 @@ import { Flex, Heading } from '@chakra-ui/react'; import { Helmet } from 'react-helmet'; import { PositionsList, StatsList } from '../components'; import { WatchAccountBanner } from '../components/WatchAccountBanner/WatchAccountBanner'; +import { MAINNET, SEPOLIA, useNetwork } from '@snx-v3/useBlockchain'; +import { MigrationBanner } from '../components/Migration/MigrationBanner'; export function Dashboard() { + const { network } = useNetwork(); return ( <> Synthetix Liquidity V3 - - + + {network && [MAINNET.id, SEPOLIA.id].includes(network.id) && ( + + )} + + Dashboard + diff --git a/liquidity/ui/src/pages/Home.tsx b/liquidity/ui/src/pages/Home.tsx index c4ebc27ff..cb78ed6ac 100644 --- a/liquidity/ui/src/pages/Home.tsx +++ b/liquidity/ui/src/pages/Home.tsx @@ -9,7 +9,7 @@ export function Home() { Synthetix Liquidity V3 - + { - const assigned = asset.collateral.availableCollateral; - const wallet = asset.balance.mul(asset.price); - return assigned.add(wallet); - }) + ?.map((asset) => asset.collateral.availableCollateral.add(asset.balance).mul(asset.price)) .reduce((prev, cur) => prev.add(cur), ZEROWEI) .toNumber() .toFixed(2); diff --git a/yarn.lock b/yarn.lock index 8967bc330..9c9c6ec91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3347,7 +3347,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.5.0": +"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.5.0, @ethersproject/providers@npm:^5.7.2": version: 5.7.2 resolution: "@ethersproject/providers@npm:5.7.2" dependencies: @@ -5578,15 +5578,19 @@ __metadata: "@snx-v3/useEthBalance": "workspace:*" "@snx-v3/useGasSpeed": "workspace:*" "@snx-v3/useGetUSDTokens": "workspace:*" + "@snx-v3/useLegacyMarket": "workspace:*" "@snx-v3/useLiquidityPosition": "workspace:*" "@snx-v3/useLiquidityPositions": "workspace:*" "@snx-v3/useLocalStorage": "workspace:*" "@snx-v3/useManagePermissions": "workspace:*" + "@snx-v3/useMigrate": "workspace:*" + "@snx-v3/useMigrateUSD": "workspace:*" "@snx-v3/useOraclePrice": "workspace:*" "@snx-v3/useParams": "workspace:*" "@snx-v3/usePoolConfiguration": "workspace:*" "@snx-v3/usePoolData": "workspace:*" "@snx-v3/usePoolsList": "workspace:*" + "@snx-v3/useRates": "workspace:*" "@snx-v3/useRepay": "workspace:*" "@snx-v3/useRewards": "workspace:^" "@snx-v3/useRewardsDistributors": "workspace:*" @@ -5594,8 +5598,11 @@ __metadata: "@snx-v3/useTokenBalance": "workspace:*" "@snx-v3/useTransferAccountId": "workspace:*" "@snx-v3/useTransferableSynthetix": "workspace:*" + "@snx-v3/useUSDProxy": "workspace:*" "@snx-v3/useUndelegate": "workspace:*" "@snx-v3/useUndelegateBaseAndromeda": "workspace:*" + "@snx-v3/useV2Position": "workspace:*" + "@snx-v3/useV2sUSD": "workspace:*" "@snx-v3/useVaultsData": "workspace:*" "@snx-v3/validatePosition": "workspace:*" "@synthetixio/safe-import": "workspace:*" @@ -6214,6 +6221,18 @@ __metadata: languageName: unknown linkType: soft +"@snx-v3/useLegacyMarket@workspace:*, @snx-v3/useLegacyMarket@workspace:liquidity/lib/useLegacyMarket": + version: 0.0.0-use.local + resolution: "@snx-v3/useLegacyMarket@workspace:liquidity/lib/useLegacyMarket" + dependencies: + "@ethersproject/contracts": "npm:^5.7.0" + "@snx-v3/contracts": "workspace:*" + "@snx-v3/useBlockchain": "workspace:*" + "@tanstack/react-query": "npm:^5.8.3" + react: "npm:^18.2.0" + languageName: unknown + linkType: soft + "@snx-v3/useLiquidityPosition@workspace:*, @snx-v3/useLiquidityPosition@workspace:liquidity/lib/useLiquidityPosition": version: 0.0.0-use.local resolution: "@snx-v3/useLiquidityPosition@workspace:liquidity/lib/useLiquidityPosition" @@ -6284,6 +6303,36 @@ __metadata: languageName: unknown linkType: soft +"@snx-v3/useMigrate@workspace:*, @snx-v3/useMigrate@workspace:liquidity/lib/useMigrate": + version: 0.0.0-use.local + resolution: "@snx-v3/useMigrate@workspace:liquidity/lib/useMigrate" + dependencies: + "@snx-v3/useBlockchain": "workspace:*" + "@snx-v3/useGasOptions": "workspace:*" + "@snx-v3/useGasPrice": "workspace:*" + "@snx-v3/useGasSpeed": "workspace:*" + "@snx-v3/useLegacyMarket": "workspace:*" + "@synthetixio/wei": "npm:^2.74.4" + "@tanstack/react-query": "npm:^5.8.3" + react: "npm:^18.2.0" + languageName: unknown + linkType: soft + +"@snx-v3/useMigrateUSD@workspace:*, @snx-v3/useMigrateUSD@workspace:liquidity/lib/useMigrateUSD": + version: 0.0.0-use.local + resolution: "@snx-v3/useMigrateUSD@workspace:liquidity/lib/useMigrateUSD" + dependencies: + "@snx-v3/useBlockchain": "workspace:*" + "@snx-v3/useGasOptions": "workspace:*" + "@snx-v3/useGasPrice": "workspace:*" + "@snx-v3/useGasSpeed": "workspace:*" + "@snx-v3/useLegacyMarket": "workspace:*" + "@synthetixio/wei": "npm:^2.74.4" + "@tanstack/react-query": "npm:^5.8.3" + react: "npm:^18.2.0" + languageName: unknown + linkType: soft + "@snx-v3/useMulticall3@workspace:*, @snx-v3/useMulticall3@workspace:liquidity/lib/useMulticall3": version: 0.0.0-use.local resolution: "@snx-v3/useMulticall3@workspace:liquidity/lib/useMulticall3" @@ -6388,6 +6437,19 @@ __metadata: languageName: unknown linkType: soft +"@snx-v3/useRates@workspace:*, @snx-v3/useRates@workspace:liquidity/lib/useRates": + version: 0.0.0-use.local + resolution: "@snx-v3/useRates@workspace:liquidity/lib/useRates" + dependencies: + "@snx-v3/useBlockchain": "workspace:*" + "@synthetixio/contracts": "npm:^1.2.3" + "@synthetixio/wei": "npm:^2.74.4" + "@tanstack/react-query": "npm:^5.8.3" + ethers: "npm:^5.7.2" + react: "npm:^18.2.0" + languageName: unknown + linkType: soft + "@snx-v3/useRepay@workspace:*, @snx-v3/useRepay@workspace:liquidity/lib/useRepay": version: 0.0.0-use.local resolution: "@snx-v3/useRepay@workspace:liquidity/lib/useRepay" @@ -6615,6 +6677,43 @@ __metadata: languageName: unknown linkType: soft +"@snx-v3/useV2Position@workspace:*, @snx-v3/useV2Position@workspace:liquidity/lib/useV2Position": + version: 0.0.0-use.local + resolution: "@snx-v3/useV2Position@workspace:liquidity/lib/useV2Position" + dependencies: + "@snx-v3/useBlockchain": "workspace:*" + "@snx-v3/useV2xSynthetix": "workspace:*" + "@synthetixio/wei": "npm:^2.74.4" + "@tanstack/react-query": "npm:^5.8.3" + ethers: "npm:^5.7.2" + react: "npm:^18.2.0" + languageName: unknown + linkType: soft + +"@snx-v3/useV2sUSD@workspace:*, @snx-v3/useV2sUSD@workspace:liquidity/lib/useV2sUSD": + version: 0.0.0-use.local + resolution: "@snx-v3/useV2sUSD@workspace:liquidity/lib/useV2sUSD" + dependencies: + "@snx-v3/useBlockchain": "workspace:*" + "@snx-v3/useV2xSynthetix": "workspace:*" + "@tanstack/react-query": "npm:^5.8.3" + ethers: "npm:^5.7.2" + react: "npm:^18.2.0" + languageName: unknown + linkType: soft + +"@snx-v3/useV2xSynthetix@workspace:*, @snx-v3/useV2xSynthetix@workspace:liquidity/lib/useV2xSynthetix": + version: 0.0.0-use.local + resolution: "@snx-v3/useV2xSynthetix@workspace:liquidity/lib/useV2xSynthetix" + dependencies: + "@ethersproject/contracts": "npm:^5.7.0" + "@snx-v3/contracts": "workspace:*" + "@snx-v3/useBlockchain": "workspace:*" + "@tanstack/react-query": "npm:^5.8.3" + react: "npm:^18.2.0" + languageName: unknown + linkType: soft + "@snx-v3/useVaultsData@workspace:*, @snx-v3/useVaultsData@workspace:liquidity/lib/useVaultsData": version: 0.0.0-use.local resolution: "@snx-v3/useVaultsData@workspace:liquidity/lib/useVaultsData" @@ -6965,6 +7064,16 @@ __metadata: languageName: node linkType: hard +"@synthetixio/contracts@npm:^1.2.3": + version: 1.2.3 + resolution: "@synthetixio/contracts@npm:1.2.3" + dependencies: + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.2" + checksum: db21ac9403c5508aa2760927d7825e95665c2ec1a3cf9dac0fab06835fcb34e9ede61c22d05be57cc34f03213d652ad2c4e0f250d8f879ae272056459a0a45b0 + languageName: node + linkType: hard + "@synthetixio/deps@workspace:*, @synthetixio/deps@workspace:tools/deps": version: 0.0.0-use.local resolution: "@synthetixio/deps@workspace:tools/deps"