Skip to content

Commit

Permalink
Feat: nomination pool fees (#1178)
Browse files Browse the repository at this point in the history
* chore: update Polkadot priority pool id

* style: truncate pool name

* feat: usePoolCommission hook

* feat: display commission on staking side sheet

* feat: added tooltip to Comssion fee

* feat: added commission fee to pool selector

* fix: pagination indicator gap

* chore: updated tooltip text
  • Loading branch information
UrbanWill authored Sep 18, 2024
1 parent 6b9638b commit 162ea79
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 23 deletions.
4 changes: 3 additions & 1 deletion apps/portal/src/components/recipes/StakeForm/StakeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const PoolInfo = (props: PoolInfoProps) => {
>
<div css={{ display: 'flex', alignItems: 'center', gap: '0.8rem' }}>
<StakeStatusIndicator status={props.status} />
<Text.Body css={{ fontSize: '1.4rem' }} alpha={expanded ? 'high' : 'medium'}>
<Text.Body alpha={expanded ? 'high' : 'medium'} className="md:max-w-[31rem] md:truncate text-[1.4rem]">
{props.noPoolsAvailable ? 'No pools available' : props.name}
</Text.Body>
</div>
Expand Down Expand Up @@ -333,6 +333,7 @@ export type StakeFormProps = {
poolInfo: ReactNode
estimatedYield: ReactNode
claimPermission: ReactNode
commissionFee: ReactNode
stakeButton: ReactNode
existingPool: ReactNode
}
Expand Down Expand Up @@ -361,6 +362,7 @@ const StakeForm = Object.assign(
{props.amountInput}
{props.poolInfo}
{props.estimatedYield}
{props.commissionFee}
{props.claimPermission}
{props.stakeButton}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const StakeTargetSelectorDialog = Object.assign(
: []),
])}
</motion.div>
<div css={{ display: 'flex', justifyContent: 'end', marginTop: '0.6rem' }}>
<div css={{ display: 'flex', justifyContent: 'end', marginTop: '0.6rem', gap: '1rem' }}>
<Button variant="noop" onClick={() => setPage(page => page - 1)} hidden={!hasPreviousPage}>
<div css={{ display: 'flex', alignItems: 'center', userSelect: 'none' }}>
<ChevronLeft />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type StakeTargetSelectorItemProps = {
balance: string
balancePlanck?: bigint
balanceDescription: string
commissionFeeDescription?: string
commissionFee?: string
estimatedReturn?: number | bigint
estimatedApr?: string
estimatedAprDescription?: string
Expand Down Expand Up @@ -106,17 +108,27 @@ const StakeTargetSelectorItem = (props: StakeTargetSelectorItemProps) => {
</div>
) : null}
</Tooltip>
<Tooltip content={props.estimatedAprDescription}>
{props.estimatedApr ? <div>{props.estimatedApr}</div> : null}
</Tooltip>
</div>
</div>
<Tooltip content={props.estimatedAprDescription}>
{props.estimatedApr ? <div>{props.estimatedApr}</div> : null}
</Tooltip>
{props.talismanRecommended && (
<Tooltip content={props.talismanRecommendedDescription}>
<TalismanHand size="1.4rem" />
</Tooltip>
)}
</Text.Body>
<Tooltip content={<div className="max-w-[276px]">{props.commissionFeeDescription}</div>}>
{props.commissionFee ? (
<div className="text-[14px] flex justify-between">
<div className="flex gap-2 items-center">
<Text.Body alpha={alpha}>Commission fee</Text.Body>
</div>
<Text.Body alpha={alpha}>{props.commissionFee}</Text.Body>
</div>
) : null}
</Tooltip>
</article>
)
}
Expand Down
67 changes: 51 additions & 16 deletions apps/portal/src/components/widgets/staking/substrate/StakeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ import PoolClaimPermissionDialog, {
toUiPermission,
} from './PoolClaimPermissionDialog'
import UnstakeDialog from './UnstakeDialog'
import { usePoolCommission } from '@/domains/staking/substrate/nominationPools/hooks/usePoolCommission'
import type { ApiPromise } from '@polkadot/api'
import { type Decimal } from '@talismn/math'
import { CircularProgressIndicator, Select } from '@talismn/ui'
import { Tooltip } from '@talismn/ui'
import { Info } from '@talismn/web-icons'
import BN from 'bn.js'
import {
Suspense,
Expand Down Expand Up @@ -164,6 +167,7 @@ const PoolSelector = (props: {
const [chain, recommendedPools, nativeTokenDecimal] = useRecoilValue(
waitForAll([useChainRecoilState(), useRecommendedPoolsState(), useNativeTokenDecimalState()])
)
const { getCurrentCommission } = usePoolCommission()

return (
<PoolSelectorDialog
Expand All @@ -180,22 +184,26 @@ const PoolSelector = (props: {
props.onDismiss()
}, [newPoolId, props])}
>
{recommendedPools.map((pool, index) => (
<PoolSelectorDialog.Item
key={pool.poolId}
selected={props.selectedPoolId !== undefined && pool.poolId === props.selectedPoolId}
highlighted={newPoolId !== undefined && pool.poolId === newPoolId}
talismanRecommended={index === 0}
name={pool.name ?? ''}
detailUrl={
chain?.subscanUrl ? new URL(`nomination_pool/${pool.poolId}`, chain.subscanUrl).toString() : undefined
}
balance={`${nativeTokenDecimal.fromPlanck(pool.bondedPool.points.toBigInt()).toLocaleString()} staked`}
rating={3}
count={pool.bondedPool.memberCounter.toString()}
onClick={() => setNewPoolId(pool.poolId)}
/>
))}
{recommendedPools.map((pool, index) => {
return (
<PoolSelectorDialog.Item
key={pool.poolId}
selected={props.selectedPoolId !== undefined && pool.poolId === props.selectedPoolId}
highlighted={newPoolId !== undefined && pool.poolId === newPoolId}
talismanRecommended={index === 0}
name={pool.name ?? ''}
detailUrl={
chain?.subscanUrl ? new URL(`nomination_pool/${pool.poolId}`, chain.subscanUrl).toString() : undefined
}
balance={`${nativeTokenDecimal.fromPlanck(pool.bondedPool.points.toBigInt()).toLocaleString()} staked`}
rating={3}
count={pool.bondedPool.memberCounter.toString()}
onClick={() => setNewPoolId(pool.poolId)}
commissionFeeDescription="Commission shown is only for the nomination pool, but actual earnings will reflect fees charged by both validators and nomination pools. The total amount of fees can change regularly and can't be determined by Talisman."
commissionFee={getCurrentCommission(pool.poolId).toString() + '%'}
/>
)
})}
</PoolSelectorDialog>
)
}
Expand Down Expand Up @@ -267,6 +275,32 @@ const DeferredEstimatedYield = (props: { amount: Decimal }) => (
<EstimatedYield amount={useDeferredValue(props.amount)} />
)

const CommissionFee = ({ poolId }: { poolId: number }) => {
const { getCurrentCommission } = usePoolCommission()

const poolCommission = getCurrentCommission(poolId)

return (
<div className="text-[14px] flex justify-between">
<div className="flex gap-2 items-center">
<div>Commission fee</div>
<Tooltip
content={
<div className="max-w-[276px] text-[12px]">
Commission shown is only for the nomination pool, but actual earnings will reflect fees charged by both
validators and nomination pools. The total amount of fees can change regularly and can't be determined by
Talisman.
</div>
}
>
<Info size="1.4rem" />
</Tooltip>
</div>
<div>{`${poolCommission}%`}</div>
</div>
)
}

export const ControlledStakeForm = (props: { assetSelector: ReactNode; account?: string }) => {
const location = useLocation()

Expand Down Expand Up @@ -476,6 +510,7 @@ export const ControlledStakeForm = (props: { assetSelector: ReactNode; account?:
</Suspense>
)
}
commissionFee={<CommissionFee poolId={selectedPoolId || 0} />}
claimPermission={
<StakeFormComponent.ClaimPermission
permission={toUiPermission(claimPermission)}
Expand Down
4 changes: 2 additions & 2 deletions apps/portal/src/domains/chains/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const chainConfigs: ChainConfig[] = [
{
genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
hasNominationPools: true,
priorityPool: 16,
talismanPools: [12, 16],
priorityPool: 282,
talismanPools: [12, 16, 282],
novaIndexerUrl: 'https://api.subquery.network/sq/nova-wallet/nova-wallet-polkadot',
},
// Kusama
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useRecommendedPoolsState } from '../recoils'
import type { AnyJson } from '@w3ux/types'
import { useRecoilValue } from 'recoil'

export type BondedPool = {
commission?: {
current?: AnyJson | null
max?: AnyJson | null
changeRate: {
maxIncrease: AnyJson
minDelay: AnyJson
} | null
throttleFrom?: AnyJson | null
}
}

export const usePoolCommission = () => {
const pools = useRecoilValue(useRecommendedPoolsState())

const getCurrentCommission = (poolId: number) => {
const pool: BondedPool | undefined = pools.find(pool => pool.poolId === poolId)?.bondedPool.toHuman()

return Number(pool?.commission?.current?.[0]?.slice(0, -1) || 0)
}

return { getCurrentCommission }
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@w3ux/types": "^0.2.0",
"eslint": "^9.2.0",
"husky": "^8.0.3",
"prettier": "^2.8.8",
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12620,6 +12620,13 @@ __metadata:
languageName: node
linkType: hard

"@w3ux/types@npm:^0.2.0":
version: 0.2.0
resolution: "@w3ux/types@npm:0.2.0"
checksum: 10c0/dae057b008f110f99da6c97c6f43849708234fbabeb5fa7370a3f9908513227f420ba48c6183b421ec9f8c758cd2c98e45a8a380efe6491012341bcc08b3063b
languageName: node
linkType: hard

"@wagmi/connectors@npm:5.0.21":
version: 5.0.21
resolution: "@wagmi/connectors@npm:5.0.21"
Expand Down Expand Up @@ -25472,6 +25479,7 @@ __metadata:
"@commitlint/cli": "npm:^18.4.3"
"@commitlint/config-conventional": "npm:^18.4.3"
"@trivago/prettier-plugin-sort-imports": "npm:^4.3.0"
"@w3ux/types": "npm:^0.2.0"
eslint: "npm:^9.2.0"
husky: "npm:^8.0.3"
prettier: "npm:^2.8.8"
Expand Down

0 comments on commit 162ea79

Please sign in to comment.