Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mint markets in llamalend table #622

Merged
merged 20 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5178a68
feat: show mint markets (wip)
DanielSchiavini Jan 22, 2025
327a20b
Merge branch 'main' of github.com:curvefi/curve-frontend into feat/mi…
DanielSchiavini Jan 23, 2025
e7f5dc5
refactor: use proper tanstack queries
DanielSchiavini Jan 23, 2025
4ff3b7c
Merge branch 'main' of github.com:curvefi/curve-frontend into feat/mi…
DanielSchiavini Jan 27, 2025
80d71d6
Merge branch 'main' of github.com:curvefi/curve-frontend into feat/mi…
DanielSchiavini Jan 28, 2025
daef206
feat: mint market graphs
DanielSchiavini Jan 28, 2025
f2c3f03
fix: self-review
DanielSchiavini Jan 28, 2025
ebecf72
feat: crvUSD price from API
DanielSchiavini Jan 28, 2025
85a5349
feat: sort undefined as last
DanielSchiavini Jan 28, 2025
620392e
Merge branch 'main' of github.com:curvefi/curve-frontend into feat/mi…
DanielSchiavini Jan 28, 2025
03fd978
feat: separate graph columns
DanielSchiavini Jan 28, 2025
6497fde
fix: update test-id
DanielSchiavini Jan 28, 2025
242e0d8
Merge branch 'main' of github.com:curvefi/curve-frontend into feat/mi…
DanielSchiavini Jan 29, 2025
74d4129
fix: align graph cell left
DanielSchiavini Jan 29, 2025
6b51949
Merge branch 'feat/mint-markets' of github.com:curvefi/curve-frontend…
DanielSchiavini Jan 29, 2025
ee0bb34
fix: cypress
DanielSchiavini Jan 29, 2025
6024e75
Merge branch 'main' into feat/mint-markets
DanielSchiavini Jan 29, 2025
2e15900
fix: disable chart sorting
DanielSchiavini Jan 29, 2025
427d672
Merge pull request #648 from curvefi/feat/table-headers
DanielSchiavini Jan 30, 2025
2526fe2
Merge branch 'main' into feat/mint-markets
DanielSchiavini Jan 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const TableSettings = ({
searchText={searchParams.searchText}
handleInputChange={(val) => updatePath({ searchText: val })}
handleClose={() => updatePath({ searchText: '' })}
testId="search-pools"
/>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Grid from '@mui/material/Grid2'
import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces'
import React, { useMemo } from 'react'
import { LendingVault } from '@/loan/entities/vaults'
import { capitalize } from 'lodash'
import { ChainIcon } from '@ui-kit/shared/icons/ChainIcon'
import Typography from '@mui/material/Typography'
Expand All @@ -10,14 +9,15 @@ import { MultiSelectFilter } from '@/loan/components/PageLlamaMarkets/filters/Mu
import { formatNumber, getImageBaseUrl } from '@ui/utils'
import TokenIcon from '../TokenIcon'
import { MinimumSliderFilter } from '@/loan/components/PageLlamaMarkets/filters/MinimumSliderFilter'
import { LlamaMarket } from '@/loan/entities/llama-markets'

const { Spacing } = SizesAndSpaces

/**
* Displays a token with its icon and symbol.
* This is used in the lending markets filters to display collateral and debt tokens.
*/
const Token = ({ symbol, data, field }: { symbol: string; data: LendingVault[]; field: 'collateral' | 'borrowed' }) => {
const Token = ({ symbol, data, field }: { symbol: string; data: LlamaMarket[]; field: 'collateral' | 'borrowed' }) => {
const { blockchainId, address } = useMemo(
() => data.find((d) => d.assets[field].symbol === symbol)!.assets[field],
[data, field, symbol],
Expand All @@ -40,7 +40,7 @@ export const LendingMarketsFilters = ({
}: {
columnFilters: Record<string, unknown>
setColumnFilter: (id: string, value: unknown) => void
data: LendingVault[]
data: LlamaMarket[]
}) => (
<Grid container spacing={Spacing.sm} paddingTop={Spacing.sm}>
<Grid size={{ mobile: 12, tablet: 4 }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import Stack from '@mui/material/Stack'
import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces'
import { TableFilters, useColumnFilters } from '@ui-kit/shared/ui/TableFilters'
import { t } from '@lingui/macro'
import { CompactUsdCell, LineGraphCell, PoolTitleCell, UtilizationCell } from './cells'
import { CompactUsdCell, LineGraphCell, PoolTitleCell, RateCell, UtilizationCell } from './cells'
import { DataTable } from '@ui-kit/shared/ui/DataTable'
import { LendingVault } from '@/loan/entities/vaults'
import { LlamaMarket } from '@/loan/entities/llama-markets'
import {
ColumnDef,
createColumnHelper,
Expand All @@ -16,18 +16,14 @@ import {
import { LendingMarketsFilters } from '@/loan/components/PageLlamaMarkets/LendingMarketsFilters'
import { useSortFromQueryString } from '@ui-kit/hooks/useSortFromQueryString'
import { DeepKeys } from '@tanstack/table-core/build/lib/utils'
import {
isFeatureVisible,
useVisibilitySettings,
VisibilityGroup,
} from '@ui-kit/shared/ui/TableVisibilitySettingsPopover'
import { useVisibilitySettings, VisibilityGroup } from '@ui-kit/shared/ui/TableVisibilitySettingsPopover'

const { ColumnWidth, Spacing, MaxWidth } = SizesAndSpaces

const columnHelper = createColumnHelper<LendingVault>()
const columnHelper = createColumnHelper<LlamaMarket>()

/** Define a hidden column. */
const hidden = (id: DeepKeys<LendingVault>) =>
const hidden = (id: DeepKeys<LlamaMarket>) =>
columnHelper.accessor(id, {
filterFn: (row, columnId, filterValue) => !filterValue?.length || filterValue.includes(row.getValue(columnId)),
meta: { hidden: true },
Expand All @@ -42,17 +38,33 @@ const columns = [
cell: PoolTitleCell,
size: ColumnWidth.lg,
}),
columnHelper.accessor('rates.borrowApyPcent', {
header: t`7D Borrow Rate`,
cell: (c) => <LineGraphCell vault={c.row.original} type="borrow" showChart={isFeatureVisible(c, borrowChartId)} />,
columnHelper.accessor('rates.borrow', {
header: t`7D Avg Borrow Rate`,
cell: (c) => <RateCell market={c.row.original} type="borrow" />,
meta: { type: 'numeric' },
size: ColumnWidth.sm,
}),
columnHelper.accessor('rates.borrow', {
id: borrowChartId,
header: t`7D Borrow Rate Chart`,
cell: (c) => <LineGraphCell market={c.row.original} type="borrow" />,
size: ColumnWidth.md,
enableSorting: false,
}),
columnHelper.accessor('rates.lendApyPcent', {
header: t`7D Supply Yield`,
cell: (c) => <LineGraphCell vault={c.row.original} type="lend" showChart={isFeatureVisible(c, lendChartId)} />,
columnHelper.accessor('rates.lend', {
header: t`7D Avg Supply Yield`,
cell: (c) => <RateCell market={c.row.original} type="lend" />,
meta: { type: 'numeric' },
size: ColumnWidth.sm,
sortUndefined: 'last',
}),
columnHelper.accessor('rates.lend', {
id: lendChartId,
header: t`7D Supply Yield Chart`,
cell: (c) => <LineGraphCell market={c.row.original} type="lend" />,
size: ColumnWidth.md,
sortUndefined: 'last',
enableSorting: false,
}),
columnHelper.accessor('utilizationPercent', {
header: t`Utilization`,
Expand All @@ -70,25 +82,25 @@ const columns = [
hidden('blockchainId'),
hidden('assets.collateral.symbol'),
hidden('assets.borrowed.symbol'),
] satisfies ColumnDef<LendingVault, any>[]
] satisfies ColumnDef<LlamaMarket, any>[]

const DEFAULT_SORT = [{ id: 'totalSupplied.usdTotal', desc: true }]

const DEFAULT_VISIBILITY: VisibilityGroup[] = [
{
label: t`Markets`,
options: [
{ label: t`Available Liquidity`, id: 'totalSupplied.usdTotal', active: true, type: 'column' },
{ label: t`Utilization`, id: 'utilizationPercent', active: true, type: 'column' },
{ label: t`Available Liquidity`, id: 'totalSupplied.usdTotal', active: true },
{ label: t`Utilization`, id: 'utilizationPercent', active: true },
],
},
{
label: t`Borrow`,
options: [{ label: t`Chart`, id: borrowChartId, active: true, type: 'feature' }],
options: [{ label: t`Chart`, id: borrowChartId, active: true }],
},
{
label: t`Lend`,
options: [{ label: t`Chart`, id: lendChartId, active: true, type: 'feature' }],
options: [{ label: t`Chart`, id: lendChartId, active: true }],
},
]

Expand All @@ -98,12 +110,11 @@ export const LendingMarketsTable = ({
headerHeight,
}: {
onReload: () => void
data: LendingVault[]
data: LlamaMarket[]
headerHeight: string
}) => {
const [columnFilters, columnFiltersById, setColumnFilter] = useColumnFilters()
const { columnSettings, columnVisibility, featureVisibility, toggleVisibility } =
useVisibilitySettings(DEFAULT_VISIBILITY)
const { columnSettings, columnVisibility, toggleVisibility } = useVisibilitySettings(DEFAULT_VISIBILITY)

const [sorting, onSortingChange] = useSortFromQueryString(DEFAULT_SORT)
const table = useReactTable({
Expand All @@ -112,7 +123,7 @@ export const LendingMarketsTable = ({
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: { sorting, columnVisibility, featureVisibility, columnFilters },
state: { sorting, columnVisibility, columnFilters },
onSortingChange,
maxMultiSortColCount: 3, // allow 3 columns to be sorted at once
})
Expand Down
10 changes: 5 additions & 5 deletions apps/main/src/loan/components/PageLlamaMarkets/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import Box from '@mui/material/Box'
import Skeleton from '@mui/material/Skeleton'
import { LendingMarketsTable } from '@/loan/components/PageLlamaMarkets/LendingMarketsTable'
import { LendTableFooter } from '@/loan/components/PageLlamaMarkets/LendTableFooter'
import { invalidateLendingVaults, useLendingVaults } from '@/loan/entities/vaults'
import { invalidateLendingVaults } from '@/loan/entities/lending-vaults'
import DocumentHead from '@/loan/layout/DocumentHead'
import { t } from '@lingui/macro'
import React from 'react'
import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces'
import { useHeaderHeight } from '@ui-kit/widgets/Header'
import useStore from '@/loan/store/useStore'
import { useLlamaMarkets } from '@/loan/entities/llama-markets'

const onReload = () => invalidateLendingVaults({})

Expand All @@ -18,15 +19,14 @@ const { Spacing, MaxWidth, ModalHeight } = SizesAndSpaces
* Page for displaying the lending markets table.
*/
export const PageLlamaMarkets = () => {
const { data, error, isFetching } = useLendingVaults({}) // todo: show errors and loading state
const { data, isFetching, isError } = useLlamaMarkets() // todo: show errors and loading state
const bannerHeight = useStore((state) => state.layout.height.globalAlert)
const isReady = data && !isFetching
const headerHeight = useHeaderHeight(bannerHeight)
return (
<Box sx={{ marginBlockEnd: Spacing.xxl }}>
<DocumentHead title={t`Llamalend Markets`} />
{isReady ? (
<LendingMarketsTable onReload={onReload} data={data.lendingVaultData} headerHeight={headerHeight} />
{data ? (
<LendingMarketsTable onReload={onReload} data={data} headerHeight={headerHeight} />
) : (
<Skeleton variant="rectangular" width={MaxWidth.table} height={ModalHeight.md.height} />
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { LendingVault } from '@/loan/entities/vaults'
import { formatNumber } from '@ui/utils'
import { CellContext } from '@tanstack/react-table'
import { LlamaMarket } from '@/loan/entities/llama-markets'

export const CompactUsdCell = ({ getValue }: CellContext<LendingVault, number>) => {
export const CompactUsdCell = ({ getValue }: CellContext<LlamaMarket, number>) => {
const value = getValue()
return value ? formatNumber(value, { currency: 'USD', notation: 'compact' }) : '-'
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import { LendingSnapshot, useLendingSnapshots } from '@/loan/entities/lending'
import { LendingVault } from '@/loan/entities/vaults'
import { LlamaMarket } from '@/loan/entities/llama-markets'
import { Line, LineChart, YAxis } from 'recharts'
import { useTheme } from '@mui/material/styles'
import { DesignSystem } from '@ui-kit/themes/design'
import Stack from '@mui/material/Stack'
import Skeleton from '@mui/material/Skeleton'
import Typography from '@mui/material/Typography'
import { t } from '@lingui/macro'
import { useMemo } from 'react'
import { meanBy } from 'lodash'
import Box from '@mui/material/Box'
import { GraphType, useSnapshots } from '../hooks/useSnapshots'

const graphSize = { width: 172, height: 48 }

type GraphType = 'borrow' | 'lend'

/**
* Get the color for the line graph. Will be green if the last value is higher than the first, red if lower, and blue if equal.
*/
function getColor(design: DesignSystem, data: LendingSnapshot[], type: GraphType) {
function getColor<T>(design: DesignSystem, data: T[], snapshotKey: keyof T) {
if (!data.length) return undefined
const first = data[0][`${type}_apy`]
const last = data[data.length - 1][`${type}_apy`]
const first = data[0][snapshotKey]
const last = data[data.length - 1][snapshotKey]
return design.Layer.Feedback[last === first ? 'Info' : last < first ? 'Error' : 'Success']
}

Expand All @@ -33,59 +28,40 @@ const calculateDomain =
return [first - diff, first + diff]
}

type RateCellProps = {
market: LlamaMarket
type: GraphType
}

/**
* Line graph cell that displays the average historical APY for a vault and a given type (borrow or lend).
*/
export const LineGraphCell = ({
vault,
type,
showChart,
}: {
vault: LendingVault
type: GraphType
showChart: boolean // chart is hidden depending on the chart settings
}) => {
const { data: snapshots, isLoading } = useLendingSnapshots({
blockchainId: vault.blockchainId,
contractAddress: vault.controllerAddress,
})
export const LineGraphCell = ({ market, type }: RateCellProps) => {
const { snapshots, snapshotKey, isLoading, rate } = useSnapshots(market, type)
const { design } = useTheme()
const currentValue = vault.rates[`${type}ApyPcent`]
const snapshotKey = `${type}_apy` as const

const rate = useMemo(
() => (snapshots?.length ? meanBy(snapshots, (row) => row[snapshotKey]) : currentValue),
[snapshots, currentValue, snapshotKey],
)
if (rate == null) {
return '-'
}

return (
<Stack direction="row" alignItems="center" justifyContent="end" gap={3} data-testid={`line-graph-cell-${type}`}>
{rate.toPrecision(4)}%
{showChart && (
<Box data-testid={`line-graph-${type}`}>
{snapshots?.length ? (
<LineChart data={snapshots} {...graphSize} compact>
<YAxis hide type="number" domain={calculateDomain(snapshots[0][snapshotKey])} />
<Line
type="monotone"
dataKey={snapshotKey}
stroke={getColor(design, snapshots, type)}
strokeWidth={1}
dot={<></>}
/>
</LineChart>
) : isLoading ? (
<Skeleton {...graphSize} />
) : (
<Typography sx={{ ...graphSize, alignContent: 'center', textAlign: 'left' }} variant="bodyXsBold">
{t`No historical data`}
</Typography>
)}
</Box>
<Box data-testid={`line-graph-${type}`}>
{snapshots?.length ? (
<LineChart data={snapshots} {...graphSize} compact>
<YAxis hide type="number" domain={calculateDomain(snapshots[0][snapshotKey] as number)} />
<Line
type="monotone"
dataKey={snapshotKey}
stroke={getColor(design, snapshots, snapshotKey)}
strokeWidth={1}
dot={<></>}
/>
</LineChart>
) : isLoading ? (
<Skeleton {...graphSize} />
) : (
<Typography sx={{ ...graphSize, alignContent: 'center', textAlign: 'left' }} variant="bodyXsBold">
{t`No historical data`}
</Typography>
)}
</Stack>
</Box>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { ReactNode } from 'react'
import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces'
import Typography from '@mui/material/Typography'
import { ChainIcon } from '@ui-kit/shared/icons/ChainIcon'
import { LlamaMarketType } from '@/loan/entities/llama-markets'

const { Spacing } = SizesAndSpaces

Expand All @@ -29,13 +30,17 @@ const Badge = ({ children, compact }: { children: ReactNode; compact?: boolean }
</Typography>
)

const poolTypeNames: Record<LlamaMarketType, () => string> = {
[LlamaMarketType.Pool]: () => t`Pool`,
[LlamaMarketType.Mint]: () => t`Mint`,
}

/** Displays badges for a pool, such as the chain icon and the pool type. */
export const PoolBadges = ({ blockchainId }: { blockchainId: string }) => (
export const PoolBadges = ({ blockchainId, type }: { blockchainId: string; type: LlamaMarketType }) => (
<Stack direction="row" gap={Spacing.sm}>
<Badge compact>
<ChainIcon blockchainId={blockchainId} size="md" />
</Badge>
{/* TODO: We should display 'Mint' for some pools but what is the logic? */}
<Badge>{t`Pool`}</Badge>
<Badge>{poolTypeNames[type]()}</Badge>
</Stack>
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LendingVault } from '@/loan/entities/vaults'
import { LlamaMarket } from '@/loan/entities/llama-markets'
import Stack from '@mui/material/Stack'
import TokenIcons from '@/loan/components/TokenIcons'
import React, { useMemo } from 'react'
Expand All @@ -12,13 +12,13 @@ import { cleanColumnId } from '@ui-kit/shared/ui/TableVisibilitySettingsPopover'

const { Spacing } = SizesAndSpaces

export const PoolTitleCell = ({ getValue, row, table }: CellContext<LendingVault, LendingVault['assets']>) => {
export const PoolTitleCell = ({ getValue, row, table }: CellContext<LlamaMarket, LlamaMarket['assets']>) => {
const showCollateral = table.getColumn(cleanColumnId('assets.collateral.symbol'))!.getIsVisible()
const coins = useMemo(() => {
const { borrowed, collateral } = getValue()
return showCollateral ? [collateral, borrowed] : [borrowed]
}, [getValue, showCollateral])
const { blockchainId } = row.original
const { blockchainId, type } = row.original
const imageBaseUrl = getImageBaseUrl(blockchainId)
return (
<Stack direction="row" gap={Spacing.sm} alignItems="center">
Expand All @@ -28,7 +28,7 @@ export const PoolTitleCell = ({ getValue, row, table }: CellContext<LendingVault
tokenAddresses={coins.map((c) => c.address)}
/>
<Stack direction="column" gap={Spacing.xs}>
<PoolBadges blockchainId={blockchainId} />
<PoolBadges blockchainId={blockchainId} type={type} />
<Typography variant="tableCellL">{coins.map((coin) => coin.symbol).join(' - ')}</Typography>
<PoolWarnings />
</Stack>
Expand Down
Loading