diff --git a/package.json b/package.json index ae1e23ad..0981d55a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "mars-v2-frontend", "version": "2.2.4", + "v1version": "1.7.5", "homepage": "./", "private": false, "license": "SEE LICENSE IN LICENSE FILE", diff --git a/public/images/bg-v1.svg b/public/images/bg-v1.svg new file mode 100644 index 00000000..c15fa8f9 --- /dev/null +++ b/public/images/bg-v1.svg @@ -0,0 +1,63 @@ + + + + + + + + + diff --git a/public/images/tokens/stkatom.svg b/public/images/tokens/stkatom.svg new file mode 100644 index 00000000..c0679269 --- /dev/null +++ b/public/images/tokens/stkatom.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + diff --git a/public/images/tokens/wsteth.svg b/public/images/tokens/wsteth.svg new file mode 100644 index 00000000..656b0244 --- /dev/null +++ b/public/images/tokens/wsteth.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/sentry.client.config.js b/sentry.client.config.js deleted file mode 100644 index dc6b933a..00000000 --- a/sentry.client.config.js +++ /dev/null @@ -1,19 +0,0 @@ -// This file configures the initialization of Sentry on the browser. -// The config you add here will be used whenever a page is visited. -// https://docs.sentry.io/platforms/javascript/guides/nextjs/ - -import * as Sentry from '@sentry/nextjs' - -const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN - -Sentry.init({ - dsn: SENTRY_DSN, - environment: process.env.NEXT_PUBLIC_SENTRY_ENV, - // Adjust this value in production, or use tracesSampler for greater control - tracesSampleRate: 0.5, - enabled: process.env.NODE_ENV !== 'development', - // ... - // Note: if you want to override the automatic release value, do not set a - // `release` value here - use the environment variable `SENTRY_RELEASE`, so - // that it will also get attached to your source maps -}) diff --git a/sentry.properties b/sentry.properties deleted file mode 100644 index 46f155aa..00000000 --- a/sentry.properties +++ /dev/null @@ -1,4 +0,0 @@ -defaults.url=https://sentry.io/ -defaults.org=delphi-mars -defaults.project=mars-v2 -cli.executable=../../../.npm/_npx/a8388072043b4cbc/node_modules/@sentry/cli/bin/sentry-cli diff --git a/sentry.server.config.js b/sentry.server.config.js deleted file mode 100644 index 9f9ce8ab..00000000 --- a/sentry.server.config.js +++ /dev/null @@ -1,19 +0,0 @@ -// This file configures the initialization of Sentry on the server. -// The config you add here will be used whenever the server handles a request. -// https://docs.sentry.io/platforms/javascript/guides/nextjs/ - -import * as Sentry from '@sentry/nextjs' - -const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN - -Sentry.init({ - dsn: SENTRY_DSN, - environment: process.env.NEXT_PUBLIC_SENTRY_ENV, - // Adjust this value in production, or use tracesSampler for greater control - tracesSampleRate: 0.5, - enabled: process.env.NODE_ENV !== 'development', - // ... - // Note: if you want to override the automatic release value, do not set a - // `release` value here - use the environment variable `SENTRY_RELEASE`, so - // that it will also get attached to your source maps -}) diff --git a/src/api/cache.ts b/src/api/cache.ts index 3ce93c94..f6051db3 100644 --- a/src/api/cache.ts +++ b/src/api/cache.ts @@ -10,7 +10,12 @@ import { TotalDepositResponse, VaultConfigBaseForAddr, } from 'types/generated/mars-params/MarsParams.types' -import { ArrayOfMarket } from 'types/generated/mars-red-bank/MarsRedBank.types' +import { + ArrayOfMarket, + ArrayOfUserCollateralResponse, + ArrayOfUserDebtResponse, + UserCollateralResponse, +} from 'types/generated/mars-red-bank/MarsRedBank.types' interface Cache extends Map {} @@ -62,3 +67,5 @@ export const underlyingDebtCache: Cache = new Map() export const previewDepositCache: Cache<{ vaultAddress: string; amount: string }> = new Map() export const stakingAprCache: Cache = new Map() export const assetParamsCache: Cache = new Map() +export const userCollateralCache: Cache = new Map() +export const userDebtCache: Cache = new Map() diff --git a/src/api/cosmwasm-client.ts b/src/api/cosmwasm-client.ts index 357b6167..c391367c 100644 --- a/src/api/cosmwasm-client.ts +++ b/src/api/cosmwasm-client.ts @@ -6,7 +6,9 @@ import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMo import { MarsOracleOsmosisQueryClient } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.client' import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.client' import { MarsPerpsQueryClient } from 'types/generated/mars-perps/MarsPerps.client' +import { MarsRedBankQueryClient } from 'types/generated/mars-red-bank/MarsRedBank.client' import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client' +import { getUrl } from 'utils/url' let _cosmWasmClient: Map = new Map() let _creditManagerQueryClient: Map = new Map() @@ -15,6 +17,7 @@ let _paramsQueryClient: Map = new Map() let _incentivesQueryClient: Map = new Map() let _swapperOsmosisClient: Map = new Map() let _perpsClient: Map = new Map() +let _redBankQueryClient: Map = new Map() const getClient = async (rpc: string) => { try { @@ -32,7 +35,7 @@ const getClient = async (rpc: string) => { const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => { try { const contract = chainConfig.contracts.creditManager - const rpc = chainConfig.endpoints.rpc + const rpc = getUrl(chainConfig.endpoints.rpc) const key = rpc + contract if (!_creditManagerQueryClient.get(key)) { @@ -49,7 +52,7 @@ const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => { const getParamsQueryClient = async (chainConfig: ChainConfig) => { try { const contract = chainConfig.contracts.params - const rpc = chainConfig.endpoints.rpc + const rpc = getUrl(chainConfig.endpoints.rpc) const key = rpc + contract if (!_paramsQueryClient.get(key)) { @@ -66,7 +69,7 @@ const getParamsQueryClient = async (chainConfig: ChainConfig) => { const getOracleQueryClient = async (chainConfig: ChainConfig) => { try { const contract = chainConfig.contracts.oracle - const rpc = chainConfig.endpoints.rpc + const rpc = getUrl(chainConfig.endpoints.rpc) const key = rpc + contract if (!_oracleQueryClient.get(key)) { @@ -82,7 +85,7 @@ const getOracleQueryClient = async (chainConfig: ChainConfig) => { const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) => { try { - const client = await getClient(chainConfig.endpoints.rpc) + const client = await getClient(getUrl(chainConfig.endpoints.rpc)) return new MarsMockVaultQueryClient(client, address) } catch (error) { throw error @@ -92,7 +95,7 @@ const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) => const getIncentivesQueryClient = async (chainConfig: ChainConfig) => { try { const contract = chainConfig.contracts.incentives - const rpc = chainConfig.endpoints.rpc + const rpc = getUrl(chainConfig.endpoints.rpc) const key = rpc + contract if (!_incentivesQueryClient.get(key)) { const client = await getClient(rpc) @@ -108,7 +111,7 @@ const getIncentivesQueryClient = async (chainConfig: ChainConfig) => { const getSwapperQueryClient = async (chainConfig: ChainConfig) => { try { const contract = chainConfig.contracts.swapper - const rpc = chainConfig.endpoints.rpc + const rpc = getUrl(chainConfig.endpoints.rpc) const key = rpc + contract if (!_swapperOsmosisClient.get(key)) { const client = await getClient(rpc) @@ -124,7 +127,7 @@ const getSwapperQueryClient = async (chainConfig: ChainConfig) => { const getPerpsQueryClient = async (chainConfig: ChainConfig) => { try { const contract = chainConfig.contracts.perps - const rpc = chainConfig.endpoints.rpc + const rpc = getUrl(chainConfig.endpoints.rpc) const key = rpc + contract if (!_perpsClient.get(key)) { const client = await getClient(rpc) @@ -137,13 +140,31 @@ const getPerpsQueryClient = async (chainConfig: ChainConfig) => { } } +const getRedBankQueryClient = async (chainConfig: ChainConfig) => { + try { + const contract = chainConfig.contracts.redBank + const rpc = getUrl(chainConfig.endpoints.rpc) + const key = rpc + contract + + if (!_redBankQueryClient.get(key)) { + const client = await getClient(rpc) + _redBankQueryClient.set(key, new MarsRedBankQueryClient(client, contract)) + } + + return _redBankQueryClient.get(key)! + } catch (error) { + throw error + } +} + export { getClient, getCreditManagerQueryClient, getIncentivesQueryClient, getOracleQueryClient, getParamsQueryClient, + getPerpsQueryClient, + getRedBankQueryClient, getSwapperQueryClient, getVaultQueryClient, - getPerpsQueryClient, } diff --git a/src/api/v1/getV1Positions.ts b/src/api/v1/getV1Positions.ts new file mode 100644 index 00000000..33032dfa --- /dev/null +++ b/src/api/v1/getV1Positions.ts @@ -0,0 +1,41 @@ +import { cacheFn, userCollateralCache, userDebtCache } from 'api/cache' +import { getRedBankQueryClient } from 'api/cosmwasm-client' +import { BNCoin } from 'types/classes/BNCoin' +import { + ArrayOfUserCollateralResponse, + ArrayOfUserDebtResponse, +} from 'types/generated/mars-red-bank/MarsRedBank.types' + +export default async function getV1Positions( + chainConfig: ChainConfig, + user?: string, +): Promise { + if (!user) return new Promise((_, reject) => reject('No account Wallet ID found')) + + const redBankQueryClient = await getRedBankQueryClient(chainConfig) + + const userCollateral: ArrayOfUserCollateralResponse = await cacheFn( + () => redBankQueryClient.userCollaterals({ user: user, limit: 100 }), + userCollateralCache, + `${chainConfig.id}/v1/deposits/${user}`, + ) + const userDebt: ArrayOfUserDebtResponse = await cacheFn( + () => redBankQueryClient.userDebts({ user: user, limit: 100 }), + userDebtCache, + `${chainConfig.id}/v1/debts/${user}`, + ) + + if (userCollateral && userDebt) { + return { + id: user, + debts: userDebt.map((debt) => new BNCoin(debt)), + lends: userCollateral.map((lend) => new BNCoin(lend)), + deposits: [], + vaults: [], + perps: [], + kind: 'default', + } + } + + return new Promise((_, reject) => reject('No account found')) +} diff --git a/src/components/Modals/AssetAmountSelectActionModal.tsx b/src/components/Modals/AssetAmountSelectActionModal.tsx index e0b2f05a..6dbdd83c 100644 --- a/src/components/Modals/AssetAmountSelectActionModal.tsx +++ b/src/components/Modals/AssetAmountSelectActionModal.tsx @@ -10,12 +10,12 @@ import Text from 'components/common/Text' import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider' import AssetImage from 'components/common/assets/AssetImage' import { BN_ZERO } from 'constants/math' -import useCurrentAccount from 'hooks/accounts/useCurrentAccount' import { BNCoin } from 'types/classes/BNCoin' import { byDenom } from 'utils/array' import { BN } from 'utils/helpers' interface Props { + account: Account asset: Asset title: string coinBalances: BNCoin[] @@ -29,6 +29,7 @@ interface Props { export default function AssetAmountSelectActionModal(props: Props) { const { + account, asset, title, coinBalances, @@ -41,7 +42,6 @@ export default function AssetAmountSelectActionModal(props: Props) { } = props const [amount, setAmount] = useState(BN_ZERO) const maxAmount = BN(coinBalances.find(byDenom(asset.denom))?.amount ?? 0) - const account = useCurrentAccount() const handleAmountChange = useCallback( (value: BigNumber) => { setAmount(value) @@ -54,7 +54,6 @@ export default function AssetAmountSelectActionModal(props: Props) { onAction(amount, amount.isEqualTo(maxAmount)) }, [amount, maxAmount, onAction]) - if (!account) return return ( -} - function RepayNotAvailable(props: { asset: Asset; repayFromWallet: boolean }) { return ( - +
@@ -90,7 +81,6 @@ function BorrowModal(props: Props) { const apy = modal.marketData.apy.borrow const isAutoLendEnabled = autoLendEnabledAccountIds.includes(account.id) const { computeMaxBorrowAmount } = useHealthComputer(account) - const totalDebt = BN(getDebtAmount(modal)) const accountDebt = account.debts.find(byDenom(asset.denom))?.amount ?? BN_ZERO const markets = useMarkets() @@ -237,7 +227,7 @@ function BorrowModal(props: Props) { onClose={onClose} header={ - {getAssetLogo(modal)} + {isRepay ? 'Repay' : 'Borrow'} {asset.symbol} @@ -251,14 +241,14 @@ function BorrowModal(props: Props) { title={formatPercent(modal.marketData.apy.borrow)} sub={'Borrow Rate APY'} /> - {totalDebt.isGreaterThan(0) && ( + {accountDebt.isGreaterThan(0) && ( <>
@@ -303,59 +293,57 @@ function BorrowModal(props: Props) {
-
- - {isRepay && maxRepayAmount.isZero() && ( - - )} - {isRepay ? ( - <> - + + {isRepay && maxRepayAmount.isZero() && ( + + )} + {isRepay ? ( + <> + +
Repay from Wallet Repay your debt directly from your wallet
-
- -
- - ) : ( - <> - + +
+ + ) : ( + <> + +
Receive funds to Wallet Your borrowed funds will directly go to your wallet
-
- -
- - )} -
+ +
+ + )}
+ + ) +} diff --git a/src/components/Modals/v1/Deposit.tsx b/src/components/Modals/v1/Deposit.tsx new file mode 100644 index 00000000..cb9000f2 --- /dev/null +++ b/src/components/Modals/v1/Deposit.tsx @@ -0,0 +1,82 @@ +import { useCallback, useEffect, useMemo, useState } from 'react' + +import WalletBridges from 'components/Wallet/WalletBridges' +import { BN_ZERO } from 'constants/math' +import useBaseAsset from 'hooks/assets/useBasetAsset' +import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance' +import { useUpdatedAccount } from 'hooks/useUpdatedAccount' +import useWalletBalances from 'hooks/useWalletBalances' +import useStore from 'store' +import { BNCoin } from 'types/classes/BNCoin' +import { byDenom } from 'utils/array' +import { defaultFee } from 'utils/constants' +import { BN } from 'utils/helpers' +import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal' +import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader' + +interface Props { + account: Account +} + +export default function Deposit(props: Props) { + const { account } = props + const baseAsset = useBaseAsset() + const modal = useStore((s) => s.v1DepositAndWithdrawModal) + const address = useStore((s) => s.address) + const asset = modal?.data.asset ?? baseAsset + const [fundingAsset, setFundingAsset] = useState( + BNCoin.fromDenomAndBigNumber(modal?.data.asset.denom ?? baseAsset.denom, BN_ZERO), + ) + const { data: walletBalances } = useWalletBalances(address) + const { simulateDeposits } = useUpdatedAccount(account) + const balance = useCurrentWalletBalance(asset.denom) + const v1Action = useStore((s) => s.v1Action) + + const baseBalance = useMemo( + () => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0', + [walletBalances, baseAsset], + ) + + const close = useCallback(() => { + useStore.setState({ v1DepositAndWithdrawModal: null }) + }, []) + + const handleClick = useCallback(async () => { + v1Action('deposit', fundingAsset) + close() + }, [v1Action, fundingAsset, close]) + + useEffect(() => { + if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) { + useStore.setState({ focusComponent: { component: } }) + } + }, [baseBalance]) + + const onDebounce = useCallback(() => { + simulateDeposits('lend', [fundingAsset]) + }, [fundingAsset, simulateDeposits]) + + const handleAmountChange = useCallback( + (value: BigNumber) => { + setFundingAsset(BNCoin.fromDenomAndBigNumber(asset.denom, value)) + }, + [asset.denom], + ) + + if (!modal) return + + return ( + } + coinBalances={balance ? [BNCoin.fromCoin(balance)] : []} + actionButtonText={`Deposit ${asset.symbol}`} + title={`Deposit ${asset.symbol} into the Red Bank`} + onClose={close} + onAction={handleClick} + onChange={handleAmountChange} + onDebounce={onDebounce} + /> + ) +} diff --git a/src/components/Modals/v1/Repay.tsx b/src/components/Modals/v1/Repay.tsx new file mode 100644 index 00000000..8ab28264 --- /dev/null +++ b/src/components/Modals/v1/Repay.tsx @@ -0,0 +1,208 @@ +import BigNumber from 'bignumber.js' +import { useCallback, useEffect, useMemo, useState } from 'react' + +import Modal from 'components/Modals/Modal' +import AccountSummaryInModal from 'components/account/AccountSummary/AccountSummaryInModal' +import Button from 'components/common/Button' +import Card from 'components/common/Card' +import DisplayCurrency from 'components/common/DisplayCurrency' +import Divider from 'components/common/Divider' +import { FormattedNumber } from 'components/common/FormattedNumber' +import { ArrowRight, InfoCircle } from 'components/common/Icons' +import Text from 'components/common/Text' +import TitleAndSubCell from 'components/common/TitleAndSubCell' +import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider' +import AssetImage from 'components/common/assets/AssetImage' +import { BN_ZERO } from 'constants/math' +import useBaseAsset from 'hooks/assets/useBasetAsset' +import useMarkets from 'hooks/markets/useMarkets' +import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance' +import { useUpdatedAccount } from 'hooks/useUpdatedAccount' +import useStore from 'store' +import { BNCoin } from 'types/classes/BNCoin' +import { formatPercent } from 'utils/formatters' +import { BN } from 'utils/helpers' +import { getDebtAmountWithInterest } from 'utils/tokens' + +interface Props { + account: Account +} + +function RepayNotAvailable(props: { asset: Asset }) { + return ( + +
+ +
+ No funds for repay + {`Unfortunately you don't have any ${props.asset.symbol} in your Wallet to repay the debt.`} +
+
+
+ ) +} + +export default function Repay(props: Props) { + const { account } = props + const modal = useStore((s) => s.v1BorrowAndRepayModal) + const baseAsset = useBaseAsset() + const asset = modal?.data.asset ?? baseAsset + const [amount, setAmount] = useState(BN_ZERO) + const balance = useCurrentWalletBalance(asset.denom) + const v1Action = useStore((s) => s.v1Action) + const [max, setMax] = useState(BN_ZERO) + const { simulateRepay } = useUpdatedAccount(account) + const apy = modal?.data.apy.borrow ?? 0 + const accountDebt = modal?.data.accountDebtAmount ?? BN_ZERO + const markets = useMarkets() + + const accountDebtWithInterest = useMemo( + () => getDebtAmountWithInterest(accountDebt, apy), + [accountDebt, apy], + ) + + const overpayExeedsCap = useMemo(() => { + const marketAsset = markets.find((market) => market.asset.denom === asset.denom) + if (!marketAsset) return + const overpayAmount = accountDebtWithInterest.minus(accountDebt) + const marketCapAfterOverpay = marketAsset.cap.used.plus(overpayAmount) + + return marketAsset.cap.max.isLessThanOrEqualTo(marketCapAfterOverpay) + }, [markets, asset.denom, accountDebt, accountDebtWithInterest]) + + const maxRepayAmount = useMemo(() => { + const maxBalance = BN(balance?.amount ?? 0) + return BigNumber.min(maxBalance, overpayExeedsCap ? accountDebt : accountDebtWithInterest) + }, [accountDebtWithInterest, overpayExeedsCap, accountDebt, balance?.amount]) + + const close = useCallback(() => { + setAmount(BN_ZERO) + useStore.setState({ v1BorrowAndRepayModal: null }) + }, [setAmount]) + + const onConfirmClick = useCallback(() => { + v1Action('repay', BNCoin.fromDenomAndBigNumber(asset.denom, amount)) + close() + }, [v1Action, asset, amount, close]) + + const handleChange = useCallback( + (newAmount: BigNumber) => { + if (!amount.isEqualTo(newAmount)) setAmount(newAmount) + }, + [amount, setAmount], + ) + + const onDebounce = useCallback(() => { + const repayCoin = BNCoin.fromDenomAndBigNumber( + asset.denom, + amount.isGreaterThan(accountDebt) ? accountDebt : amount, + ) + simulateRepay(repayCoin, true) + }, [amount, accountDebt, asset, simulateRepay]) + + useEffect(() => { + if (maxRepayAmount.isEqualTo(max)) return + setMax(maxRepayAmount) + }, [max, maxRepayAmount]) + + useEffect(() => { + if (amount.isLessThanOrEqualTo(max)) return + handleChange(max) + setAmount(max) + }, [amount, max, handleChange]) + + if (!modal) return null + + return ( + + + + {'Repay'} {asset.symbol} + + + } + headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b' + contentClassName='flex flex-col' + > +
+ + +
+
+
+ + +
+ + Total Borrowed + +
+
+
+
+ + +
+ + Liquidity available + +
+
+
+ + + + {maxRepayAmount.isZero() && } +
+ + ) +} diff --git a/src/components/Modals/v1/V1BorrowAndRepay.tsx b/src/components/Modals/v1/V1BorrowAndRepay.tsx new file mode 100644 index 00000000..141a052f --- /dev/null +++ b/src/components/Modals/v1/V1BorrowAndRepay.tsx @@ -0,0 +1,15 @@ +import useAccount from 'hooks/accounts/useAccount' +import useStore from 'store' +import Borrow from 'components/Modals/v1/Borrow' +import Repay from 'components/Modals/v1/Repay' + +export default function V1BorrowAndRepayModal() { + const address = useStore((s) => s.address) + const { data: account } = useAccount(address) + const modal = useStore((s) => s.v1BorrowAndRepayModal) + const isBorrow = modal?.type === 'borrow' + + if (!modal || !account) return null + if (isBorrow) return + return +} diff --git a/src/components/Modals/v1/V1DepositAndWithdraw.tsx b/src/components/Modals/v1/V1DepositAndWithdraw.tsx new file mode 100644 index 00000000..475f0c06 --- /dev/null +++ b/src/components/Modals/v1/V1DepositAndWithdraw.tsx @@ -0,0 +1,15 @@ +import useAccount from 'hooks/accounts/useAccount' +import useStore from 'store' +import Deposit from 'components/Modals/v1/Deposit' +import Withdraw from 'components/Modals/v1/Withdraw' + +export default function V1DepositAndWithdraw() { + const address = useStore((s) => s.address) + const { data: account } = useAccount(address) + const modal = useStore((s) => s.v1DepositAndWithdrawModal) + const isDeposit = modal?.type === 'deposit' + + if (!modal || !account) return null + if (isDeposit) return + return +} diff --git a/src/components/Modals/v1/Withdraw.tsx b/src/components/Modals/v1/Withdraw.tsx new file mode 100644 index 00000000..f4d111ce --- /dev/null +++ b/src/components/Modals/v1/Withdraw.tsx @@ -0,0 +1,66 @@ +import { useCallback, useState } from 'react' + +import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal' +import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader' +import { BN_ZERO } from 'constants/math' +import useBaseAsset from 'hooks/assets/useBasetAsset' +import useHealthComputer from 'hooks/useHealthComputer' +import { useUpdatedAccount } from 'hooks/useUpdatedAccount' +import useStore from 'store' +import { BNCoin } from 'types/classes/BNCoin' + +interface Props { + account: Account +} + +export default function Withdraw(props: Props) { + const { account } = props + const baseAsset = useBaseAsset() + const modal = useStore((s) => s.v1DepositAndWithdrawModal) + const asset = modal?.data.asset ?? baseAsset + const [withdrawAsset, setWithdrawAsset] = useState( + BNCoin.fromDenomAndBigNumber(modal?.data.asset.denom ?? baseAsset.denom, BN_ZERO), + ) + const { computeMaxWithdrawAmount } = useHealthComputer(account) + const maxWithdrawAmount = computeMaxWithdrawAmount(asset.denom) + const { simulateWithdraw } = useUpdatedAccount(account) + const balance = BNCoin.fromDenomAndBigNumber(asset.denom, maxWithdrawAmount) + const v1Action = useStore((s) => s.v1Action) + + const close = useCallback(() => { + useStore.setState({ v1DepositAndWithdrawModal: null }) + }, []) + + const handleClick = useCallback(async () => { + v1Action('withdraw', withdrawAsset) + close() + }, [v1Action, withdrawAsset, close]) + + const onDebounce = useCallback(() => { + simulateWithdraw(false, withdrawAsset) + }, [withdrawAsset, simulateWithdraw]) + + const handleAmountChange = useCallback( + (value: BigNumber) => { + setWithdrawAsset(BNCoin.fromDenomAndBigNumber(asset.denom, value)) + }, + [asset.denom], + ) + + if (!modal) return + + return ( + } + coinBalances={[balance]} + actionButtonText={`Withdraw ${asset.symbol}`} + title={`Withdraw ${asset.symbol} from the Red Bank`} + onClose={close} + onAction={handleClick} + onChange={handleAmountChange} + onDebounce={onDebounce} + /> + ) +} diff --git a/src/components/Wallet/RecentTransactions.tsx b/src/components/Wallet/RecentTransactions.tsx index 567da7d9..962e1a88 100644 --- a/src/components/Wallet/RecentTransactions.tsx +++ b/src/components/Wallet/RecentTransactions.tsx @@ -7,8 +7,8 @@ import Text from 'components/common/Text' import { TextLink } from 'components/common/TextLink' import { generateToastContent } from 'components/common/Toaster' import useTransactions from 'hooks/localStorage/useTransactions' -import useStore from 'store' import useChainConfig from 'hooks/useChainConfig' +import useStore from 'store' export default function RecentTransactions() { const address = useStore((s) => s.address) @@ -47,7 +47,9 @@ export default function RecentTransactions() { key={hash} >
- Credit Account {accountId} + + {accountId === address ? 'Red Bank' : `Credit Account ${accountId}`} + {moment.unix(timestamp).format('lll')} diff --git a/src/components/Wallet/WalletConnectButton.tsx b/src/components/Wallet/WalletConnectButton.tsx index 08bb00ef..68b0f38c 100644 --- a/src/components/Wallet/WalletConnectButton.tsx +++ b/src/components/Wallet/WalletConnectButton.tsx @@ -15,6 +15,7 @@ interface Props { color?: ButtonProps['color'] variant?: ButtonProps['variant'] size?: ButtonProps['size'] + short?: boolean } export default function WalletConnectButton(props: Props) { diff --git a/src/components/Wallet/WalletConnecting.tsx b/src/components/Wallet/WalletConnecting.tsx index 156e01aa..78e4f9ee 100644 --- a/src/components/Wallet/WalletConnecting.tsx +++ b/src/components/Wallet/WalletConnecting.tsx @@ -10,6 +10,7 @@ import useChainConfig from 'hooks/useChainConfig' import useCurrentWallet from 'hooks/useCurrentWallet' import useToggle from 'hooks/useToggle' import useStore from 'store' +import { getUrl } from 'utils/url' interface Props { providerId?: string @@ -53,7 +54,7 @@ export default function WalletConnecting(props: Props) { setIsConnecting(true) try { const response = await connect({ extensionProviderId, chainId: chainConfig.id }) - const cosmClient = await CosmWasmClient.connect(chainConfig.endpoints.rpc) + const cosmClient = await CosmWasmClient.connect(getUrl(chainConfig.endpoints.rpc)) const walletClient: WalletClient = { broadcast, cosmWasmClient: cosmClient, @@ -137,7 +138,7 @@ export default function WalletConnecting(props: Props) { setIsConnecting(true) try { await mobileConnect({ mobileProviderId, chainId: chainConfig.id }) - const cosmClient = await CosmWasmClient.connect(chainConfig.endpoints.rpc) + const cosmClient = await CosmWasmClient.connect(getUrl(chainConfig.endpoints.rpc)) const walletClient: WalletClient = { broadcast, cosmWasmClient: cosmClient, diff --git a/src/components/Wallet/WalletFetchBalancesAndAccounts.tsx b/src/components/Wallet/WalletFetchBalancesAndAccounts.tsx index 162a60f8..e006fc75 100644 --- a/src/components/Wallet/WalletFetchBalancesAndAccounts.tsx +++ b/src/components/Wallet/WalletFetchBalancesAndAccounts.tsx @@ -29,7 +29,7 @@ function FetchLoading() { function Content() { const address = useStore((s) => s.address) const [searchParams] = useSearchParams() - + const isV1 = useStore((s) => s.isV1) const { address: urlAddress } = useParams() const urlAccountId = useAccountId() const navigate = useNavigate() @@ -62,7 +62,7 @@ function Content() { ) { const currentAccountIsHLS = urlAccountId && !accountIds.includes(urlAccountId) const currentAccount = currentAccountIsHLS || !urlAccountId ? accountIds[0] : urlAccountId - navigate(getRoute(page, searchParams, address, currentAccount)) + navigate(getRoute(page, searchParams, address, isV1 ? undefined : currentAccount)) useStore.setState({ balances: walletBalances, focusComponent: null }) } }, [ @@ -75,11 +75,12 @@ function Content() { urlAddress, urlAccountId, searchParams, + isV1, ]) if (isLoadingAccounts || isLoadingBalances) return if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) return - if (accountIds && accountIds.length === 0) return + if (accountIds && accountIds.length === 0 && !isV1) return return null } diff --git a/src/components/account/AccountBalancesTable/index.tsx b/src/components/account/AccountBalancesTable/index.tsx index a3e00be6..2eee9ced 100644 --- a/src/components/account/AccountBalancesTable/index.tsx +++ b/src/components/account/AccountBalancesTable/index.tsx @@ -75,6 +75,7 @@ export default function AccountBalancesTable(props: Props) { }, }) }} + short />
diff --git a/src/components/account/AccountDetails/index.tsx b/src/components/account/AccountDetails/index.tsx index 1ac488f9..e0ad160b 100644 --- a/src/components/account/AccountDetails/index.tsx +++ b/src/components/account/AccountDetails/index.tsx @@ -37,6 +37,7 @@ import { export default function AccountDetailsController() { const address = useStore((s) => s.address) const isHLS = useStore((s) => s.isHLS) + const isV1 = useStore((s) => s.isV1) const { data: _, isLoading } = useAccounts('default', address) const { data: accountIds } = useAccountIds(address, false, true) @@ -45,10 +46,11 @@ export default function AccountDetailsController() { const account = useCurrentAccount() const focusComponent = useStore((s) => s.focusComponent) const isOwnAccount = accountId && accountIds?.includes(accountId) + const hideAccountDetails = !address || focusComponent || !isOwnAccount || isHLS || isV1 + const isLoadingAccountDetails = (isLoading && accountId && !focusComponent) || !account - if (!address || focusComponent || !isOwnAccount || isHLS) return null - - if ((isLoading && accountId && !focusComponent) || !account) return + if (hideAccountDetails) return null + if (isLoadingAccountDetails) return return } diff --git a/src/components/borrow/Borrowings.tsx b/src/components/borrow/Borrowings.tsx index a30cc99e..5cb6d42c 100644 --- a/src/components/borrow/Borrowings.tsx +++ b/src/components/borrow/Borrowings.tsx @@ -1,19 +1,19 @@ +import ActiveBorrowingsTable from 'components/borrow/Table/ActiveBorrowingsTable' import AvailableBorrowingsTable from 'components/borrow/Table/AvailableBorrowingsTable' -import DepositedBorrowingsTable from 'components/borrow/Table/DepositedBorrowingsTable' import useBorrowMarketAssetsTableData from 'components/borrow/Table/useBorrowMarketAssetsTableData' import { BN_ZERO } from 'constants/math' import useBorrowEnabledAssets from 'hooks/assets/useBorrowEnabledAssets' export default function Borrowings() { - const data = useBorrowMarketAssetsTableData() + const { accountBorrowedAssets, availableAssets, allAssets } = useBorrowMarketAssetsTableData() - if (!data?.allAssets?.length) { + if (!allAssets?.length) { return } return ( <> - - + + ) } diff --git a/src/components/borrow/Table/DepositedBorrowingsTable.tsx b/src/components/borrow/Table/ActiveBorrowingsTable.tsx similarity index 51% rename from src/components/borrow/Table/DepositedBorrowingsTable.tsx rename to src/components/borrow/Table/ActiveBorrowingsTable.tsx index 8eaf08da..605fb8fa 100644 --- a/src/components/borrow/Table/DepositedBorrowingsTable.tsx +++ b/src/components/borrow/Table/ActiveBorrowingsTable.tsx @@ -1,18 +1,20 @@ import { Row } from '@tanstack/react-table' import { useCallback } from 'react' +import { DEBT_VALUE_META } from 'components/borrow/Table/Columns/DebtValue' import { NAME_META } from 'components/borrow/Table/Columns/Name' -import useDepositedColumns from 'components/borrow/Table/Columns/useDepositedColumns' +import useBorrowingsColumns from 'components/borrow/Table/Columns/useActiveColumns' import MarketDetails from 'components/common/MarketDetails' import Table from 'components/common/Table' type Props = { data: BorrowMarketTableData[] isLoading: boolean + v1?: boolean } -export default function DepositedBorrowingsTable(props: Props) { - const columns = useDepositedColumns() +export default function ActiveBorrowingsTable(props: Props) { + const columns = useBorrowingsColumns({ v1: props.v1 }) const renderExpanded = useCallback((row: Row) => { return @@ -22,10 +24,17 @@ export default function DepositedBorrowingsTable(props: Props) { return ( ) diff --git a/src/components/borrow/Table/AvailableBorrowingsTable.tsx b/src/components/borrow/Table/AvailableBorrowingsTable.tsx index b025e004..5a89521d 100644 --- a/src/components/borrow/Table/AvailableBorrowingsTable.tsx +++ b/src/components/borrow/Table/AvailableBorrowingsTable.tsx @@ -13,7 +13,7 @@ type Props = { } export default function AvailableBorrowingsTable(props: Props) { - const columns = useAvailableColumns() + const columns = useAvailableColumns({ v1: false }) const renderExpanded = useCallback( (row: Row, _: TanstackTable) => { diff --git a/src/components/borrow/Table/Columns/BorrowButton.tsx b/src/components/borrow/Table/Columns/BorrowButton.tsx index 421a277d..da13ebb6 100644 --- a/src/components/borrow/Table/Columns/BorrowButton.tsx +++ b/src/components/borrow/Table/Columns/BorrowButton.tsx @@ -54,6 +54,7 @@ export default function BorrowButton(props: Props) { e.stopPropagation() }} text='Borrow' + short /> diff --git a/src/components/borrow/Table/Columns/Debt.tsx b/src/components/borrow/Table/Columns/Debt.tsx deleted file mode 100644 index 794822c6..00000000 --- a/src/components/borrow/Table/Columns/Debt.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Row } from '@tanstack/react-table' - -import AmountAndValue from 'components/common/AmountAndValue' -import { BN_ZERO } from 'constants/math' -import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets' -import { byDenom } from 'utils/array' - -export const DEBT_META = { - accessorKey: 'debt', - header: 'Debt', -} - -export const debtSortingFn = ( - a: Row, - b: Row, -): number => { - const assetA = a.original.asset - const assetB = b.original.asset - if (!a.original.accountDebt || !b.original.accountDebt) return 0 - const debtA = a.original.accountDebt.shiftedBy(-assetA.decimals) - const debtB = b.original.accountDebt.shiftedBy(-assetB.decimals) - return debtA.minus(debtB).toNumber() -} - -interface Props { - data: BorrowMarketTableData -} - -export default function Debt(props: Props) { - const marketAssets = useMarketEnabledAssets() - const asset = marketAssets.find(byDenom(props.data.asset.denom)) - - if (!asset) return null - - return -} diff --git a/src/components/borrow/Table/Columns/DebtValue.tsx b/src/components/borrow/Table/Columns/DebtValue.tsx new file mode 100644 index 00000000..ea5a4d4d --- /dev/null +++ b/src/components/borrow/Table/Columns/DebtValue.tsx @@ -0,0 +1,30 @@ +import { Row } from '@tanstack/react-table' + +import AmountAndValue from 'components/common/AmountAndValue' +import { BN_ZERO } from 'constants/math' +import { BN } from 'utils/helpers' + +export const DEBT_VALUE_META = { + id: 'accountDebtValue', + accessorKey: 'accountDebtValue', + header: 'Debt', +} + +export const debtSortingFn = ( + a: Row, + b: Row, +): number => { + const debtValueA = BN(a.original?.accountDebtValue ?? 0) + const debtValueB = BN(b.original?.accountDebtValue ?? 0) + return debtValueA.minus(debtValueB).toNumber() +} + +interface Props { + asset: Asset + debtAmount?: BigNumber +} +export default function DebtValue(props: Props) { + return ( + + ) +} diff --git a/src/components/borrow/Table/Columns/Manage.tsx b/src/components/borrow/Table/Columns/Manage.tsx index c7112d06..8e5c72c6 100644 --- a/src/components/borrow/Table/Columns/Manage.tsx +++ b/src/components/borrow/Table/Columns/Manage.tsx @@ -48,7 +48,7 @@ export default function Manage(props: Props) { if (!address) return null return ( -
+
) diff --git a/src/components/borrow/Table/Columns/Name.tsx b/src/components/borrow/Table/Columns/Name.tsx index 6ec22845..d4541f1f 100644 --- a/src/components/borrow/Table/Columns/Name.tsx +++ b/src/components/borrow/Table/Columns/Name.tsx @@ -5,6 +5,7 @@ export const NAME_META = { accessorKey: 'asset.symbol', header: 'Asset', id: 'sy interface Props { data: BorrowMarketTableData + v1?: boolean } export default function Name(props: Props) { @@ -12,7 +13,11 @@ export default function Name(props: Props) { return (
- +
) } diff --git a/src/components/borrow/Table/Columns/useDepositedColumns.tsx b/src/components/borrow/Table/Columns/useActiveColumns.tsx similarity index 63% rename from src/components/borrow/Table/Columns/useDepositedColumns.tsx rename to src/components/borrow/Table/Columns/useActiveColumns.tsx index 4c311786..afcbef32 100644 --- a/src/components/borrow/Table/Columns/useDepositedColumns.tsx +++ b/src/components/borrow/Table/Columns/useActiveColumns.tsx @@ -3,24 +3,34 @@ import { useMemo } from 'react' import BorrowRate, { BORROW_RATE_META } from 'components/borrow/Table/Columns/BorrowRate' import Chevron, { CHEVRON_META } from 'components/borrow/Table/Columns/Chevron' -import Debt, { DEBT_META, debtSortingFn } from 'components/borrow/Table/Columns/Debt' +import DebtValue, { + DEBT_VALUE_META, + debtSortingFn, +} from 'components/borrow/Table/Columns/DebtValue' import Liquidity, { LIQUIDITY_META, liquiditySortingFn, } from 'components/borrow/Table/Columns/Liquidity' import Manage, { MANAGE_META } from 'components/borrow/Table/Columns/Manage' import Name, { NAME_META } from 'components/borrow/Table/Columns/Name' +import Action from 'components/v1/Table/borrowings/Columns/Action' -export default function useDepositedColumns() { +interface Props { + v1?: boolean +} + +export default function useActiveColumns(props: Props) { return useMemo[]>(() => { return [ { ...NAME_META, - cell: ({ row }) => , + cell: ({ row }) => , }, { - ...DEBT_META, - cell: ({ row }) => , + ...DEBT_VALUE_META, + cell: ({ row }) => ( + + ), sortingFn: debtSortingFn, }, { @@ -34,12 +44,13 @@ export default function useDepositedColumns() { }, { ...MANAGE_META, - cell: ({ row }) => , + cell: ({ row }) => + props.v1 ? : , }, { ...CHEVRON_META, cell: ({ row }) => , }, ] - }, []) + }, [props.v1]) } diff --git a/src/components/borrow/Table/Columns/useAvailableColumns.tsx b/src/components/borrow/Table/Columns/useAvailableColumns.tsx index 9cc2bd8e..c55f5870 100644 --- a/src/components/borrow/Table/Columns/useAvailableColumns.tsx +++ b/src/components/borrow/Table/Columns/useAvailableColumns.tsx @@ -10,12 +10,16 @@ import Liquidity, { } from 'components/borrow/Table/Columns/Liquidity' import Name, { NAME_META } from 'components/borrow/Table/Columns/Name' -export default function useAvailableColumns() { +interface Props { + v1?: boolean +} + +export default function useAvailableColumns(props: Props) { return useMemo[]>(() => { return [ { ...NAME_META, - cell: ({ row }) => , + cell: ({ row }) => , }, { ...BORROW_RATE_META, @@ -35,5 +39,5 @@ export default function useAvailableColumns() { cell: ({ row }) => , }, ] - }, []) + }, [props.v1]) } diff --git a/src/components/borrow/Table/useBorrowMarketAssetsTableData.ts b/src/components/borrow/Table/useBorrowMarketAssetsTableData.ts index d5650cc2..e786a0c6 100644 --- a/src/components/borrow/Table/useBorrowMarketAssetsTableData.ts +++ b/src/components/borrow/Table/useBorrowMarketAssetsTableData.ts @@ -1,11 +1,14 @@ import { useMemo } from 'react' +import { BN_ZERO } from 'constants/math' import useCurrentAccount from 'hooks/accounts/useCurrentAccount' import useMarkets from 'hooks/markets/useMarkets' +import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice' export default function useBorrowMarketAssetsTableData() { const account = useCurrentAccount() const markets = useMarkets() + const { convertAmount } = useDisplayCurrencyPrice() return useMemo((): { accountBorrowedAssets: BorrowMarketTableData[] @@ -18,15 +21,21 @@ export default function useBorrowMarketAssetsTableData() { markets .filter((market) => market.borrowEnabled) .forEach((market) => { - const debt = account?.debts?.find((debt) => debt.denom === market.asset.denom) + const amount = + account?.debts?.find((debt) => debt.denom === market.asset.denom)?.amount ?? BN_ZERO + const value = amount ? convertAmount(market.asset, amount) : undefined const borrowMarketAsset: BorrowMarketTableData = { ...market, - accountDebt: debt?.amount, + accountDebtAmount: amount, + accountDebtValue: value, + } + + if (borrowMarketAsset.accountDebtAmount?.isZero()) { + availableAssets.push(borrowMarketAsset) + } else { + accountBorrowedAssets.push(borrowMarketAsset) } - ;(borrowMarketAsset.accountDebt ? accountBorrowedAssets : availableAssets).push( - borrowMarketAsset, - ) }) return { @@ -34,5 +43,5 @@ export default function useBorrowMarketAssetsTableData() { availableAssets, allAssets: [...accountBorrowedAssets, ...availableAssets], } - }, [account?.debts, markets]) + }, [account?.debts, markets, convertAmount]) } diff --git a/src/components/common/Background.tsx b/src/components/common/Background.tsx index 54ef2924..2feb7339 100644 --- a/src/components/common/Background.tsx +++ b/src/components/common/Background.tsx @@ -15,19 +15,31 @@ export default function Background() { ) const { pathname } = useLocation() const page = getPage(pathname) - const isHLS = useMemo(() => page.split('-')[0] === 'hls', [page]) + const [isHLS, isV1] = useMemo(() => [page.split('-')[0] === 'hls', page === 'v1'], [page]) useEffect(() => { - useStore.setState({ isHLS }) - }, [isHLS]) + useStore.setState({ isHLS: isHLS, isV1: isV1 }) + }, [isHLS, isV1]) + + const [primaryOrbClassName, secondaryOrbClassName, tertiaryOrbClassName, bodyClassName] = + useMemo(() => { + if (isHLS) { + return ['bg-orb-primary-hls', 'bg-orb-secondary-hls', 'bg-orb-tertiary-hls', 'bg-body-hls'] + } + if (isV1) { + return ['bg-transparent', 'bg-transparent', 'bg-transparent', 'bg-body bg-v1 blur-[2px]'] + } + + return ['bg-orb-primary', 'bg-orb-secondary', 'bg-orb-tertiary', 'bg-body'] + }, [isHLS, isV1]) return (
@@ -39,7 +51,7 @@ export default function Background() { 'max-h-[500px] max-w-[500px]', 'left-[-10vw] top-[-10vw]', 'blur-orb-primary', - isHLS ? ' bg-orb-primary-hls' : 'bg-orb-primary', + primaryOrbClassName, 'translate-x-0 translate-y-0 rounded-full opacity-20', !reduceMotion && 'animate-[float_120s_ease-in-out_infinite_2s]', !reduceMotion && 'transition-bg duration-1000 delay-300', @@ -53,7 +65,7 @@ export default function Background() { 'max-h-[1000px] max-w-[1000px]', 'bottom-[-20vw] right-[-10vw]', 'blur-orb-secondary', - isHLS ? ' bg-orb-secondary-hls' : 'bg-orb-secondary', + secondaryOrbClassName, 'translate-x-0 translate-y-0 rounded-full opacity-30', !reduceMotion && 'transition-bg duration-1000 delay-300', )} @@ -66,7 +78,7 @@ export default function Background() { 'max-h-[600px] max-w-[600px]', 'right-[-4vw] top-[-10vw]', 'blur-orb-tertiary ', - isHLS ? ' bg-orb-tertiary-hls' : 'bg-orb-tertiary', + tertiaryOrbClassName, 'translate-x-0 translate-y-0 rounded-full opacity-20', !reduceMotion && 'animate-[float_180s_ease-in_infinite]', !reduceMotion && 'transition-bg duration-1000 delay-300', diff --git a/src/components/common/Button/ActionButton.tsx b/src/components/common/Button/ActionButton.tsx index 7cf580df..49c5c022 100644 --- a/src/components/common/Button/ActionButton.tsx +++ b/src/components/common/Button/ActionButton.tsx @@ -9,10 +9,15 @@ import useAccountIds from 'hooks/accounts/useAccountIds' import useAccountId from 'hooks/useAccountId' import useStore from 'store' -export default function ActionButton(props: ButtonProps) { - const { className, color, variant, size } = props +interface Props extends ButtonProps { + short?: boolean +} + +export default function ActionButton(props: Props) { + const { className, color, variant, size, short } = props const defaultProps = { className, color, variant, size } const address = useStore((s) => s.address) + const isV1 = useStore((s) => s.isV1) const { data: accountIds } = useAccountIds(address || '') const selectedAccountId = useAccountId() @@ -21,7 +26,8 @@ export default function ActionButton(props: ButtonProps) { useStore.setState({ focusComponent: { component: } }) }, []) - if (!address) return + if (!address) + return if (accountIds && accountIds.length === 0) { return ( @@ -34,7 +40,7 @@ export default function ActionButton(props: ButtonProps) { ) } - if (!selectedAccountId) { + if (!selectedAccountId && !isV1) { return (
) diff --git a/src/components/header/ChainSelect.tsx b/src/components/header/ChainSelect.tsx index 11f761d7..f036fa3d 100644 --- a/src/components/header/ChainSelect.tsx +++ b/src/components/header/ChainSelect.tsx @@ -1,11 +1,12 @@ import classNames from 'classnames' -import { useCallback, useMemo } from 'react' -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' +import React, { useCallback, useMemo } from 'react' +import { useNavigate, useSearchParams } from 'react-router-dom' import { useSWRConfig } from 'swr' import Button from 'components/common/Button' import { ExternalLink } from 'components/common/Icons' import Overlay from 'components/common/Overlay' +import Radio from 'components/common/Radio' import Text from 'components/common/Text' import ChainLogo from 'components/common/chain/ChainLogo' import chains from 'configs/chains' @@ -15,11 +16,52 @@ import useToggle from 'hooks/useToggle' import useStore from 'store' import { NETWORK } from 'types/enums/network' import { ChainInfoID } from 'types/enums/wallet' -import { getPage, getRoute } from 'utils/route' +import { getRoute } from 'utils/route' -const v1Outposts = [ - { chainId: ChainInfoID.Neutron1, name: 'Neutron', url: 'https://neutron.marsprotocol.io' }, - { chainId: ChainInfoID.Osmosis1, name: 'Osmosis', url: 'https://v1.marsprotocol.io' }, +interface V1Outpost { + chainId: ChainInfoID + name: string + url: string + network: NETWORK.MAINNET | NETWORK.TESTNET + target: '_blank' | '_self' +} + +interface ChainOptionProps { + chainConfig?: ChainConfig + onSelect?: (chain: ChainConfig) => void + active: boolean + outpost?: V1Outpost +} + +const v1Outposts: V1Outpost[] = [ + { + chainId: ChainInfoID.Neutron1, + name: 'Neutron', + url: 'https://neutron.marsprotocol.io', + network: NETWORK.MAINNET, + target: '_blank', + }, + { + chainId: ChainInfoID.Pion1, + name: 'Neutron Testnet', + url: '/v1', + network: NETWORK.TESTNET, + target: '_self', + }, + { + chainId: ChainInfoID.Osmosis1, + name: 'Osmosis', + url: '/v1', + network: NETWORK.MAINNET, + target: '_self', + }, + { + chainId: ChainInfoID.OsmosisDevnet, + name: 'Osmosis Devnet', + network: NETWORK.TESTNET, + url: '/v1', + target: '_self', + }, ] export default function ChainSelect() { @@ -27,8 +69,8 @@ export default function ChainSelect() { const chainConfig = useChainConfig() const { mutate } = useSWRConfig() const navigate = useNavigate() - const { pathname } = useLocation() const [searchParams] = useSearchParams() + const isV1 = useStore((s) => s.isV1) const [_, setCurrentChainId] = useCurrentChainId() @@ -39,20 +81,65 @@ export default function ChainSelect() { mutate(() => true) useStore.setState({ chainConfig, + isV1: false, client: undefined, address: undefined, userDomain: undefined, balances: [], }) - navigate(getRoute(getPage(pathname), searchParams)) + navigate(getRoute('trade', searchParams)) }, - [setCurrentChainId, setShowMenu, mutate, navigate, pathname, searchParams], + [setCurrentChainId, setShowMenu, mutate, navigate, searchParams], ) - const currentChains = useMemo(() => { - const currentNetworkType = process.env.NEXT_PUBLIC_NETWORK ?? NETWORK.TESTNET + const ChainOption = (props: ChainOptionProps) => { + const { onSelect, active, outpost, chainConfig } = props + return ( +
onSelect(chainConfig) + : () => { + if (chainConfig) { + setCurrentChainId(chainConfig.id) + useStore.setState({ + chainConfig, + }) + } + window.open(outpost?.url, outpost?.target) + } + } + > + + {outpost ? 'v1' : 'v2'} Outpost + {outpost && outpost.target !== '_self' && } +
+ ) + } - return Object.entries(chains).filter(([_, chain]) => chain.network === currentNetworkType) + const availableChains = useMemo(() => { + const currentNetworkType = process.env.NEXT_PUBLIC_NETWORK ?? NETWORK.TESTNET + const availableChains: { chainId: ChainInfoID; name: string }[] = [] + Object.entries(chains).forEach(([chainId, chainConfig]) => { + if (chainConfig.network !== currentNetworkType) return + availableChains.push({ chainId: chainId as ChainInfoID, name: chainConfig.name }) + }) + if (currentNetworkType === NETWORK.TESTNET) return availableChains + + v1Outposts.forEach((v1Outpost) => { + if ( + !availableChains.find((chain) => chain.chainId === v1Outpost.chainId) && + v1Outpost.network === currentNetworkType + ) + availableChains.push({ chainId: v1Outpost.chainId, name: v1Outpost.name }) + }) + + return availableChains }, []) return ( @@ -63,65 +150,37 @@ export default function ChainSelect() { color='secondary' onClick={() => setShowMenu()} className={classNames('!p-0 w-8 flex items-center justify-center')} - > - -
- - Select Chain - -
-
    - {currentChains.map(([name, chain]) => ( -
  • selectChain(chain)} - > - - {chain.name} -
  • - ))} -
- {process.env.NEXT_PUBLIC_NETWORK === NETWORK.MAINNET && ( - <> + /> + + {availableChains.map((chain, index) => ( +
0 && 'border-t', )} > - - V1 Outposts - + + {chain.name}
-
    - {v1Outposts.map((outpost) => ( -
  • window.open(outpost.url, '_blank')} - key={outpost.name} - > - - - {outpost.name} - -
  • - ))} -
- - )} + {!!chains[chain.chainId] && ( + selectChain(chains[chain.chainId])} + active={chainConfig.name === chain.name && !isV1} + /> + )} + outpost.chainId === chain.chainId)} + active={chainConfig.name === chain.name && isV1} + /> +
+ ))}
) diff --git a/src/components/header/DesktopHeader.tsx b/src/components/header/DesktopHeader.tsx index e6b7b7f2..a3378156 100644 --- a/src/components/header/DesktopHeader.tsx +++ b/src/components/header/DesktopHeader.tsx @@ -16,7 +16,7 @@ import useStore from 'store' import { WalletID } from 'types/enums/wallet' import { getGovernanceUrl } from 'utils/helpers' -export const menuTree = (walletId: WalletID, chainConfig: ChainConfig): MenuTreeEntry[] => [ +const menuTree = (walletId: WalletID, chainConfig: ChainConfig): MenuTreeEntry[] => [ { pages: ['trade', 'trade-advanced'], label: 'Trade', @@ -49,6 +49,7 @@ export default function DesktopHeader() { const isOracleStale = useStore((s) => s.isOracleStale) const isHLS = useStore((s) => s.isHLS) const accountId = useAccountId() + const showAccountMenu = address && !isHLS function handleCloseFocusMode() { if (focusComponent && focusComponent.onClose) focusComponent.onClose() @@ -72,7 +73,7 @@ export default function DesktopHeader() { focusComponent ? 'relative isolate' : 'border-b border-white/20', )} > - + {focusComponent ? (
@@ -92,7 +93,7 @@ export default function DesktopHeader() {
{showStaleOracle && } {accountId && } - {address && !isHLS && } + {showAccountMenu && } diff --git a/src/components/header/V1DesktopHeader.tsx b/src/components/header/V1DesktopHeader.tsx new file mode 100644 index 00000000..4635ddd2 --- /dev/null +++ b/src/components/header/V1DesktopHeader.tsx @@ -0,0 +1,81 @@ +import classNames from 'classnames' +import { useMemo } from 'react' +import { isDesktop } from 'react-device-detect' + +import Wallet from 'components/Wallet' +import EscButton from 'components/common/Button/EscButton' +import Settings from 'components/common/Settings' +import ChainSelect from 'components/header/ChainSelect' +import OracleResyncButton from 'components/header/OracleResyncButton' +import RewardsCenter from 'components/header/RewardsCenter' +import DesktopNavigation from 'components/header/navigation/DesktopNavigation' +import useAccountId from 'hooks/useAccountId' +import useStore from 'store' +import { WalletID } from 'types/enums/wallet' +import { getGovernanceUrl } from 'utils/helpers' + +const menuTree = (walletId: WalletID, chainConfig: ChainConfig): MenuTreeEntry[] => [ + { + pages: ['v1'], + label: 'Red Bank', + }, + { pages: ['governance'], label: 'Governance', externalUrl: getGovernanceUrl(walletId) }, +] + +export default function DesktopHeader() { + const address = useStore((s) => s.address) + const focusComponent = useStore((s) => s.focusComponent) + const isOracleStale = useStore((s) => s.isOracleStale) + const accountId = useAccountId() + + function handleCloseFocusMode() { + if (focusComponent && focusComponent.onClose) focusComponent.onClose() + useStore.setState({ focusComponent: null }) + } + + const showStaleOracle = useMemo(() => isOracleStale && address, [isOracleStale, address]) + + if (!isDesktop) return null + + return ( +
+
+ + + {focusComponent ? ( +
+
+ {address && ( +
+ + +
+ )} +
+ {!address && } + +
+
+ ) : ( +
+ {showStaleOracle && } + {accountId && } + + + +
+ )} +
+
+ ) +} diff --git a/src/components/header/navigation/DesktopNavigation.tsx b/src/components/header/navigation/DesktopNavigation.tsx index 92fc1a47..a8c0c888 100644 --- a/src/components/header/navigation/DesktopNavigation.tsx +++ b/src/components/header/navigation/DesktopNavigation.tsx @@ -4,7 +4,6 @@ import { useMemo } from 'react' import Button from 'components/common/Button' import { ChevronDown, Logo } from 'components/common/Icons' -import { menuTree } from 'components/header/DesktopHeader' import { NavLink } from 'components/header/navigation/NavLink' import { NavMenu } from 'components/header/navigation/NavMenu' import useChainConfig from 'hooks/useChainConfig' @@ -12,19 +11,24 @@ import useToggle from 'hooks/useToggle' import useStore from 'store' import { WalletID } from 'types/enums/wallet' +interface Props { + menuTree: (walletId: WalletID, chainConfig: ChainConfig) => MenuTreeEntry[] +} + export function getIsActive(pages: string[]) { const segments = location.pathname.split('/') return pages.some((page) => segments.includes(page)) } -export default function DesktopNavigation() { +export default function DesktopNavigation(props: Props) { + const { menuTree } = props const [showMenu, setShowMenu] = useToggle() const { recentWallet } = useShuttle() const chainConfig = useChainConfig() const walletId = (recentWallet?.providerId as WalletID) ?? WalletID.Keplr const focusComponent = useStore((s) => s.focusComponent) - const menu = useMemo(() => menuTree(walletId, chainConfig), [walletId, chainConfig]) + const menu = useMemo(() => menuTree(walletId, chainConfig), [walletId, chainConfig, menuTree]) return (
} /> } /> } /> + } /> } /> {chainConfig.hls && } />} {chainConfig.hls && } />} @@ -47,6 +49,7 @@ export default function Routes() { } /> {chainConfig.hls && } />} {chainConfig.hls && } />} + } /> } /> diff --git a/src/components/portfolio/Account/Summary.tsx b/src/components/portfolio/Account/Summary.tsx index 3818afa6..00ed0247 100644 --- a/src/components/portfolio/Account/Summary.tsx +++ b/src/components/portfolio/Account/Summary.tsx @@ -17,6 +17,7 @@ import { DEFAULT_PORTFOLIO_STATS } from 'utils/constants' interface Props { accountId: string + v1?: boolean } function Content(props: Props) { @@ -74,21 +75,21 @@ function Content(props: Props) { title: ( ), - sub: DEFAULT_PORTFOLIO_STATS[4].sub, + sub: props.v1 ? 'Total Leverage' : DEFAULT_PORTFOLIO_STATS[4].sub, }, ] - }, [account, assets, borrowAssets, hlsStrategies, lendingAssets, prices, vaultAprs]) + }, [account, assets, borrowAssets, hlsStrategies, lendingAssets, prices, vaultAprs, props.v1]) return ( ) diff --git a/src/components/portfolio/Overview/Summary.tsx b/src/components/portfolio/Overview/Summary.tsx index dc121c9d..4b4afd88 100644 --- a/src/components/portfolio/Overview/Summary.tsx +++ b/src/components/portfolio/Overview/Summary.tsx @@ -89,7 +89,7 @@ export default function PortfolioSummary() { title: ( ), diff --git a/src/components/v1/Borrowings.tsx b/src/components/v1/Borrowings.tsx new file mode 100644 index 00000000..b480f899 --- /dev/null +++ b/src/components/v1/Borrowings.tsx @@ -0,0 +1,47 @@ +import BorrowingsTable from 'components/borrow/Table/ActiveBorrowingsTable' +import useV1BorrowingsTableData from 'components/v1/Table/useV1BorrowingsTableData' +import { BN_ZERO } from 'constants/math' +import useBorrowEnabledAssets from 'hooks/assets/useBorrowEnabledAssets' + +export default function Borrowings() { + const { debtAssets } = useV1BorrowingsTableData() + + if (!debtAssets?.length) { + return + } + + return ( + <> + + + ) +} + +function Fallback() { + const assets = useBorrowEnabledAssets() + const data: BorrowMarketTableData[] = assets.map((asset) => ({ + asset, + apy: { + borrow: 0, + deposit: 0, + }, + ltv: { + max: 0, + liq: 0, + }, + liquidity: BN_ZERO, + marketLiquidityRate: 0, + cap: { + denom: asset.denom, + max: BN_ZERO, + used: BN_ZERO, + }, + debt: BN_ZERO, + borrowEnabled: true, + depositEnabled: true, + deposits: BN_ZERO, + accountDebt: BN_ZERO, + })) + + return +} diff --git a/src/components/v1/Deposits.tsx b/src/components/v1/Deposits.tsx new file mode 100644 index 00000000..4fc025af --- /dev/null +++ b/src/components/v1/Deposits.tsx @@ -0,0 +1,46 @@ +import DepositsTable from 'components/earn/lend/Table/DepositedLendsTable' +import useV1DepositsTableData from 'components/v1/Table/useV1DepositsTableData' +import { BN_ZERO } from 'constants/math' +import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets' + +export default function Deposits() { + const { depositAssets } = useV1DepositsTableData() + + if (!depositAssets?.length) { + return + } + + return ( + <> + + + ) +} + +function Fallback() { + const assets = useMarketEnabledAssets() + + const data: LendingMarketTableData[] = assets.map((asset) => ({ + asset, + borrowEnabled: true, + depositEnabled: true, + debt: BN_ZERO, + deposits: BN_ZERO, + liquidity: BN_ZERO, + cap: { + max: BN_ZERO, + used: BN_ZERO, + denom: asset.denom, + }, + apy: { + borrow: 0, + deposit: 0, + }, + ltv: { + max: 0, + liq: 0, + }, + })) + + return +} diff --git a/src/components/v1/Table/borrowings/Columns/Action.tsx b/src/components/v1/Table/borrowings/Columns/Action.tsx new file mode 100644 index 00000000..a8238c63 --- /dev/null +++ b/src/components/v1/Table/borrowings/Columns/Action.tsx @@ -0,0 +1,20 @@ +import BorrowButton from 'components/v1/Table/borrowings/Columns/BorrowButton' +import Manage from 'components/v1/Table/borrowings/Columns/Manage' + +export const MANAGE_META = { + accessorKey: 'manage', + enableSorting: false, + header: '', +} + +interface Props { + data: BorrowMarketTableData +} + +export default function Action(props: Props) { + const hasDebt = !props.data.accountDebtAmount?.isZero() ?? false + + if (hasDebt) return + + return +} diff --git a/src/components/v1/Table/borrowings/Columns/BorrowButton.tsx b/src/components/v1/Table/borrowings/Columns/BorrowButton.tsx new file mode 100644 index 00000000..4232532a --- /dev/null +++ b/src/components/v1/Table/borrowings/Columns/BorrowButton.tsx @@ -0,0 +1,51 @@ +import ActionButton from 'components/common/Button/ActionButton' +import { Plus } from 'components/common/Icons' +import Text from 'components/common/Text' +import { Tooltip } from 'components/common/Tooltip' +import ConditionalWrapper from 'hocs/ConditionalWrapper' +import useAccount from 'hooks/accounts/useAccount' +import useStore from 'store' + +interface Props { + data: BorrowMarketTableData +} +export default function BorrowButton(props: Props) { + const address = useStore((s) => s.address) + const { data: account } = useAccount(address) + + const hasCollateral = account?.lends?.length ?? 0 > 0 + + return ( +
+ ( + {`You don’t have assets deposited in the Red Bank. Please deposit assets before you borrow.`} + } + contentClassName='max-w-[200px]' + className='ml-auto' + > + {children} + + )} + > + } + disabled={!hasCollateral} + color='tertiary' + onClick={(e) => { + useStore.setState({ + v1BorrowAndRepayModal: { type: 'borrow', data: props.data }, + }) + e.stopPropagation() + }} + text='Borrow' + short + /> + +
+ ) +} diff --git a/src/components/v1/Table/borrowings/Columns/Manage.tsx b/src/components/v1/Table/borrowings/Columns/Manage.tsx new file mode 100644 index 00000000..b05b896b --- /dev/null +++ b/src/components/v1/Table/borrowings/Columns/Manage.tsx @@ -0,0 +1,47 @@ +import { useMemo } from 'react' + +import DropDownButton from 'components/common/Button/DropDownButton' +import { HandCoins, Plus } from 'components/common/Icons' +import useWalletBalances from 'hooks/useWalletBalances' +import useStore from 'store' +import { byDenom } from 'utils/array' + +interface Props { + data: BorrowMarketTableData +} + +export default function Manage(props: Props) { + const address = useStore((s) => s.address) + const { data: balances } = useWalletBalances(address) + const hasBalance = !!balances.find(byDenom(props.data.asset.denom)) + + const ITEMS: DropDownItem[] = useMemo( + () => [ + { + icon: , + text: 'Borrow more', + onClick: () => + useStore.setState({ + v1BorrowAndRepayModal: { type: 'borrow', data: props.data }, + }), + }, + { + icon: , + text: 'Repay', + onClick: () => + useStore.setState({ + v1BorrowAndRepayModal: { type: 'repay', data: props.data }, + }), + disabled: !hasBalance, + disabledTooltip: `You don’t have any ${props.data.asset.symbol} in your Wallet.`, + }, + ], + [hasBalance, props.data], + ) + + return ( +
+ +
+ ) +} diff --git a/src/components/v1/Table/deposits/Columns/Action.tsx b/src/components/v1/Table/deposits/Columns/Action.tsx new file mode 100644 index 00000000..b08f2c52 --- /dev/null +++ b/src/components/v1/Table/deposits/Columns/Action.tsx @@ -0,0 +1,19 @@ +import DepositButton from 'components/v1/Table/deposits/Columns/DepositButton' +import Manage from 'components/v1/Table/deposits/Columns/Manage' + +export const MANAGE_META = { + accessorKey: 'manage', + enableSorting: false, + header: '', +} + +interface Props { + data: LendingMarketTableData +} +export default function Action(props: Props) { + const hasDeposits = !props.data.accountLentAmount?.isZero() ?? false + + if (hasDeposits) return + + return +} diff --git a/src/components/v1/Table/deposits/Columns/DepositButton.tsx b/src/components/v1/Table/deposits/Columns/DepositButton.tsx new file mode 100644 index 00000000..60c876cf --- /dev/null +++ b/src/components/v1/Table/deposits/Columns/DepositButton.tsx @@ -0,0 +1,51 @@ +import ActionButton from 'components/common/Button/ActionButton' +import { ArrowUpLine } from 'components/common/Icons' +import Text from 'components/common/Text' +import { Tooltip } from 'components/common/Tooltip' +import ConditionalWrapper from 'hocs/ConditionalWrapper' +import useWalletBalances from 'hooks/useWalletBalances' +import useStore from 'store' +import { byDenom } from 'utils/array' + +interface Props { + data: LendingMarketTableData +} +export default function DepositButton(props: Props) { + const address = useStore((s) => s.address) + const { data: balances } = useWalletBalances(address) + const hasBalance = !!balances.find(byDenom(props.data.asset.denom)) + + return ( +
+ ( + {`You don’t have any ${props.data.asset.symbol} in your Wallet.`} + } + contentClassName='max-w-[200px]' + className='ml-auto' + > + {children} + + )} + > + } + disabled={!hasBalance} + color='tertiary' + onClick={(e) => { + useStore.setState({ + v1DepositAndWithdrawModal: { type: 'deposit', data: props.data }, + }) + e.stopPropagation() + }} + text='Deposit' + short + /> + +
+ ) +} diff --git a/src/components/v1/Table/deposits/Columns/Manage.tsx b/src/components/v1/Table/deposits/Columns/Manage.tsx new file mode 100644 index 00000000..728afbf1 --- /dev/null +++ b/src/components/v1/Table/deposits/Columns/Manage.tsx @@ -0,0 +1,48 @@ +import { useMemo } from 'react' + +import DropDownButton from 'components/common/Button/DropDownButton' +import { ArrowDownLine, ArrowUpLine } from 'components/common/Icons' +import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal' +import useWalletBalances from 'hooks/useWalletBalances' +import useStore from 'store' +import { byDenom } from 'utils/array' + +interface Props { + data: LendingMarketTableData +} + +export default function Manage(props: Props) { + const address = useStore((s) => s.address) + const { data: balances } = useWalletBalances(address) + const hasBalance = !!balances.find(byDenom(props.data.asset.denom)) + + const ITEMS: DropDownItem[] = useMemo( + () => [ + { + icon: , + text: 'Deposit more', + onClick: () => + useStore.setState({ + v1DepositAndWithdrawModal: { type: 'deposit', data: props.data }, + }), + disabled: !hasBalance, + disabledTooltip: `You don’t have any ${props.data.asset.symbol} in your Wallet.`, + }, + { + icon: , + text: 'Withdraw', + onClick: () => + useStore.setState({ + v1DepositAndWithdrawModal: { type: 'withdraw', data: props.data }, + }), + }, + ], + [hasBalance, props.data], + ) + + return ( +
+ +
+ ) +} diff --git a/src/components/v1/Table/useV1BorrowingsTableData.ts b/src/components/v1/Table/useV1BorrowingsTableData.ts new file mode 100644 index 00000000..62b544d3 --- /dev/null +++ b/src/components/v1/Table/useV1BorrowingsTableData.ts @@ -0,0 +1,38 @@ +import { useMemo } from 'react' + +import { BN_ZERO } from 'constants/math' +import useAccount from 'hooks/accounts/useAccount' +import useMarkets from 'hooks/markets/useMarkets' +import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice' +import useStore from 'store' + +export default function useV1BorrowingsTableData() { + const address = useStore((s) => s.address) + const markets = useMarkets() + const { data: v1Positions } = useAccount(address) + const { convertAmount } = useDisplayCurrencyPrice() + + return useMemo((): { + debtAssets: BorrowMarketTableData[] + } => { + const userDebts = v1Positions?.debts ?? [] + const debtAssets: BorrowMarketTableData[] = [] + + markets + .filter((market) => market.borrowEnabled) + .forEach((market) => { + const amount = + userDebts.find((debt) => debt.denom === market.asset.denom)?.amount ?? BN_ZERO + const value = amount ? convertAmount(market.asset, amount) : undefined + + const borrowMarketAsset: BorrowMarketTableData = { + ...market, + accountDebtAmount: amount, + accountDebtValue: value, + } + debtAssets.push(borrowMarketAsset) + }) + + return { debtAssets } + }, [v1Positions, markets, convertAmount]) +} diff --git a/src/components/v1/Table/useV1DepositsTableData.ts b/src/components/v1/Table/useV1DepositsTableData.ts new file mode 100644 index 00000000..3224a224 --- /dev/null +++ b/src/components/v1/Table/useV1DepositsTableData.ts @@ -0,0 +1,39 @@ +import { useMemo } from 'react' + +import { BN_ZERO } from 'constants/math' +import useAccount from 'hooks/accounts/useAccount' +import useMarkets from 'hooks/markets/useMarkets' +import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice' +import useStore from 'store' +import { byDenom } from 'utils/array' + +export default function useV1DepositsTableData(): { + depositAssets: LendingMarketTableData[] +} { + const address = useStore((s) => s.address) + const markets = useMarkets() + const { data: v1Positions } = useAccount(address) + const { convertAmount } = useDisplayCurrencyPrice() + + return useMemo(() => { + const depositAssets: LendingMarketTableData[] = [] + const userCollateral = v1Positions?.lends ?? [] + + markets.forEach((market) => { + const amount = userCollateral.find(byDenom(market.asset.denom))?.amount ?? BN_ZERO + const value = amount ? convertAmount(market.asset, amount) : undefined + + const lendingMarketAsset: LendingMarketTableData = { + ...market, + accountLentValue: value, + accountLentAmount: amount, + } + + depositAssets.push(lendingMarketAsset) + }) + + return { + depositAssets, + } + }, [markets, v1Positions, convertAmount]) +} diff --git a/src/components/v1/V1Intro.tsx b/src/components/v1/V1Intro.tsx new file mode 100644 index 00000000..a5a67838 --- /dev/null +++ b/src/components/v1/V1Intro.tsx @@ -0,0 +1,24 @@ +import WalletConnectButton from 'components/Wallet/WalletConnectButton' +import Intro from 'components/common/Intro' +import useStore from 'store' + +export default function V1Intro() { + const address = useStore((state) => state.address) + return ( + + Welcome to the Red Bank! +
+ This is the first version (v1) of the Red Bank. It provides simple lending and borrowing, + without the use of Credit Accounts. +
+ Deposited funds can‘t be used on v2 as collateral. + + } + bg='v1' + > + {!address && } +
+ ) +} diff --git a/src/configs/assets/WETH.xal.ts b/src/configs/assets/WETH.axl.ts similarity index 100% rename from src/configs/assets/WETH.xal.ts rename to src/configs/assets/WETH.axl.ts diff --git a/src/configs/assets/stkATOM.ts b/src/configs/assets/stkATOM.ts new file mode 100644 index 00000000..4116de1b --- /dev/null +++ b/src/configs/assets/stkATOM.ts @@ -0,0 +1,16 @@ +const stkATOM: AssetMetaData = { + symbol: 'stkATOM', + name: 'Persistence Staked Atom', + id: 'stkATOM', + color: '#c73238', + logo: '/images/tokens/stkatom.svg', + decimals: 6, + hasOraclePrice: true, + isEnabled: true, + isMarket: true, + isDisplayCurrency: true, + isAutoLendEnabled: false, + isStaking: true, +} + +export default stkATOM diff --git a/src/configs/assets/wstETH.ts b/src/configs/assets/wstETH.ts new file mode 100644 index 00000000..d2d24e63 --- /dev/null +++ b/src/configs/assets/wstETH.ts @@ -0,0 +1,18 @@ +const wstETH: AssetMetaData = { + symbol: 'wstETH', + id: 'wstETH', + name: 'Lido Wrapped Staked Ethereum', + color: '#00a3ff', + logo: '/images/tokens/wsteth.svg', + decimals: 18, + hasOraclePrice: true, + isEnabled: true, + isMarket: true, + isDisplayCurrency: true, + isAutoLendEnabled: true, + isBorrowEnabled: true, + pythPriceFeedId: '0x6df640f3b8963d8f8358f791f352b8364513f6ab1cca5ed3f1f7b5448980e784', + pythFeedName: 'WSTETHUSD', +} + +export default wstETH diff --git a/src/configs/chains/neutron/neutron-1.ts b/src/configs/chains/neutron/neutron-1.ts new file mode 100644 index 00000000..00f0b9bb --- /dev/null +++ b/src/configs/chains/neutron/neutron-1.ts @@ -0,0 +1,86 @@ +import { Bech32Address } from '@keplr-wallet/cosmos' + +import ATOM from 'configs/assets/ATOM' +import DYDX from 'configs/assets/DYDX' +import NTRN from 'configs/assets/NTRN' +import USDCaxl from 'configs/assets/USDC.axl' +import USDollar from 'configs/assets/USDollar' +import WETHaxl from 'configs/assets/WETH.axl' +import stATOM from 'configs/assets/stATOM' +import stkATOM from 'configs/assets/stkATOM' +import wstETH from 'configs/assets/wstETH' +import { NETWORK } from 'types/enums/network' +import { ChainInfoID } from 'types/enums/wallet' + +const Neutron1: ChainConfig = { + assets: [ + { ...NTRN, denom: 'untrn' }, + { ...USDCaxl, denom: 'ibc/F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349' }, + { + ...ATOM, + denom: 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9', + }, + { + ...stATOM, + denom: 'ibc/B7864B03E1B9FD4F049243E92ABD691586F682137037A9F3FCA5222815620B3C', + }, + { + ...stkATOM, + denom: 'ibc/3649CE0C8A2C79048D8C6F31FF18FA69C9BC7EB193512E0BD03B733011290445', + }, + { ...WETHaxl, denom: 'ibc/A585C2D15DCD3B010849B453A2CFCB5E213208A5AB665691792684C26274304D' }, + { + ...wstETH, + denom: 'factory/neutron1ug740qrkquxzrk2hh29qrlx3sktkfml3je7juusc2te7xmvsscns0n2wry/wstETH', + }, + { ...DYDX, denom: 'ibc/2CB87BCE0937B1D1DFCEE79BE4501AAF3C265E923509AEAC410AD85D27F35130' }, + USDollar, + ], + id: ChainInfoID.Neutron1, + name: 'Neutron', + contracts: { + redBank: 'neutron1n97wnm7q6d2hrcna3rqlnyqw2we6k0l8uqvmyqq6gsml92epdu7quugyph', + incentives: 'neutron1aszpdh35zsaz0yj80mz7f5dtl9zq5jfl8hgm094y0j0vsychfekqxhzd39', + oracle: 'neutron1dwp6m7pdrz6rnhdyrx5ha0acsduydqcpzkylvfgspsz60pj2agxqaqrr7g', + swapper: 'neutron1udr9fc3kd743dezrj38v2ac74pxxr6qsx4xt4nfpcfczgw52rvyqyjp5au', + params: 'neutron16kqg3hr2qc36gz2wqvdzsctatkmzd3ss5gc07tnj6u3n5ajw89asrx8hfp', + creditManager: 'neutron1kj50g96c86nu7jmy5y7uy5cyjanntgru0eekmwz2qcmyyvx6383s8dgvm6', + accountNft: 'neutron17wvpxdc3k37054ume0ga4r0r6ra2rpfe622m0ecgd9s7xd5s0qusspc4ct', + perps: 'neutron14v9g7regs90qvful7djcajsvrfep5pg9qau7qm6wya6c2lzcpnms692dlt', + pyth: 'neutron1m2emc93m9gpwgsrsf2vylv9xvgqh654630v7dfrhrkmr5slly53spg85wv', + }, + endpoints: { + routes: 'https://app.astroport.fi/api/routes', + rpc: process.env.NEXT_PUBLIC_NEUTRON_RPC ?? 'https://rpc-kralum.neutron-1.neutron.org', + rest: process.env.NEXT_PUBLIC_NEUTRON_REST ?? 'https://rest-kralum.neutron-1.neutron.org', + swap: 'https://neutron.astroport.fi/swap', + pools: '', //TODO: ⛓️ Implement this + explorer: 'https://mintscan.io/neutron', + aprs: { + vaults: 'https://api.marsprotocol.io/v1/vaults/neutron', + stride: 'https://edge.stride.zone/api/stake-stats', + }, + }, + network: NETWORK.MAINNET, + vaults: [], + explorerName: 'Mintscan', + bech32Config: Bech32Address.defaultBech32Config('neutron'), + defaultCurrency: { + coinDenom: 'NTRN', + coinMinimalDenom: 'untrn', + coinDecimals: 6, + coinGeckoId: 'neutron', + gasPriceStep: { + low: 0, + average: 0.025, + high: 0.04, + }, + }, + features: ['ibc-transfer', 'ibc-go'], + gasPrice: '0.025untrn', + hls: false, + perps: false, + farm: false, +} + +export default Neutron1 diff --git a/src/configs/chains/osmosis/osmosis-1.ts b/src/configs/chains/osmosis/osmosis-1.ts index b25594e8..227c022b 100644 --- a/src/configs/chains/osmosis/osmosis-1.ts +++ b/src/configs/chains/osmosis/osmosis-1.ts @@ -13,7 +13,7 @@ import USDCaxl from 'configs/assets/USDC.axl' import USDT from 'configs/assets/USDT' import USDollar from 'configs/assets/USDollar' import WBTCaxl from 'configs/assets/WBTC.axl' -import WETHaxl from 'configs/assets/WETH.xal' +import WETHaxl from 'configs/assets/WETH.axl' import OSMO_ATOM from 'configs/assets/lp/OSMO-ATOM' import OSMO_USDC from 'configs/assets/lp/OSMO_USDC' import OSMO_WBTC from 'configs/assets/lp/OSMO_WBTC' diff --git a/src/constants/pageMetadata.ts b/src/constants/pageMetadata.ts index a212f879..86344373 100644 --- a/src/constants/pageMetadata.ts +++ b/src/constants/pageMetadata.ts @@ -47,6 +47,11 @@ const PAGE_METADATA = { 'Stake MARS token to ascend to the Martian Council and help govern key changes to the protocol.', keywords: 'martian council, mars governance, cosmos governance, mars voting, mars staking', }, + v1: { + title: 'Mars Protocol V1', + description: "Lend, borrow and earn on the galaxy's most powerful credit protocol.", + keywords: 'martian council, mars governance, cosmos governance, mars voting, mars staking', + }, } export default PAGE_METADATA diff --git a/src/constants/wallets.ts b/src/constants/wallets.ts index c641fbf9..062d7948 100644 --- a/src/constants/wallets.ts +++ b/src/constants/wallets.ts @@ -15,7 +15,7 @@ export const WALLETS: WalletInfos = { walletConnect: 'Cosmostation WalletConnect', imageURL: '/images/wallets/cosmostation.png', mobileImageURL: '/images/wallets/cosmostation-wc.png', - supportedChains: [ChainInfoID.Osmosis1, ChainInfoID.OsmosisDevnet], + supportedChains: [ChainInfoID.Osmosis1, ChainInfoID.OsmosisDevnet, ChainInfoID.Pion1], }, [WalletID.Keplr]: { name: 'Keplr Wallet', diff --git a/src/hooks/accounts/useAccount.tsx b/src/hooks/accounts/useAccount.tsx index 980cc590..2a5f6a06 100644 --- a/src/hooks/accounts/useAccount.tsx +++ b/src/hooks/accounts/useAccount.tsx @@ -1,14 +1,22 @@ import useSWR from 'swr' import getAccount from 'api/accounts/getAccount' +import getV1Positions from 'api/v1/getV1Positions' import useChainConfig from 'hooks/useChainConfig' +import useStore from 'store' export default function useAccount(accountId?: string, suspense?: boolean) { const chainConfig = useChainConfig() + const address = useStore((s) => s.address) + const isV1 = accountId === address + + const cacheKey = isV1 + ? `chains/${chainConfig.id}/v1/user/${accountId}` + : `chains/${chainConfig.id}/accounts/${accountId}` return useSWR( - accountId && `chains/${chainConfig.id}/accounts/${accountId}`, - () => getAccount(chainConfig, accountId), + accountId && cacheKey, + () => (isV1 ? getV1Positions(chainConfig, accountId) : getAccount(chainConfig, accountId)), { suspense: suspense, revalidateOnFocus: false, diff --git a/src/hooks/useClients.ts b/src/hooks/useClients.ts index 24b03dbb..4cd3d471 100644 --- a/src/hooks/useClients.ts +++ b/src/hooks/useClients.ts @@ -11,6 +11,7 @@ import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.cl import { MarsPerpsQueryClient } from 'types/generated/mars-perps/MarsPerps.client' import { MarsRedBankQueryClient } from 'types/generated/mars-red-bank/MarsRedBank.client' import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client' +import { getUrl } from 'utils/url' export default function useClients() { const chainConfig = useChainConfig() @@ -18,8 +19,7 @@ export default function useClients() { const swr = useSWR( `chains/${chainConfig.id}/clients`, async () => { - const client = await CosmWasmClient.connect(chainConfig.endpoints.rpc) - + const client = await CosmWasmClient.connect(getUrl(chainConfig.endpoints.rpc)) return { creditManager: new MarsCreditManagerQueryClient( client, diff --git a/src/pages/BorrowPage.tsx b/src/pages/BorrowPage.tsx index 8eefc851..eec33e71 100644 --- a/src/pages/BorrowPage.tsx +++ b/src/pages/BorrowPage.tsx @@ -1,11 +1,9 @@ import Borrowings from 'components/borrow/Borrowings' import BorrowIntro from 'components/borrow/BorrowIntro' -import MigrationBanner from 'components/common/MigrationBanner' export default function BorrowPage() { return (
-
diff --git a/src/pages/FarmPage.tsx b/src/pages/FarmPage.tsx index 4c6bcccd..07938c9c 100644 --- a/src/pages/FarmPage.tsx +++ b/src/pages/FarmPage.tsx @@ -1,13 +1,11 @@ +import Tab from 'components/earn/Tab' import FarmIntro from 'components/earn/farm/FarmIntro' import Vaults from 'components/earn/farm/Vaults' -import Tab from 'components/earn/Tab' -import MigrationBanner from 'components/common/MigrationBanner' import { EARN_TABS } from 'constants/pages' export default function FarmPage() { return (
- diff --git a/src/pages/HLSFarmPage.tsx b/src/pages/HLSFarmPage.tsx index 40e75b38..290ceb4a 100644 --- a/src/pages/HLSFarmPage.tsx +++ b/src/pages/HLSFarmPage.tsx @@ -1,13 +1,11 @@ import Tab from 'components/earn/Tab' import AvailableHLSVaults from 'components/hls/Farm/AvailableHLSVaults' import HlsFarmIntro from 'components/hls/Farm/HLSFarmIntro' -import MigrationBanner from 'components/common/MigrationBanner' import { HLS_TABS } from 'constants/pages' export default function HLSFarmPage() { return (
- diff --git a/src/pages/HLSStakingPage.tsx b/src/pages/HLSStakingPage.tsx index 3aeac7c8..d8f57501 100644 --- a/src/pages/HLSStakingPage.tsx +++ b/src/pages/HLSStakingPage.tsx @@ -2,13 +2,11 @@ import Tab from 'components/earn/Tab' import ActiveStakingAccounts from 'components/hls/Staking/ActiveStakingAccounts' import AvailableHlsStakingAssets from 'components/hls/Staking/AvailableHLSStakingAssets' import HLSStakingIntro from 'components/hls/Staking/HLSStakingIntro' -import MigrationBanner from 'components/common/MigrationBanner' import { HLS_TABS } from 'constants/pages' export default function HLSStakingPage() { return (
- diff --git a/src/pages/LendPage.tsx b/src/pages/LendPage.tsx index 81a15210..450ede17 100644 --- a/src/pages/LendPage.tsx +++ b/src/pages/LendPage.tsx @@ -1,4 +1,3 @@ -import MigrationBanner from 'components/common/MigrationBanner' import Tab from 'components/earn/Tab' import LendIntro from 'components/earn/lend/LendIntro' import Lends from 'components/earn/lend/Lends' @@ -10,7 +9,6 @@ export default function LendPage() { return (
- {chainConfig.farm && } diff --git a/src/pages/PortfolioAccountPage.tsx b/src/pages/PortfolioAccountPage.tsx index b0308320..ab0464b5 100644 --- a/src/pages/PortfolioAccountPage.tsx +++ b/src/pages/PortfolioAccountPage.tsx @@ -1,6 +1,5 @@ import { useNavigate, useParams, useSearchParams } from 'react-router-dom' -import MigrationBanner from 'components/common/MigrationBanner' import ShareBar from 'components/common/ShareBar' import Balances from 'components/portfolio/Account/Balances' import BreadCrumbs from 'components/portfolio/Account/BreadCrumbs' @@ -25,7 +24,6 @@ export default function PortfolioAccountPage() { return (
- diff --git a/src/pages/PortfolioPage.tsx b/src/pages/PortfolioPage.tsx index 6e7269c2..0d05c6ae 100644 --- a/src/pages/PortfolioPage.tsx +++ b/src/pages/PortfolioPage.tsx @@ -1,13 +1,11 @@ -import MigrationBanner from 'components/common/MigrationBanner' +import ShareBar from 'components/common/ShareBar' import AccountOverview from 'components/portfolio/Overview' import PortfolioSummary from 'components/portfolio/Overview/Summary' import PortfolioIntro from 'components/portfolio/PortfolioIntro' -import ShareBar from 'components/common/ShareBar' export default function PortfolioPage() { return (
- diff --git a/src/pages/TradePage.tsx b/src/pages/TradePage.tsx index 0a27a128..34eef272 100644 --- a/src/pages/TradePage.tsx +++ b/src/pages/TradePage.tsx @@ -1,7 +1,6 @@ import { useMemo } from 'react' import { useLocation } from 'react-router-dom' -import MigrationBanner from 'components/common/MigrationBanner' import AccountDetailsCard from 'components/trade/AccountDetailsCard' import TradeChart from 'components/trade/TradeChart' import TradeModule from 'components/trade/TradeModule' @@ -47,7 +46,6 @@ export default function TradePage() { ) return (
-
diff --git a/src/pages/V1Page.tsx b/src/pages/V1Page.tsx new file mode 100644 index 00000000..0236c50a --- /dev/null +++ b/src/pages/V1Page.tsx @@ -0,0 +1,19 @@ +import Summary from 'components/portfolio/Account/Summary' +import Borrowings from 'components/v1/Borrowings' +import Deposits from 'components/v1/Deposits' +import V1Intro from 'components/v1/V1Intro' +import useStore from 'store' + +export default function V1Page() { + const address = useStore((s) => s.address) + return ( +
+ + {address && } +
+ + +
+
+ ) +} diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index dba5d0a4..72cb6af9 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -4,13 +4,14 @@ import { isMobile } from 'react-device-detect' import { useLocation } from 'react-router-dom' import { SWRConfig } from 'swr' +import ModalsContainer from 'components/Modals/ModalsContainer' import AccountDetails from 'components/account/AccountDetails' import Background from 'components/common/Background' import Footer from 'components/common/Footer' import PageMetadata from 'components/common/PageMetadata' import Toaster from 'components/common/Toaster' import DesktopHeader from 'components/header/DesktopHeader' -import ModalsContainer from 'components/Modals/ModalsContainer' +import V1DesktopHeader from 'components/header/V1DesktopHeader' import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { LocalStorageKeys } from 'constants/localStorageKeys' import useLocalStorage from 'hooks/localStorage/useLocalStorage' @@ -25,6 +26,8 @@ interface Props { } function PageContainer(props: Props) { + const isV1 = useStore((s) => s.isV1) + if (isMobile) return props.children if (!props.focusComponent) @@ -32,7 +35,8 @@ function PageContainer(props: Props) {
{props.children} @@ -50,6 +54,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { const location = useLocation() const focusComponent = useStore((s) => s.focusComponent) const address = useStore((s) => s.address) + const isV1 = useStore((s) => s.isV1) const [reduceMotion] = useLocalStorage( LocalStorageKeys.REDUCE_MOTION, @@ -67,7 +72,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { - + {isV1 ? : }
{ + let msg: RedBankExecuteMsg + let toastOptions: ToastObjectOptions = { + action: type, + accountId: get().address, + changes: {}, + } + let funds: Coin[] = [] + + switch (type) { + case 'withdraw': + msg = { + withdraw: { + amount: coin.amount.toString(), + denom: coin.denom, + }, + } + toastOptions = { + ...toastOptions, + changes: { deposits: [coin] }, + target: 'wallet', + } + break + case 'repay': + msg = { + repay: {}, + } + toastOptions.changes = { deposits: [coin] } + funds = [coin.toCoin()] + break + case 'borrow': + msg = { + borrow: { + amount: coin.amount.toString(), + denom: coin.denom, + }, + } + toastOptions = { + ...toastOptions, + changes: { debts: [coin] }, + target: 'wallet', + } + break + default: + msg = { + deposit: {}, + } + toastOptions.changes = { deposits: [coin] } + funds = [coin.toCoin()] + } + + const redBankContract = get().chainConfig.contracts.redBank + + const response = get().executeMsg({ + messages: [generateExecutionMessage(get().address, redBankContract, msg, funds)], + }) + + get().setToast({ + response, + options: toastOptions, + }) + + return response.then((response) => !!response.result) + }, } } diff --git a/src/store/slices/common.ts b/src/store/slices/common.ts index eee116cc..f8ffb448 100644 --- a/src/store/slices/common.ts +++ b/src/store/slices/common.ts @@ -19,5 +19,6 @@ export default function createCommonSlice(set: SetState, get: GetSt useAutoRepay: true, isOracleStale: false, isHLS: false, + isV1: false, } } diff --git a/src/store/slices/modal.ts b/src/store/slices/modal.ts index fe373b2a..76277afe 100644 --- a/src/store/slices/modal.ts +++ b/src/store/slices/modal.ts @@ -19,5 +19,7 @@ export default function createModalSlice(set: SetState, get: GetStat vaultModal: null, walletAssetsModal: null, withdrawFromVaultsModal: null, + v1DepositAndWithdrawModal: null, + v1BorrowAndRepayModal: null, } } diff --git a/src/types/interfaces/market.d.ts b/src/types/interfaces/market.d.ts index f96d2b47..9858f4b0 100644 --- a/src/types/interfaces/market.d.ts +++ b/src/types/interfaces/market.d.ts @@ -17,7 +17,8 @@ interface Market { } interface BorrowMarketTableData extends Market { - accountDebt?: BigNumber + accountDebtAmount?: BigNumber + accountDebtValue?: BigNumber } interface LendingMarketTableData extends Market { diff --git a/src/types/interfaces/route.d.ts b/src/types/interfaces/route.d.ts index 59fff097..cd4f5fca 100644 --- a/src/types/interfaces/route.d.ts +++ b/src/types/interfaces/route.d.ts @@ -11,6 +11,7 @@ type Page = | 'hls-staking' | 'governance' | 'execute' + | 'v1' type OsmosisRouteResponse = { amount_in: { diff --git a/src/types/interfaces/store/broadcast.d.ts b/src/types/interfaces/store/broadcast.d.ts index 719c1c69..1ec3edd1 100644 --- a/src/types/interfaces/store/broadcast.d.ts +++ b/src/types/interfaces/store/broadcast.d.ts @@ -167,4 +167,7 @@ interface BroadcastSlice { borrow: BNCoin[] reclaims: ActionCoin[] }) => Promise + v1Action: (type: V1ActionType, funds: BNCoin) => Promise } + +type V1ActionType = 'withdraw' | 'deposit' | 'borrow' | 'repay' diff --git a/src/types/interfaces/store/common.d.ts b/src/types/interfaces/store/common.d.ts index 93d61291..9c9122cd 100644 --- a/src/types/interfaces/store/common.d.ts +++ b/src/types/interfaces/store/common.d.ts @@ -18,6 +18,7 @@ interface CommonSlice { useAutoRepay: boolean isOracleStale: boolean isHLS: boolean + isV1: boolean } interface FocusComponent { diff --git a/src/types/interfaces/store/modals.d.ts b/src/types/interfaces/store/modals.d.ts index ceef9fa6..e0fc378f 100644 --- a/src/types/interfaces/store/modals.d.ts +++ b/src/types/interfaces/store/modals.d.ts @@ -15,6 +15,8 @@ interface ModalSlice { vaultModal: VaultModal | null walletAssetsModal: WalletAssetModal | null withdrawFromVaultsModal: DepositedVault[] | null + v1DepositAndWithdrawModal: V1DepositAndWithdrawModal | null + v1BorrowAndRepayModal: V1BorrowAndRepayModal | null } interface AlertDialogButton { @@ -84,3 +86,13 @@ interface HlsManageModal { } type HlsStakingManageAction = 'deposit' | 'withdraw' | 'repay' | 'leverage' + +interface V1DepositAndWithdrawModal { + type: 'deposit' | 'withdraw' + data: LendingMarketTableData +} + +interface V1BorrowAndRepayModal { + type: 'borrow' | 'repay' + data: BorrowMarketTableData +} diff --git a/src/types/interfaces/v1.d.ts b/src/types/interfaces/v1.d.ts new file mode 100644 index 00000000..e40c3902 --- /dev/null +++ b/src/types/interfaces/v1.d.ts @@ -0,0 +1,4 @@ +interface V1Positions { + deposits: BNCoin[] + debts: BNCoin[] +} diff --git a/src/utils/getCurrentChainId.ts b/src/utils/getCurrentChainId.ts index 2350c12a..701e5400 100644 --- a/src/utils/getCurrentChainId.ts +++ b/src/utils/getCurrentChainId.ts @@ -23,6 +23,10 @@ export const getCurrentChainId = () => { if (currentNetwork === NETWORK.TESTNET) chainId = ChainInfoID.OsmosisDevnet break + case 'neutron': + if (currentNetwork === NETWORK.MAINNET) chainId = ChainInfoID.Neutron1 + break + case 'testnet-neutron': if (currentNetwork === NETWORK.TESTNET) chainId = ChainInfoID.Pion1 break @@ -42,6 +46,10 @@ export const getCurrentChainId = () => { if (currentNetwork === NETWORK.TESTNET) chainId = ChainInfoID.OsmosisDevnet break + case ChainInfoID.Neutron1: + if (currentNetwork === NETWORK.MAINNET) chainId = ChainInfoID.Neutron1 + break + case ChainInfoID.Pion1: if (currentNetwork === NETWORK.TESTNET) chainId = ChainInfoID.Pion1 break diff --git a/src/utils/route.ts b/src/utils/route.ts index aae1b19e..723cc922 100644 --- a/src/utils/route.ts +++ b/src/utils/route.ts @@ -40,6 +40,7 @@ export function getPage(pathname: string): Page { 'portfolio', 'hls-farm', 'hls-staking', + 'v1', ] const segments = pathname.split('/') diff --git a/src/utils/url.ts b/src/utils/url.ts index a3a331c9..d411c924 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -1,9 +1,10 @@ -export const getUrl = (baseUrl: string, path: string): string => { +export const getUrl = (baseUrl: string, path?: string): string => { + if (!path) path = '' const isPlaceholder = baseUrl.split('APP_').length > 1 if (isPlaceholder) return baseUrl + '/' + path - const url = new URL(baseUrl) + const url = new URL(baseUrl.split('?')[0]) if (process.env.NEXT_PUBLIC_API_KEY) return `${url.href}${path}?x-apikey=${process.env.NEXT_PUBLIC_API_KEY}` diff --git a/tailwind.config.js b/tailwind.config.js index 321717d1..6a9d67ff 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -240,6 +240,7 @@ module.exports = { }, maxWidth: { content: '1024px', + v1: '1248px', modal: '895px', 'modal-md': '556px', 'modal-sm': '526px', @@ -259,7 +260,7 @@ module.exports = { screens: { sm: '480px', md: '720px', - lg: '1024px', + lg: '1280px', xl: '1280px', '2xl': '1920px', }, @@ -504,6 +505,12 @@ module.exports = { textTransform: 'uppercase', letterSpacing: '9px', }, + '.bg-v1': { + backgroundImage: 'url(/images/bg-v1.svg)', + backgroundRepeat: 'no-repeat', + backgroundSize: '100% auto', + backgroundPosition: 'top', + }, }) }), plugin(({ matchUtilities, theme }) => {