Farm components (#158)
* add basic endpoint for vaultConfigs * implement apy for vaults * add tab + update routing * fixed routing issues * add featured vaults and vault card * add availablevaults table * fixed comments
This commit is contained in:
parent
4af7e63c5f
commit
ac09862f1f
84
package-lock.json
generated
84
package-lock.json
generated
@ -19229,6 +19229,66 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-android-arm-eabi": {
|
||||||
|
"version": "13.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz",
|
||||||
|
"integrity": "sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-android-arm64": {
|
||||||
|
"version": "13.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz",
|
||||||
|
"integrity": "sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-freebsd-x64": {
|
||||||
|
"version": "13.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz",
|
||||||
|
"integrity": "sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@next/swc-linux-arm-gnueabihf": {
|
||||||
|
"version": "13.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz",
|
||||||
|
"integrity": "sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -33300,6 +33360,30 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"use-sync-external-store": "1.2.0"
|
"use-sync-external-store": "1.2.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"@next/swc-android-arm-eabi": {
|
||||||
|
"version": "13.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz",
|
||||||
|
"integrity": "sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@next/swc-android-arm64": {
|
||||||
|
"version": "13.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz",
|
||||||
|
"integrity": "sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@next/swc-freebsd-x64": {
|
||||||
|
"version": "13.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz",
|
||||||
|
"integrity": "sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@next/swc-linux-arm-gnueabihf": {
|
||||||
|
"version": "13.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz",
|
||||||
|
"integrity": "sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==",
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
src/components/Earn/AvailableVaults.tsx
Normal file
38
src/components/Earn/AvailableVaults.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Suspense } from 'react'
|
||||||
|
|
||||||
|
import Card from 'components/Card'
|
||||||
|
import { getVaults } from 'utils/api'
|
||||||
|
import { Text } from 'components/Text'
|
||||||
|
import { VAULTS } from 'constants/vaults'
|
||||||
|
|
||||||
|
import { VaultTable } from './VaultTable'
|
||||||
|
|
||||||
|
async function Content() {
|
||||||
|
const vaults = await getVaults()
|
||||||
|
|
||||||
|
if (!vaults.length) return null
|
||||||
|
|
||||||
|
return <VaultTable data={vaults} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AvailableVaults() {
|
||||||
|
return (
|
||||||
|
<Card title='Available vaults' className='mb-4 h-fit w-full bg-white/5'>
|
||||||
|
<Suspense fallback={<Fallback />}>
|
||||||
|
{/* @ts-expect-error Server Component */}
|
||||||
|
<Content />
|
||||||
|
</Suspense>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Fallback() {
|
||||||
|
// TODO: Replace with loading state of vaulttable
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{VAULTS.map((vault) => (
|
||||||
|
<Text key={vault.address}>{vault.name}</Text>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
36
src/components/Earn/FeaturedVaults.tsx
Normal file
36
src/components/Earn/FeaturedVaults.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Suspense } from 'react'
|
||||||
|
|
||||||
|
import Card from 'components/Card'
|
||||||
|
import { getVaults } from 'utils/api'
|
||||||
|
import { Text } from 'components/Text'
|
||||||
|
|
||||||
|
import VaultCard from './VaultCard'
|
||||||
|
|
||||||
|
async function Content() {
|
||||||
|
const vaults = await getVaults()
|
||||||
|
|
||||||
|
const featuredVaults = vaults.filter((vault) => vault.isFeatured)
|
||||||
|
|
||||||
|
if (!featuredVaults.length) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title='Featured vaults'
|
||||||
|
className='mb-4 h-fit w-full bg-white/5'
|
||||||
|
contentClassName='grid grid-cols-3'
|
||||||
|
>
|
||||||
|
{featuredVaults.map((vault) => (
|
||||||
|
<VaultCard key={vault.address} vault={vault} />
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FeaturedVaults() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
{/* @ts-expect-error Server Component */}
|
||||||
|
<Content />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
@ -1,40 +0,0 @@
|
|||||||
import { Suspense } from 'react'
|
|
||||||
|
|
||||||
import Card from 'components/Card'
|
|
||||||
import Loading from 'components/Loading'
|
|
||||||
import { Text } from 'components/Text'
|
|
||||||
import { getVaults } from 'utils/api'
|
|
||||||
|
|
||||||
async function Content(props: PageProps) {
|
|
||||||
const vaults = await getVaults()
|
|
||||||
|
|
||||||
const address = props.params.address
|
|
||||||
|
|
||||||
if (!address)
|
|
||||||
return (
|
|
||||||
<Text size='sm' className='w-full text-center'>
|
|
||||||
You need to be connected to use the earn page
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
|
|
||||||
return <Text size='sm'>{`Earn page for ${address}`}</Text>
|
|
||||||
}
|
|
||||||
|
|
||||||
function Fallback() {
|
|
||||||
return <Loading className='h-4 w-50' />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Overview(props: PageProps) {
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
className='h-fit w-full justify-center bg-white/5'
|
|
||||||
title='Earn'
|
|
||||||
contentClassName='px-4 py-6'
|
|
||||||
>
|
|
||||||
<Suspense fallback={<Fallback />}>
|
|
||||||
{/* @ts-expect-error Server Component */}
|
|
||||||
<Content params={props.params} />
|
|
||||||
</Suspense>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
68
src/components/Earn/VaultCard.tsx
Normal file
68
src/components/Earn/VaultCard.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
import { Text } from 'components/Text'
|
||||||
|
import { getAssetByDenom } from 'utils/assets'
|
||||||
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
|
import { formatPercent, formatValue } from 'utils/formatters'
|
||||||
|
import { Button } from 'components/Button'
|
||||||
|
import VaultLogo from 'components/Earn/VaultLogo'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
vault: Vault
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VaultCard(props: Props) {
|
||||||
|
function openVaultModal() {}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='border-r-[1px] border-r-white/10 p-4'>
|
||||||
|
<div className='align-center mb-8 flex justify-between'>
|
||||||
|
<div>
|
||||||
|
<Text size='xs' className='mb-2 text-white/60'>
|
||||||
|
Hot off the presses
|
||||||
|
</Text>
|
||||||
|
<span className='flex'>
|
||||||
|
<Text className='mr-2 font-bold'>{props.vault.name}</Text>
|
||||||
|
<Text size='sm' className='text-white/60'>
|
||||||
|
via {props.vault.provider}
|
||||||
|
</Text>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<VaultLogo vault={props.vault} />
|
||||||
|
</div>
|
||||||
|
<div className='mb-6 flex justify-between'>
|
||||||
|
<TitleAndSubCell
|
||||||
|
className='text-xs'
|
||||||
|
title={props.vault.apy ? formatPercent(props.vault.apy) : '-'}
|
||||||
|
sub={'APY'}
|
||||||
|
/>
|
||||||
|
<TitleAndSubCell
|
||||||
|
className='text-xs'
|
||||||
|
title={`${props.vault.lockup.duration} ${props.vault.lockup.timeframe}`}
|
||||||
|
sub={'Lockup'}
|
||||||
|
/>
|
||||||
|
<TitleAndSubCell
|
||||||
|
className='text-xs'
|
||||||
|
title={formatValue(props.vault.cap.used || '0', {
|
||||||
|
abbreviated: true,
|
||||||
|
decimals: getAssetByDenom(props.vault.cap.denom)?.decimals,
|
||||||
|
})}
|
||||||
|
sub={'TVL'}
|
||||||
|
/>
|
||||||
|
<TitleAndSubCell
|
||||||
|
className='text-xs'
|
||||||
|
title={formatValue(props.vault.cap.max || '0', {
|
||||||
|
abbreviated: true,
|
||||||
|
decimals: getAssetByDenom(props.vault.cap.denom)?.decimals,
|
||||||
|
})}
|
||||||
|
sub={'Depo. Cap'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button color='secondary' onClick={openVaultModal} className='w-full'>
|
||||||
|
Deposit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
25
src/components/Earn/VaultLogo.tsx
Normal file
25
src/components/Earn/VaultLogo.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
import { getAssetByDenom } from 'utils/assets'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
vault: Vault
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VaultLogo(props: Props) {
|
||||||
|
const primaryAsset = getAssetByDenom(props.vault.denoms.primary)
|
||||||
|
const secondaryAsset = getAssetByDenom(props.vault.denoms.secondary)
|
||||||
|
|
||||||
|
if (!primaryAsset || !secondaryAsset) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative grid w-12 place-items-center'>
|
||||||
|
<div className='absolute'>
|
||||||
|
<Image src={primaryAsset.logo} alt={`${primaryAsset.symbol} logo`} width={24} height={24} />
|
||||||
|
</div>
|
||||||
|
<div className='absolute'>
|
||||||
|
<Image className='ml-5 mt-5' src={secondaryAsset.logo} alt='token' width={16} height={16} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
33
src/components/Earn/VaultRow.tsx
Normal file
33
src/components/Earn/VaultRow.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { flexRender, Row } from '@tanstack/react-table'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
type AssetRowProps = {
|
||||||
|
row: Row<Vault>
|
||||||
|
resetExpanded: (defaultState?: boolean | undefined) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VaultRow = (props: AssetRowProps) => {
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={props.row.id}
|
||||||
|
className={classNames(
|
||||||
|
'cursor-pointer transition-colors',
|
||||||
|
props.row.getIsExpanded() ? ' bg-black/20' : 'bg-white/0 hover:bg-white/5',
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
const isExpanded = props.row.getIsExpanded()
|
||||||
|
props.resetExpanded()
|
||||||
|
!isExpanded && props.row.toggleExpanded()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.row.getVisibleCells().map((cell, index) => {
|
||||||
|
return (
|
||||||
|
<td key={cell.id} className={'p-4 text-right'}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
170
src/components/Earn/VaultTable.tsx
Normal file
170
src/components/Earn/VaultTable.tsx
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
SortingState,
|
||||||
|
useReactTable,
|
||||||
|
} from '@tanstack/react-table'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||||
|
import { Text } from 'components/Text'
|
||||||
|
import { getAssetByDenom, getMarketAssets } from 'utils/assets'
|
||||||
|
import VaultLogo from 'components/Earn/VaultLogo'
|
||||||
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
|
import { convertPercentage, formatPercent, formatValue } from 'utils/formatters'
|
||||||
|
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
|
||||||
|
|
||||||
|
import { VaultRow } from 'components/Earn/VaultRow'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: Vault[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VaultTable = (props: Props) => {
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
|
const marketAssets = getMarketAssets()
|
||||||
|
|
||||||
|
const columns = React.useMemo<ColumnDef<Vault>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
header: 'Vault',
|
||||||
|
id: 'address',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return <VaultLogo vault={row.original} />
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'apy',
|
||||||
|
header: 'APY',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return <Text size='xs'>{row.original.apy ? formatPercent(row.original.apy) : '-'}</Text>
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'tvl',
|
||||||
|
header: 'TVL',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
// TODO: Replace with DisplayCurrency
|
||||||
|
const symbol = getAssetByDenom(row.original.cap.denom)?.symbol ?? ''
|
||||||
|
return (
|
||||||
|
<Text size='xs'>
|
||||||
|
{formatValue(row.original.cap.used, { abbreviated: true, suffix: ` ${symbol}` })}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'cap',
|
||||||
|
header: 'Depo. Cap',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const percent = convertPercentage(
|
||||||
|
(row.original.cap.used / (row.original.cap.max * VAULT_DEPOSIT_BUFFER)) * 100,
|
||||||
|
)
|
||||||
|
const decimals = getAssetByDenom(row.original.cap.denom)?.decimals ?? 6
|
||||||
|
|
||||||
|
// TODO: Replace with DisplayCurrency
|
||||||
|
return (
|
||||||
|
<TitleAndSubCell
|
||||||
|
className='text-xs'
|
||||||
|
title={formatValue(row.original.cap.max, { abbreviated: true, decimals })}
|
||||||
|
sub={`${percent}% Filled`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'cap',
|
||||||
|
enableSorting: false,
|
||||||
|
header: 'Details',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className='flex items-center justify-end'>
|
||||||
|
<div className={classNames('w-4', row.getIsExpanded() && 'rotate-180')}>
|
||||||
|
<ChevronDown />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[marketAssets, props.data],
|
||||||
|
)
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: props.data,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
},
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
debugTable: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table className='w-full'>
|
||||||
|
<thead className='bg-black/20'>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header, index) => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
onClick={header.column.getToggleSortingHandler()}
|
||||||
|
className={classNames(
|
||||||
|
'px-4 py-3',
|
||||||
|
header.column.getCanSort() && 'cursor-pointer',
|
||||||
|
header.id === 'symbol' ? 'text-left' : 'text-right',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'flex',
|
||||||
|
header.id === 'symbol' ? 'justify-start' : 'justify-end',
|
||||||
|
'align-center',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className='h-6 w-6 text-white'>
|
||||||
|
{header.column.getCanSort()
|
||||||
|
? {
|
||||||
|
asc: <SortAsc />,
|
||||||
|
desc: <SortDesc />,
|
||||||
|
false: <SortNone />,
|
||||||
|
}[header.column.getIsSorted() as string] ?? null
|
||||||
|
: null}
|
||||||
|
</span>
|
||||||
|
<Text
|
||||||
|
tag='span'
|
||||||
|
size='xs'
|
||||||
|
className='flex items-center font-normal text-white/40'
|
||||||
|
>
|
||||||
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{table.getRowModel().rows.map((row) => {
|
||||||
|
if (row.getIsExpanded()) {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={`${row.id}_subrow`}>
|
||||||
|
<VaultRow row={row} resetExpanded={table.resetExpanded} />
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<VaultRow key={row.original.address} row={row} resetExpanded={table.resetExpanded} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
export const VAULT_DEPOSIT_BUFFER = 0.999
|
||||||
|
|
||||||
export const VAULTS: VaultMetaData[] = [
|
export const VAULTS: VaultMetaData[] = [
|
||||||
{
|
{
|
||||||
address: 'osmo108q2krqr0y9g0rtesenvsw68sap2xefelwwjs0wedyvdl0cmrntqvllfjk',
|
address: 'osmo108q2krqr0y9g0rtesenvsw68sap2xefelwwjs0wedyvdl0cmrntqvllfjk',
|
||||||
@ -16,6 +18,7 @@ export const VAULTS: VaultMetaData[] = [
|
|||||||
primary: 'OSMO',
|
primary: 'OSMO',
|
||||||
secondary: 'ATOM',
|
secondary: 'ATOM',
|
||||||
},
|
},
|
||||||
|
isFeatured: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
address: 'osmo1g5hryv0gp9dzlchkp3yxk8fmcf5asjun6cxkvyffetqzkwmvy75qfmeq3f',
|
address: 'osmo1g5hryv0gp9dzlchkp3yxk8fmcf5asjun6cxkvyffetqzkwmvy75qfmeq3f',
|
||||||
|
1
src/types/interfaces/vaults.d.ts
vendored
1
src/types/interfaces/vaults.d.ts
vendored
@ -15,6 +15,7 @@ interface VaultMetaData {
|
|||||||
primary: string
|
primary: string
|
||||||
secondary: string
|
secondary: string
|
||||||
}
|
}
|
||||||
|
isFeatured?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VaultConfig extends VaultMetaData {
|
interface VaultConfig extends VaultMetaData {
|
||||||
|
@ -65,7 +65,7 @@ export async function getPrices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getVaults() {
|
export async function getVaults() {
|
||||||
return callAPI<Coin[]>(getEndpoint(Endpoints.VAULTS), 'default')
|
return callAPI<Vault[]>(getEndpoint(Endpoints.VAULTS), 'default')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWalletBalancesSWR(url: string) {
|
export async function getWalletBalancesSWR(url: string) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ASSETS } from 'constants/assets'
|
import { ASSETS } from 'constants/assets'
|
||||||
|
|
||||||
export function getAssetByDenom(denom: string) {
|
export function getAssetByDenom(denom: string): Asset | undefined {
|
||||||
return ASSETS.find((asset) => asset.denom === denom)
|
return ASSETS.find((asset) => asset.denom === denom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,3 +136,10 @@ export function formatAmountWithSymbol(coin: Coin) {
|
|||||||
rounded: true,
|
rounded: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const convertPercentage = (percent: number) => {
|
||||||
|
let percentage = percent
|
||||||
|
if (percent >= 100) percentage = 100
|
||||||
|
if (percent !== 0 && percent < 0.01) percentage = 0.01
|
||||||
|
return Number(formatValue(percentage, { minDecimals: 0, maxDecimals: 0 }))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user