From 9e5f88ac831d6e4e8079a2e4baf846b040bb8cc8 Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:42:25 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20table=20(Farm)?= =?UTF-8?q?=20(#555)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ refactor table (Farm) * 🧽 clean up PR * 🧽 clean up PR --- .../Earn/Farm/Table/AvailableVaultsTable.tsx | 19 + .../Earn/Farm/Table/Columns/Apy.tsx | 23 + .../Earn/Farm/Table/Columns/Deposit.tsx | 32 + .../Earn/Farm/Table/Columns/DepositCap.tsx | 46 + .../Earn/Farm/Table/Columns/Details.tsx | 22 + .../Earn/Farm/Table/Columns/MaxLTV.tsx | 21 + .../Earn/Farm/Table/Columns/Name.tsx | 71 + .../Earn/Farm/Table/Columns/PositionValue.tsx | 19 + .../Earn/Farm/Table/Columns/TVL.tsx | 18 + .../Table/Columns/useAvailableColumns.tsx | 51 + .../Table/Columns/useDepositedColumns.tsx | 64 + .../Earn/Farm/Table/DepositedVaultsTable.tsx | 19 + src/components/Earn/Farm/VaultTable.tsx | 310 - src/components/Earn/Farm/Vaults.tsx | 47 +- .../HLS/{ => Farm}/AvailableHLSVaults.tsx | 0 src/components/HLS/Farm/HLSFarmIntro.tsx | 31 + .../HLS/Farm/Table/Columns/Name.tsx | 19 + .../Table/Columns/useAvailableColumns.tsx | 17 + src/components/HLS/Farm/Table/index.tsx | 7 + .../AvailableHLSStakingAssets.tsx | 0 .../HLS/Staking/HLSStakingIntro.tsx | 31 + src/components/Table/index.tsx | 130 + src/pages/FarmPage.tsx | 5 +- src/pages/HLSFarmPage.tsx | 4 +- src/pages/HLSStakingPage.tsx | 4 +- src/types/interfaces/asset.d.ts | 4 + yarn.lock | 16005 +++++++--------- 27 files changed, 7668 insertions(+), 9351 deletions(-) create mode 100644 src/components/Earn/Farm/Table/AvailableVaultsTable.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/Apy.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/Deposit.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/DepositCap.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/Details.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/MaxLTV.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/Name.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/PositionValue.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/TVL.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/useAvailableColumns.tsx create mode 100644 src/components/Earn/Farm/Table/Columns/useDepositedColumns.tsx create mode 100644 src/components/Earn/Farm/Table/DepositedVaultsTable.tsx delete mode 100644 src/components/Earn/Farm/VaultTable.tsx rename src/components/HLS/{ => Farm}/AvailableHLSVaults.tsx (100%) create mode 100644 src/components/HLS/Farm/HLSFarmIntro.tsx create mode 100644 src/components/HLS/Farm/Table/Columns/Name.tsx create mode 100644 src/components/HLS/Farm/Table/Columns/useAvailableColumns.tsx create mode 100644 src/components/HLS/Farm/Table/index.tsx rename src/components/HLS/{ => Staking}/AvailableHLSStakingAssets.tsx (100%) create mode 100644 src/components/HLS/Staking/HLSStakingIntro.tsx create mode 100644 src/components/Table/index.tsx diff --git a/src/components/Earn/Farm/Table/AvailableVaultsTable.tsx b/src/components/Earn/Farm/Table/AvailableVaultsTable.tsx new file mode 100644 index 00000000..b55aca41 --- /dev/null +++ b/src/components/Earn/Farm/Table/AvailableVaultsTable.tsx @@ -0,0 +1,19 @@ +import React from 'react' + +import Card from 'components/Card' +import useAvailableColumns from 'components/Earn/Farm/Table/Columns/useAvailableColumns' +import Table from 'components/Table' + +type Props = { + data: Vault[] + isLoading: boolean +} + +export default function AvailableVaultsTable(props: Props) { + const columns = useAvailableColumns({ isLoading: props.isLoading }) + return ( + + + + ) +} diff --git a/src/components/Earn/Farm/Table/Columns/Apy.tsx b/src/components/Earn/Farm/Table/Columns/Apy.tsx new file mode 100644 index 00000000..947b5e69 --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/Apy.tsx @@ -0,0 +1,23 @@ +import React from 'react' + +import { FormattedNumber } from 'components/FormattedNumber' +import Loading from 'components/Loading' + +interface Props { + vault: Vault | DepositedVault +} + +export default function Apy(props: Props) { + const { vault } = props + + if (vault.apy === null) return + + return ( + + ) +} diff --git a/src/components/Earn/Farm/Table/Columns/Deposit.tsx b/src/components/Earn/Farm/Table/Columns/Deposit.tsx new file mode 100644 index 00000000..207b21ca --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/Deposit.tsx @@ -0,0 +1,32 @@ +import React from 'react' + +import ActionButton from 'components/Button/ActionButton' +import Loading from 'components/Loading' +import useStore from 'store' + +interface Props { + vault: Vault | DepositedVault + isLoading: boolean +} + +export const Deposit = (props: Props) => { + const { vault } = props + + function enterVaultHandler() { + useStore.setState({ + vaultModal: { + vault, + selectedBorrowDenoms: [vault.denoms.secondary], + isCreate: true, + }, + }) + } + + if (props.isLoading) return + + return ( +
+ +
+ ) +} diff --git a/src/components/Earn/Farm/Table/Columns/DepositCap.tsx b/src/components/Earn/Farm/Table/Columns/DepositCap.tsx new file mode 100644 index 00000000..c661e020 --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/DepositCap.tsx @@ -0,0 +1,46 @@ +import React from 'react' + +import { FormattedNumber } from 'components/FormattedNumber' +import Loading from 'components/Loading' +import TitleAndSubCell from 'components/TitleAndSubCell' +import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults' +import { getAssetByDenom } from 'utils/assets' + +interface Props { + vault: Vault | DepositedVault + isLoading: boolean +} + +export default function DepositCap(props: Props) { + const { vault } = props + + if (props.isLoading) return + + const percent = vault.cap.used + .dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER)) + .multipliedBy(100) + .integerValue() + + const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6 + + return ( + + } + sub={ + + } + /> + ) +} diff --git a/src/components/Earn/Farm/Table/Columns/Details.tsx b/src/components/Earn/Farm/Table/Columns/Details.tsx new file mode 100644 index 00000000..e642c83f --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/Details.tsx @@ -0,0 +1,22 @@ +import classNames from 'classnames' +import React from 'react' + +import { ChevronDown } from 'components/Icons' +import Loading from 'components/Loading' + +interface Props { + isLoading: boolean + isExpanded: boolean +} + +export default function Details(props: Props) { + if (props.isLoading) return + + return ( +
+
+ +
+
+ ) +} diff --git a/src/components/Earn/Farm/Table/Columns/MaxLTV.tsx b/src/components/Earn/Farm/Table/Columns/MaxLTV.tsx new file mode 100644 index 00000000..bf322aeb --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/MaxLTV.tsx @@ -0,0 +1,21 @@ +import React from 'react' + +import { FormattedNumber } from 'components/FormattedNumber' +import Loading from 'components/Loading' + +interface Props { + vault: Vault | DepositedVault + isLoading: boolean +} +export default function MaxLtv(props: Props) { + const { vault } = props + if (props.isLoading) return + return ( + + ) +} diff --git a/src/components/Earn/Farm/Table/Columns/Name.tsx b/src/components/Earn/Farm/Table/Columns/Name.tsx new file mode 100644 index 00000000..6e6fab92 --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/Name.tsx @@ -0,0 +1,71 @@ +import classNames from 'classnames' +import React from 'react' + +import VaultLogo from 'components/Earn/Farm/VaultLogo' +import Text from 'components/Text' +import TitleAndSubCell from 'components/TitleAndSubCell' +import { VaultStatus } from 'types/enums/vault' +import { produceCountdown } from 'utils/formatters' + +interface Props { + vault: Vault | DepositedVault +} + +export default function Name(props: Props) { + const { vault } = props + const timeframe = vault.lockup.timeframe[0] + const unlockDuration = !!timeframe ? ` - (${vault.lockup.duration}${timeframe})` : '' + + let remainingTime = 0 + let status: VaultStatus = VaultStatus.ACTIVE + if ('status' in vault) { + status = vault.status as VaultStatus + if (vault.status === VaultStatus.UNLOCKING && vault.unlocksAt) { + remainingTime = vault.unlocksAt - Date.now() + } + } + + return ( +
+ + + {status === VaultStatus.UNLOCKING && ( + + + Unlocking + + + {produceCountdown(remainingTime)} + + + )} + {status === VaultStatus.UNLOCKED && ( + + Unlocked + + )} +
+ ) +} diff --git a/src/components/Earn/Farm/Table/Columns/PositionValue.tsx b/src/components/Earn/Farm/Table/Columns/PositionValue.tsx new file mode 100644 index 00000000..916c26b9 --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/PositionValue.tsx @@ -0,0 +1,19 @@ +import React from 'react' + +import DisplayCurrency from 'components/DisplayCurrency' +import { ORACLE_DENOM } from 'constants/oracle' +import { BNCoin } from 'types/classes/BNCoin' + +interface Props { + vault: DepositedVault + isLoading: boolean +} +export default function PositionValue(props: Props) { + const { vault } = props + const positionValue = vault.values.primary + .plus(vault.values.secondary) + .plus(vault.values.unlocking) + .plus(vault.values.unlocked) + const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue) + return +} diff --git a/src/components/Earn/Farm/Table/Columns/TVL.tsx b/src/components/Earn/Farm/Table/Columns/TVL.tsx new file mode 100644 index 00000000..122725e0 --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/TVL.tsx @@ -0,0 +1,18 @@ +import React from 'react' + +import DisplayCurrency from 'components/DisplayCurrency' +import Loading from 'components/Loading' +import { BNCoin } from 'types/classes/BNCoin' + +interface Props { + vault: Vault | DepositedVault + isLoading: boolean +} + +export default function TVL(props: Props) { + const { vault } = props + if (props.isLoading) return + const coin = BNCoin.fromDenomAndBigNumber(vault.cap.denom, vault.cap.used) + + return +} diff --git a/src/components/Earn/Farm/Table/Columns/useAvailableColumns.tsx b/src/components/Earn/Farm/Table/Columns/useAvailableColumns.tsx new file mode 100644 index 00000000..f3d0d53a --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/useAvailableColumns.tsx @@ -0,0 +1,51 @@ +import { ColumnDef } from '@tanstack/react-table' +import React, { useMemo } from 'react' + +import Apy from 'components/Earn/Farm/Table/Columns/Apy' +import { Deposit } from 'components/Earn/Farm/Table/Columns/Deposit' +import DepositCap from 'components/Earn/Farm/Table/Columns/DepositCap' +import MaxLTV from 'components/Earn/Farm/Table/Columns/MaxLTV' +import Name from 'components/Earn/Farm/Table/Columns/Name' +import TVL from 'components/Earn/Farm/Table/Columns/TVL' + +interface Props { + isLoading: boolean +} + +export default function useAvailableColumns(props: Props) { + return useMemo[]>(() => { + return [ + { + header: 'Vault', + accessorKey: 'name', + cell: ({ row }) => , + }, + { + accessorKey: 'apy', + header: 'APY', + cell: ({ row }) => , + }, + { + accessorKey: 'tvl', + header: 'TVL', + cell: ({ row }) => , + }, + { + accessorKey: 'cap', + header: 'Deposit Cap', + cell: ({ row }) => , + }, + { + accessorKey: 'ltv.max', + header: 'Max LTV', + cell: ({ row }) => , + }, + { + accessorKey: 'details', + enableSorting: false, + header: 'Deposit', + cell: ({ row }) => , + }, + ] + }, [props.isLoading]) +} diff --git a/src/components/Earn/Farm/Table/Columns/useDepositedColumns.tsx b/src/components/Earn/Farm/Table/Columns/useDepositedColumns.tsx new file mode 100644 index 00000000..d44c9c94 --- /dev/null +++ b/src/components/Earn/Farm/Table/Columns/useDepositedColumns.tsx @@ -0,0 +1,64 @@ +import { ColumnDef, Row } from '@tanstack/react-table' +import React, { useMemo } from 'react' + +import Apy from 'components/Earn/Farm/Table/Columns/Apy' +import DepositCap from 'components/Earn/Farm/Table/Columns/DepositCap' +import Details from 'components/Earn/Farm/Table/Columns/Details' +import MaxLTV from 'components/Earn/Farm/Table/Columns/MaxLTV' +import Name from 'components/Earn/Farm/Table/Columns/Name' +import PositionValue from 'components/Earn/Farm/Table/Columns/PositionValue' +import TVL from 'components/Earn/Farm/Table/Columns/TVL' + +interface Props { + isLoading: boolean +} + +export default function useDepositedColumns(props: Props) { + return useMemo[]>(() => { + return [ + { + header: 'Vault', + accessorKey: 'name', + cell: ({ row }) => , + }, + { + header: 'Pos. Value', + cell: ({ row }: { row: Row }) => ( + + ), + }, + { + accessorKey: 'apy', + header: 'APY', + cell: ({ row }) => , + }, + { + accessorKey: 'tvl', + header: 'TVL', + cell: ({ row }) => ( + + ), + }, + { + accessorKey: 'cap', + header: 'Deposit Cap', + cell: ({ row }) => ( + + ), + }, + { + accessorKey: 'ltv.max', + header: 'Max LTV', + cell: ({ row }) => ( + + ), + }, + { + accessorKey: 'details', + enableSorting: false, + header: 'Details', + cell: ({ row }) =>
, + }, + ] + }, [props.isLoading]) +} diff --git a/src/components/Earn/Farm/Table/DepositedVaultsTable.tsx b/src/components/Earn/Farm/Table/DepositedVaultsTable.tsx new file mode 100644 index 00000000..13486f3f --- /dev/null +++ b/src/components/Earn/Farm/Table/DepositedVaultsTable.tsx @@ -0,0 +1,19 @@ +import React from 'react' + +import Card from 'components/Card' +import useDepositedColumns from 'components/Earn/Farm/Table/Columns/useDepositedColumns' +import Table from 'components/Table' + +type Props = { + data: DepositedVault[] + isLoading: boolean +} + +export default function DepositedVaultsTable(props: Props) { + const columns = useDepositedColumns({ isLoading: props.isLoading }) + return ( + +
+ + ) +} diff --git a/src/components/Earn/Farm/VaultTable.tsx b/src/components/Earn/Farm/VaultTable.tsx deleted file mode 100644 index 767f6f14..00000000 --- a/src/components/Earn/Farm/VaultTable.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import { - ColumnDef, - flexRender, - getCoreRowModel, - getSortedRowModel, - Row, - SortingState, - useReactTable, -} from '@tanstack/react-table' -import classNames from 'classnames' -import React from 'react' - -import ActionButton from 'components/Button/ActionButton' -import DisplayCurrency from 'components/DisplayCurrency' -import VaultExpanded from 'components/Earn/Farm/VaultExpanded' -import VaultLogo from 'components/Earn/Farm/VaultLogo' -import { VaultRow } from 'components/Earn/Farm/VaultRow' -import { FormattedNumber } from 'components/FormattedNumber' -import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons' -import Loading from 'components/Loading' -import Text from 'components/Text' -import TitleAndSubCell from 'components/TitleAndSubCell' -import { BN_ONE } from 'constants/math' -import { ORACLE_DENOM } from 'constants/oracle' -import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults' -import useStore from 'store' -import { BNCoin } from 'types/classes/BNCoin' -import { VaultStatus } from 'types/enums/vault' -import { getAssetByDenom } from 'utils/assets' -import { produceCountdown } from 'utils/formatters' - -type Props = { - data: Vault[] | DepositedVault[] - isLoading?: boolean -} - -export const VaultTable = (props: Props) => { - const [sorting, setSorting] = React.useState([{ id: 'name', desc: true }]) - - const columns = React.useMemo[]>(() => { - return [ - { - header: 'Vault', - accessorKey: 'name', - cell: ({ row }) => { - const vault = row.original as DepositedVault - const timeframe = vault.lockup.timeframe[0] - const unlockDuration = !!timeframe ? ` - (${vault.lockup.duration}${timeframe})` : '' - - const status = vault.status - let remainingTime = 0 - - if (status === VaultStatus.UNLOCKING && vault.unlocksAt) { - remainingTime = vault.unlocksAt - Date.now() - } - - return ( -
- - - {status === VaultStatus.UNLOCKING && ( - - - Unlocking - - - {produceCountdown(remainingTime)} - - - )} - {status === VaultStatus.UNLOCKED && ( - - Unlocked - - )} -
- ) - }, - }, - - ...((props.data[0] as DepositedVault)?.values - ? [ - { - header: 'Pos. Value', - cell: ({ row }: { row: Row }) => { - const vault = row.original as DepositedVault - const positionValue = vault.values.primary - .plus(vault.values.secondary) - .plus(vault.values.unlocking) - .plus(vault.values.unlocked) - const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue) - return - }, - }, - ] - : []), - { - accessorKey: 'apy', - header: 'APY', - cell: ({ row }) => { - const vault = row.original as DepositedVault - if (vault.apy === null) return - return ( - - ) - }, - }, - { - accessorKey: 'tvl', - header: 'TVL', - cell: ({ row }) => { - const vault = row.original as DepositedVault - if (props.isLoading) return - const coin = new BNCoin({ - denom: vault.cap.denom, - amount: vault.cap.used.toString(), - }) - - return - }, - }, - { - accessorKey: 'cap', - header: 'Depo. Cap', - cell: ({ row }) => { - const vault = row.original as DepositedVault - if (props.isLoading) return - const percent = vault.cap.used - .dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER)) - .multipliedBy(100) - - const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6 - - return ( - - } - sub={ - - } - /> - ) - }, - }, - { - accessorKey: 'leverage.max', - header: 'Max. Leverage', - cell: ({ row }) => { - if (props.isLoading) return - const maxLeverage = BN_ONE.dividedBy(1 - row.original.ltv.max).toNumber() - return ( - - ) - }, - }, - { - accessorKey: 'details', - enableSorting: false, - header: (data) => { - const tableData = data.table.options.data as DepositedVault[] - if (tableData.length && tableData[0].status) return 'Details' - return 'Deposit' - }, - cell: ({ row }) => { - const vault = row.original as DepositedVault - - function enterVaultHandler() { - useStore.setState({ - vaultModal: { - vault, - selectedBorrowDenoms: [vault.denoms.secondary], - isCreate: true, - }, - }) - } - - if (props.isLoading) return - return ( -
- {vault.status ? ( -
- -
- ) : ( - - )} -
- ) - }, - }, - ] - }, [props.data, props.isLoading]) - - const table = useReactTable({ - data: props.data, - columns: columns, - state: { - sorting, - }, - onSortingChange: setSorting, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - }) - - return ( -
- - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - ) - })} - - ))} - - - {table.getRowModel().rows.map((row) => { - if (row.getIsExpanded()) { - return ( - - - - - ) - } - return ( - - ) - })} - -
-
- - {header.column.getCanSort() - ? { - asc: , - desc: , - false: , - }[header.column.getIsSorted() as string] ?? null - : null} - - - {flexRender(header.column.columnDef.header, header.getContext())} - -
-
- ) -} diff --git a/src/components/Earn/Farm/Vaults.tsx b/src/components/Earn/Farm/Vaults.tsx index 03d1c77d..ab6b8507 100644 --- a/src/components/Earn/Farm/Vaults.tsx +++ b/src/components/Earn/Farm/Vaults.tsx @@ -1,7 +1,7 @@ import { Suspense, useMemo } from 'react' -import Card from 'components/Card' -import { VaultTable } from 'components/Earn/Farm/VaultTable' +import AvailableVaultsTable from 'components/Earn/Farm/Table/AvailableVaultsTable' +import DepositedVaultsTable from 'components/Earn/Farm/Table/DepositedVaultsTable' import VaultUnlockBanner from 'components/Earn/Farm/VaultUnlockBanner' import { ENV } from 'constants/env' import { BN_ZERO } from 'constants/math' @@ -12,15 +12,10 @@ import useVaults from 'hooks/useVaults' import { NETWORK } from 'types/enums/network' import { VaultStatus } from 'types/enums/vault' -interface Props { - type: 'available' | 'deposited' -} - -function Content(props: Props) { +function Content() { const accountId = useAccountId() const { data: vaults } = useVaults() const { data: depositedVaults } = useDepositedVaults(accountId || '') - const isAvailable = props.type === 'available' const vaultsMetaData = ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA @@ -44,13 +39,9 @@ function Content(props: Props) { ) }, [vaults, depositedVaults, vaultsMetaData]) - const vaultsToDisplay = isAvailable ? available : deposited - - if (!vaultsToDisplay.length) return null - const unlockedVaults: DepositedVault[] = [] - if (!isAvailable && depositedVaults?.length > 0) { + if (depositedVaults?.length > 0) { depositedVaults.forEach((vault) => { if (vault.status === VaultStatus.UNLOCKED) { unlockedVaults.push(vault) @@ -60,13 +51,11 @@ function Content(props: Props) { return ( <> - {!isAvailable && } - - - + + {deposited.length && ( + + )} + {available.length && } ) } @@ -88,25 +77,13 @@ function Fallback() { }, })) - return ( - - - - ) + return } -export function AvailableVaults() { +export default function Vaults() { return ( }> - - - ) -} - -export function DepositedVaults() { - return ( - - + ) } diff --git a/src/components/HLS/AvailableHLSVaults.tsx b/src/components/HLS/Farm/AvailableHLSVaults.tsx similarity index 100% rename from src/components/HLS/AvailableHLSVaults.tsx rename to src/components/HLS/Farm/AvailableHLSVaults.tsx diff --git a/src/components/HLS/Farm/HLSFarmIntro.tsx b/src/components/HLS/Farm/HLSFarmIntro.tsx new file mode 100644 index 00000000..88bb9e17 --- /dev/null +++ b/src/components/HLS/Farm/HLSFarmIntro.tsx @@ -0,0 +1,31 @@ +import React from 'react' + +import Button from 'components/Button' +import { PlusSquared } from 'components/Icons' +import Intro from 'components/Intro' +import { DocURL } from 'types/enums/docURL' + +export default function HlsFarmIntro() { + return ( + + Leverage farming is a strategy where users borrow + funds to increase their yield farming position, aiming to earn more in rewards than the + cost of the borrowed assets. + + } + > +