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

Improve staking interface with apy #1261

Merged
merged 26 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6f4278d
fix queries
marc-aurele-besner Mar 1, 2025
de1b712
fix id collision
marc-aurele-besner Mar 1, 2025
d62d10d
fix fe base on removed fields
marc-aurele-besner Mar 1, 2025
576ae24
fix staking status to fit db status
marc-aurele-besner Mar 1, 2025
5277ca7
improve operator list status logic
marc-aurele-besner Mar 1, 2025
28e8580
improve op details cards
marc-aurele-besner Mar 4, 2025
72d0164
add yield on operator list
marc-aurele-besner Mar 4, 2025
f5c8b2e
Merge branch 'main' into feat/improve-staking-interface-with-apy
marc-aurele-besner Mar 4, 2025
ca4f198
allow improve precision on number formatting
marc-aurele-besner Mar 5, 2025
c458d47
fix yield and apy columns
marc-aurele-besner Mar 5, 2025
0e0d3e0
add ability to set some table column tooltip
marc-aurele-besner Mar 5, 2025
006b2db
add some tooltips on yield
marc-aurele-besner Mar 5, 2025
3803349
simplify minimumStake column with tooltip
marc-aurele-besner Mar 5, 2025
f8de825
improve dark theme on my nominations
marc-aurele-besner Mar 5, 2025
ea5f080
simplify&improve action on operator list
marc-aurele-besner Mar 5, 2025
d30bb13
fix build warnings
marc-aurele-besner Mar 5, 2025
5414bc9
allow to improve precision on number > 1
marc-aurele-besner Mar 5, 2025
8b2b841
remove unsupported operators list options
marc-aurele-besner Mar 5, 2025
1969044
add both status on nomination
marc-aurele-besner Mar 5, 2025
088ae6d
add unlock funds and withdraw button components
marc-aurele-besner Mar 6, 2025
78dd97c
improve nominator table
marc-aurele-besner Mar 6, 2025
8955465
Merge branch 'main' into feat/improve-staking-interface-with-apy
marc-aurele-besner Mar 6, 2025
bcc503d
simplify codegen
marc-aurele-besner Mar 6, 2025
6f10c78
simplify columns labels
marc-aurele-besner Mar 6, 2025
5d93cc6
force table state refresh
marc-aurele-besner Mar 7, 2025
e1b93a1
fix build
marc-aurele-besner Mar 7, 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
3 changes: 1 addition & 2 deletions explorer/codegen.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { CodegenConfig } from '@graphql-codegen/cli'
import * as dotenv from 'dotenv'
import { defaultIndexer } from './src/constants/indexers'

dotenv.config()

const config: CodegenConfig = {
generates: {
'./gql/graphql.tsx': {
schema: defaultIndexer.indexer,
schema: 'https://subql.green.taurus.subspace.network/v1/graphql',
documents: ['./src/**/query.gql'],
plugins: ['typescript', 'typescript-operations', 'typescript-react-apollo'],
},
Expand Down
1,142 changes: 660 additions & 482 deletions explorer/gql/graphql.tsx

Large diffs are not rendered by default.

358 changes: 275 additions & 83 deletions explorer/src/components/Staking/NominationsTable.tsx

Large diffs are not rendered by default.

17 changes: 4 additions & 13 deletions explorer/src/components/Staking/OperatorDetailsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { OperatorByIdQuery } from 'gql/graphql'
import useIndexers from 'hooks/useIndexers'
import Link from 'next/link'
import { FC } from 'react'
import { bigNumberToFormattedString } from 'utils/number'
import { bigNumberToFormattedString, numberFormattedString } from 'utils/number'
import { operatorStatus } from 'utils/operator'
import { AccountIconWithLink } from '../common/AccountIcon'

Expand Down Expand Up @@ -74,26 +74,17 @@ export const OperatorDetailsCard: FC<Props> = ({ operator, isDesktop = false })
<StyledListItem title='Total rewards collected'>
{bigNumberToFormattedString(operator.total_rewards_collected)} {tokenSymbol}
</StyledListItem>
<StyledListItem title='Total consensus storage fee'>
{bigNumberToFormattedString(operator.total_consensus_storage_fee)} {tokenSymbol}
</StyledListItem>
<StyledListItem title='Total domain execution fee'>
{bigNumberToFormattedString(operator.total_domain_execution_fee)} {tokenSymbol}
</StyledListItem>
<StyledListItem title='Total burned balance'>
{bigNumberToFormattedString(operator.total_burned_balance)} {tokenSymbol}
</StyledListItem>
<StyledListItem title='Total tax collected'>
{bigNumberToFormattedString(operator.total_tax_collected)} {tokenSymbol}
</StyledListItem>
<StyledListItem title='Nominators count'>
{bigNumberToFormattedString(operator.nominators_aggregate.aggregate?.count ?? '0')}
{numberFormattedString(operator.nominators_aggregate.aggregate?.count ?? 0)}
</StyledListItem>
<StyledListItem title='Deposits count'>
{bigNumberToFormattedString(operator.deposits_aggregate.aggregate?.count ?? '0')}
{numberFormattedString(operator.total_deposits_count)}
</StyledListItem>
<StyledListItem title='Withdrawals count'>
{bigNumberToFormattedString(operator.withdrawals_aggregate.aggregate?.count ?? '0')}
{numberFormattedString(operator.total_withdrawals_count)}
</StyledListItem>
<StyledListItem title='Status'>
{capitalizeFirstLetter(operatorStatus(operator.raw_status))}
Expand Down
111 changes: 37 additions & 74 deletions explorer/src/components/Staking/OperatorsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { SortedTable } from 'components/common/SortedTable'
import { Spinner } from 'components/common/Spinner'
import { BIGINT_ZERO, SHARES_CALCULATION_MULTIPLIER } from 'constants/general'
import { INTERNAL_ROUTES, Routes } from 'constants/routes'
import { OperatorPendingAction, OperatorStatus } from 'constants/staking'
import { OperatorStatus } from 'constants/staking'
import { OperatorsListDocument, OperatorsListQuery, OperatorsListQueryVariables } from 'gql/graphql'
import { useConsensusData } from 'hooks/useConsensusData'
import { useDomainsData } from 'hooks/useDomainsData'
Expand Down Expand Up @@ -36,7 +36,7 @@ import { Tooltip } from '../common/Tooltip'
import { DomainProgress } from '../Domain/DomainProgress'
import { NotFound } from '../layout/NotFound'
import { ActionsModal, OperatorAction, OperatorActionType } from './ActionsModal'
import { NominationButton, NominationButtonRow } from './NominationButton'
import { NominationButton } from './NominationButton'
import { OperatorActions, OperatorActionsRow } from './OperatorActions'

type Row = OperatorsListQuery['staking_operators'][0] & { nominatorsCount: number }
Expand Down Expand Up @@ -161,55 +161,35 @@ export const OperatorsList: FC<OperatorsListProps> = ({ domainId }) => {
),
currentTotalStake: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.currentTotalStake)} ${tokenSymbol}`,
currentStorageFeeDeposit: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.currentStorageFeeDeposit)} ${tokenSymbol}`,
currentTotalShares: ({ row }: Cell<Row>) =>
bigNumberToFormattedString(row.original.currentTotalShares),
yield30d: ({ row }: Cell<Row>) =>
`${numberFormattedString(row.original.yield30d * 1000, 6)} ${tokenSymbol}`,
yield7d: ({ row }: Cell<Row>) =>
`${numberFormattedString(row.original.yield7d * 1000, 6)} ${tokenSymbol}`,
yield1d: ({ row }: Cell<Row>) =>
`${numberFormattedString(row.original.yield1d * 1000, 6)} ${tokenSymbol}`,
apy30d: ({ row }: Cell<Row>) => `${numberFormattedString(row.original.apy30d * 100)}%`,
apy7d: ({ row }: Cell<Row>) => `${numberFormattedString(row.original.apy7d * 100)}%`,
apy1d: ({ row }: Cell<Row>) => `${numberFormattedString(row.original.apy1d * 100)}%`,
currentSharePrice: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.currentSharePrice)} ${tokenSymbol}`,
`${bigNumberToFormattedString(row.original.currentSharePrice, 6)} ${tokenSymbol}`,
totalDeposits: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalDeposits)} ${tokenSymbol}`,
totalEstimatedWithdrawals: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalEstimatedWithdrawals)} ${tokenSymbol}`,
totalWithdrawals: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalWithdrawals)} ${tokenSymbol}`,
totalTaxCollected: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalTaxCollected)} ${tokenSymbol}`,
`${bigNumberToFormattedString(row.original.totalTaxCollected, 6)} ${tokenSymbol}`,
totalRewardsCollected: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalRewardsCollected)} ${tokenSymbol}`,
totalTransfersIn: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalTransfersIn)} ${tokenSymbol}`,
transfersInCount: ({ row }: Cell<Row>) =>
numberFormattedString(row.original.transfersInCount),
totalTransfersOut: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalTransfersOut)} ${tokenSymbol}`,
transfersOutCount: ({ row }: Cell<Row>) =>
numberFormattedString(row.original.transfersOutCount),
totalTransfersRejected: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalTransfersRejected)} ${tokenSymbol}`,
totalRejectedTransfersClaimed: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalRejectedTransfersClaimed)} ${tokenSymbol}`,
rejectedTransfersClaimedCount: ({ row }: Cell<Row>) =>
numberFormattedString(row.original.rejectedTransfersClaimedCount),
transfersRejectedCount: ({ row }: Cell<Row>) =>
numberFormattedString(row.original.transfersRejectedCount),
totalVolume: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalVolume)} ${tokenSymbol}`,
totalConsensusStorageFee: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalConsensusStorageFee)} ${tokenSymbol}`,
totalDomainExecutionFee: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalDomainExecutionFee)} ${tokenSymbol}`,
totalBurnedBalance: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.totalBurnedBalance)} ${tokenSymbol}`,
accumulatedEpochShares: ({ row }: Cell<Row>) =>
bigNumberToFormattedString(row.original.accumulatedEpochShares),
accumulatedEpochStorageFeeDeposit: ({ row }: Cell<Row>) =>
bigNumberToFormattedString(row.original.accumulatedEpochStorageFeeDeposit),
activeEpochCount: ({ row }: Cell<Row>) =>
numberFormattedString(row.original.activeEpochCount),
`${bigNumberToFormattedString(row.original.totalRewardsCollected, 6)} ${tokenSymbol}`,
bundleCount: ({ row }: Cell<Row>) => numberFormattedString(row.original.bundleCount),
status: ({ row }: Cell<Row>) =>
allCapsToNormal(operatorStatus(JSON.parse(row.original.status ?? '{}'))),
rawStatus: ({ row }: Cell<Row>) => allCapsToNormal(row.original.rawStatus),
pendingAction: ({ row }: Cell<Row>) => allCapsToNormal(row.original.pendingAction),
status: ({ row }: Cell<Row>) => allCapsToNormal(row.original.status),
rawStatus: ({ row }: Cell<Row>) =>
allCapsToNormal(operatorStatus(JSON.parse(row.original.rawStatus ?? '{}'))),
lastBundleAt: ({ row }: Cell<Row>) => (
<Link
key={`created_at-${row.original.id}`}
Expand Down Expand Up @@ -252,11 +232,14 @@ export const OperatorsList: FC<OperatorsListProps> = ({ domainId }) => {
<div>{row.original.updatedAt}</div>
</Link>
),
minimumNominatorStake: ({ row }: Cell<Row>) =>
minimumStake: ({ row }: Cell<Row>) =>
`${bigNumberToFormattedString(row.original.minimumNominatorStake)} ${tokenSymbol}`,
nominationTax: ({ row }: Cell<Row>) => `${row.original.nominationTax}%`,
nominatorsAggregate: ({ row }: Cell<Row>) =>
numberFormattedString(row.original.nominatorsCount),
depositsCount: ({ row }: Cell<Row>) => numberFormattedString(row.original.depositsCount),
withdrawalsCount: ({ row }: Cell<Row>) =>
numberFormattedString(row.original.withdrawalsCount),
myStake: ({ row }: Cell<Row>) => {
const deposit = deposits.find(
(d) => d.account === subspaceAccount && d.operatorId.toString() === row.original.id,
Expand Down Expand Up @@ -292,52 +275,32 @@ export const OperatorsList: FC<OperatorsListProps> = ({ domainId }) => {
)
},
actions: ({ row }: Cell<Row>) => {
const isOperator = row.original.accountId === subspaceAccount
const nominator = row.original.nominators.find(
(nominator) => nominator.account_id === subspaceAccount,
)
const excludeActions = []
if (!isOperator)
if (row.original.accountId !== subspaceAccount)
excludeActions.push(OperatorActionType.Deregister, OperatorActionType.UnlockNominator)

if (row.original.status === OperatorStatus.PENDING_NEXT_EPOCH)
excludeActions.push(OperatorActionType.Nominating)
if (row.original.status === OperatorStatus.ACTIVE)
excludeActions.push(OperatorActionType.UnlockNominator)
if (row.original.status === OperatorStatus.DEREGISTERED)
excludeActions.push(OperatorActionType.Nominating, OperatorActionType.Deregister)
if (row.original.pendingAction !== OperatorPendingAction.READY_FOR_UNLOCK_NOMINATOR)
excludeActions.push(OperatorActionType.UnlockNominator)

if (!nominator)
excludeActions.push(OperatorActionType.Withdraw, OperatorActionType.UnlockFunds)
if (!nominator || nominator.unlock_at_confirmed_domain_block_number.length === 0)
excludeActions.push(OperatorActionType.UnlockFunds)
if (row.original.status === OperatorStatus.SLASHED) return <></>
const rowData = {
original: {
...row.original,
// eslint-disable-next-line camelcase
current_total_shares: row.original.currentTotalShares,
},
} as OperatorActionsRow
return (
<div className='flex gap-2'>
<OperatorActions
handleAction={handleAction}
row={
{
original: {
...row.original,
// eslint-disable-next-line camelcase
current_total_shares: row.original.currentTotalShares,
},
} as OperatorActionsRow
}
row={rowData}
excludeActions={excludeActions}
/>
{!excludeActions.includes(OperatorActionType.Nominating) && (
<NominationButton
handleAction={handleAction}
row={
{
original: {
...row.original,
// eslint-disable-next-line camelcase
current_total_shares: row.original.currentTotalShares,
},
} as NominationButtonRow
}
/>
<NominationButton handleAction={handleAction} row={rowData} />
)}
</div>
)
Expand Down
40 changes: 40 additions & 0 deletions explorer/src/components/Staking/UnlockFundsButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client'

import { FC, useCallback } from 'react'
import { OperatorAction, OperatorActionType } from './ActionsModal'

export type UnlockFundsButtonRow = {
original: {
id: string
current_total_shares: bigint
}
}

interface UnlockFundsButtonProps {
handleAction: (action: OperatorAction) => void
row: UnlockFundsButtonRow
}

export const UnlockFundsButton: FC<UnlockFundsButtonProps> = ({ handleAction, row }) => {
const handleClick = useCallback(
() =>
handleAction({
type: 'UnlockFunds' as OperatorActionType,
operatorId: parseInt(row.original.id),
}),
[handleAction, row.original.id],
)

return (
<div className='relative'>
<button
className='relative w-full cursor-default rounded-full bg-primaryAccent from-primaryAccent to-blueUndertone py-[10px] pl-3 pr-16 text-left font-["Montserrat"] text-white shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 dark:bg-gradient-to-r dark:text-white sm:text-sm md:pr-10'
onClick={handleClick}
>
<div className='flex items-center justify-center'>
<span className='ml-2 w-28 text-center text-sm'>Unlock Funds</span>
</div>
</button>
</div>
)
}
40 changes: 40 additions & 0 deletions explorer/src/components/Staking/WithdrawButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client'

import { FC, useCallback } from 'react'
import { OperatorAction, OperatorActionType } from './ActionsModal'

export type WithdrawButtonRow = {
original: {
id: string
current_total_shares: bigint
}
}

interface WithdrawButtonProps {
handleAction: (action: OperatorAction) => void
row: WithdrawButtonRow
}

export const WithdrawButton: FC<WithdrawButtonProps> = ({ handleAction, row }) => {
const handleClick = useCallback(
() =>
handleAction({
type: 'Withdraw' as OperatorActionType,
operatorId: parseInt(row.original.id),
}),
[handleAction, row.original.id],
)

return (
<div className='relative'>
<button
className='relative w-full cursor-default rounded-full bg-primaryAccent from-primaryAccent to-blueUndertone py-[10px] pl-3 pr-16 text-left font-["Montserrat"] text-white shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 dark:bg-gradient-to-r dark:text-white sm:text-sm md:pr-10'
onClick={handleClick}
>
<div className='flex items-center justify-center'>
<span className='ml-2 w-28 text-center text-sm'>Withdraw</span>
</div>
</button>
</div>
)
}
Loading