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 10 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
@@ -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 @@ -4,7 +4,7 @@ import { TableFilters, useColumnFilters } from '@ui-kit/shared/ui/TableFilters'
import { t } from '@lingui/macro'
import { CompactUsdCell, LineGraphCell, PoolTitleCell, 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 @@ -24,10 +24,10 @@ import {

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 +42,18 @@ const columns = [
cell: PoolTitleCell,
size: ColumnWidth.lg,
}),
columnHelper.accessor('rates.borrowApyPcent', {
columnHelper.accessor('rates.borrow', {
header: t`7D Borrow Rate`,
cell: (c) => <LineGraphCell vault={c.row.original} type="borrow" showChart={isFeatureVisible(c, borrowChartId)} />,
cell: (c) => <LineGraphCell market={c.row.original} type="borrow" showChart={isFeatureVisible(c, borrowChartId)} />,
meta: { type: 'numeric' },
size: ColumnWidth.md,
}),
columnHelper.accessor('rates.lendApyPcent', {
columnHelper.accessor('rates.lend', {
header: t`7D Supply Yield`,
cell: (c) => <LineGraphCell vault={c.row.original} type="lend" showChart={isFeatureVisible(c, lendChartId)} />,
cell: (c) => <LineGraphCell market={c.row.original} type="lend" showChart={isFeatureVisible(c, lendChartId)} />,
meta: { type: 'numeric' },
size: ColumnWidth.md,
sortUndefined: 'last',
}),
columnHelper.accessor('utilizationPercent', {
header: t`Utilization`,
Expand All @@ -70,7 +71,7 @@ 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 }]

Expand Down Expand Up @@ -98,7 +99,7 @@ export const LendingMarketsTable = ({
headerHeight,
}: {
onReload: () => void
data: LendingVault[]
data: LlamaMarket[]
headerHeight: string
}) => {
const [columnFilters, columnFiltersById, setColumnFilter] = useColumnFilters()
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 @@ -3,13 +3,14 @@ import Skeleton from '@mui/material/Skeleton'
import { LendTableTitle } from '@/loan/components/PageLlamaMarkets/LendTableTitle'
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 @@ -19,16 +20,15 @@ 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`} />
<LendTableTitle />
{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,5 +1,5 @@
import { LendingSnapshot, useLendingSnapshots } from '@/loan/entities/lending'
import { LendingVault } from '@/loan/entities/vaults'
import { LendingSnapshot, useLendingSnapshots } from '@/loan/entities/lending-snapshots'
import { LlamaMarket, LlamaMarketType } from '@/loan/entities/llama-markets'
import { Line, LineChart, YAxis } from 'recharts'
import { useTheme } from '@mui/material/styles'
import { DesignSystem } from '@ui-kit/themes/design'
Expand All @@ -10,6 +10,7 @@ import { t } from '@lingui/macro'
import { useMemo } from 'react'
import { meanBy } from 'lodash'
import Box from '@mui/material/Box'
import { useCrvUsdSnapshots } from '@/loan/entities/crvusd'

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

Expand All @@ -18,10 +19,10 @@ 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,30 +34,36 @@ const calculateDomain =
return [first - diff, first + diff]
}

/**
* 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 LineGraphCellProps = {
market: LlamaMarket
type: GraphType
showChart: boolean // chart is hidden depending on the chart settings
}) => {
const { data: snapshots, isLoading } = useLendingSnapshots({
blockchainId: vault.blockchainId,
contractAddress: vault.controllerAddress,
})
const { design } = useTheme()
const currentValue = vault.rates[`${type}ApyPcent`]
const snapshotKey = `${type}_apy` as const
}

function useSnapshots({ address, blockchainId, controllerAddress, type: marketType }: LlamaMarket, type: GraphType) {
const isPool = marketType == LlamaMarketType.Pool
const showMintGraph = !isPool && type === 'borrow'
const contractAddress = isPool ? controllerAddress : address
const params = { blockchainId: blockchainId, contractAddress }
const { data: poolSnapshots, isLoading: poolIsLoading } = useLendingSnapshots(params, isPool)
const { data: mintSnapshots, isLoading: mintIsLoading } = useCrvUsdSnapshots(params, showMintGraph)
if (isPool) {
return { snapshots: poolSnapshots, isLoading: poolIsLoading, snapshotKey: `${type}_apy` as const }
}
return { snapshots: showMintGraph ? mintSnapshots : null, isLoading: mintIsLoading, snapshotKey: 'rate' as const }
}

/**
* Line graph cell that displays the average historical APY for a vault and a given type (borrow or lend).
*/
export const LineGraphCell = ({ market, type, showChart }: LineGraphCellProps) => {
const { snapshots, snapshotKey, isLoading } = useSnapshots(market, type)
const currentValue = market.rates[type]
const rate = useMemo(
() => (snapshots?.length ? meanBy(snapshots, (row) => row[snapshotKey]) : currentValue),
[snapshots, currentValue, snapshotKey],
)
const { design } = useTheme()
if (rate == null) {
return '-'
}
Expand All @@ -72,7 +79,7 @@ export const LineGraphCell = ({
<Line
type="monotone"
dataKey={snapshotKey}
stroke={getColor(design, snapshots, type)}
stroke={getColor(design, snapshots, snapshotKey)}
strokeWidth={1}
dot={<></>}
/>
Expand Down
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
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { LendingVault } from '@/loan/entities/vaults'
import { LlamaMarket } from '@/loan/entities/llama-markets'
import { CellContext } from '@tanstack/react-table'
import { LinearProgress } from '@ui-kit/shared/ui/LinearProgress'
import Stack from '@mui/material/Stack'
import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces'

const { Spacing } = SizesAndSpaces

export const UtilizationCell = ({ getValue }: CellContext<LendingVault, number>) => {
export const UtilizationCell = ({ getValue }: CellContext<LlamaMarket, number>) => {
const value = getValue()
if (value == null) {
return '-'
Expand Down
56 changes: 56 additions & 0 deletions apps/main/src/loan/entities/crvusd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ContractParams, PoolParams, PoolQuery, queryFactory, rootKeys } from '@ui-kit/lib/model/query'
import { contractValidationSuite } from '@ui-kit/lib/model/query/contract-validation'

type CrvUsdSnapshotFromApi = {
rate: number
borrow_apy: number
lend_apy: number
liquidation_discount: number
loan_discount: number
n_loans: number
price_oracle: number
amm_price: number
base_price: number
total_debt: number
total_assets: number
total_debt_usd: number
total_assets_usd: number
minted: number
redeemed: number
minted_usd: number
redeemed_usd: number
min_band: number
max_band: number
collateral_balance: number
borrowed_balance: number
collateral_balance_usd: number
borrowed_balance_usd: number
sum_debt_squared: number
timestamp: string
}

type CrvUsdSnapshotsFromApi = {
chain: string
market_id: number
data: CrvUsdSnapshotFromApi[]
}

export const _getCrvUsdSnapshots = async ({
blockchainId,
contractAddress,
}: ContractParams): Promise<CrvUsdSnapshotFromApi[]> => {
const url = `https://prices.curve.fi/v1/crvusd/markets/${blockchainId}/${contractAddress}/snapshots`
const response = await fetch(url)
const { data } = (await response.json()) as CrvUsdSnapshotsFromApi
if (!data) {
throw new Error('Failed to fetch crvUSD snapshots')
}
return data
}

export const { useQuery: useCrvUsdSnapshots } = queryFactory({
queryKey: (params: ContractParams) => [...rootKeys.contract(params), 'crvUsd', 'snapshots'] as const,
queryFn: _getCrvUsdSnapshots,
staleTime: '1d',
validationSuite: contractValidationSuite,
})
Loading
Loading