Skip to content

Commit

Permalink
Merge pull request #2 from aleph-im/release/halving
Browse files Browse the repository at this point in the history
feat: Prepare account page to support 5 linked CRNs + staking halving
  • Loading branch information
moshemalawach authored Mar 21, 2024
2 parents be4c76e + 5eab0d4 commit 999cd55
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 37 deletions.
5 changes: 2 additions & 3 deletions src/components/common/AvailableCRNSpotChart/cmp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { memo, useMemo } from 'react'
import { CCN } from '@/domain/node'
import { StakeManager } from '@/domain/stake'
import { CCN, NodeManager } from '@/domain/node'
import { Cell, Pie, PieChart } from 'recharts'
import { useTheme } from 'styled-components'
import Card1 from '../Card1'
Expand All @@ -24,7 +23,7 @@ export const AvailableCRNSpotChart = ({
const [linkedSpots, freeSpots] = nodes.reduce(
(ac, cv) => {
const linked = cv.resource_nodes.length
const free = Math.max(3 - linked, 0)
const free = Math.max(NodeManager.maxLinkedPerNode - linked, 0)
ac[0] += linked
ac[1] += free
return ac
Expand Down
3 changes: 2 additions & 1 deletion src/components/common/CRNRewardsCell/cmp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const CRNRewardsCell = ({ node }: { node: CRN }) => {
// @todo: Refactor this (use singleton)
const rewardManager = new StakeManager()

const rewards = rewardManager.CRNRewardsPerDay(node)
const rewards = rewardManager.CRNRewardsPerDay(node) * (365 / 12)
const isNotFullyLinked = useMemo(() => !node.parent, [node])

return (
Expand All @@ -18,6 +18,7 @@ export const CRNRewardsCell = ({ node }: { node: CRN }) => {
) : (
<div tw="inline-flex gap-2 items-center">
~ <Price value={rewards} />
/M
</div>
)}
</>
Expand Down
13 changes: 10 additions & 3 deletions src/components/common/EstimatedNodeRewardsChart/cmp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { StakeManager } from '@/domain/stake'
import { Cell, Pie, PieChart } from 'recharts'
import { useTheme } from 'styled-components'
import Card1 from '../Card1'
import { ColorDot, Logo, TextGradient } from '@aleph-front/core'
import { ColorDot, TextGradient } from '@aleph-front/core'
import { SVGGradients } from '../charts'
import Price from '../Price'

Expand All @@ -20,9 +20,16 @@ export const EstimatedNodeRewardsChart = ({
const theme = useTheme()

const data = useMemo(() => {
const activeNodes = stakeManager.activeNodes(nodes || [])
let perDayRewards = 0

if (nodes) {
const activeNodes = stakeManager.activeNodes(nodes)
const totalPerDay = stakeManager.totalPerDay(nodes)
// const totalPerDay = StakeManager.dailyCCNRewardsPool / activeNodes.length

perDayRewards = totalPerDay / activeNodes.length
}

const perDayRewards = 15000 / activeNodes.length
const perMonthRewards = perDayRewards * 30
const total = perMonthRewards + perDayRewards

Expand Down
6 changes: 3 additions & 3 deletions src/components/common/NodeLinkedNodes/cmp.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { HTMLAttributes, memo } from 'react'
import { StyledDotIcon } from './styles'
import { CRN } from '@/domain/node'
import { CRN, NodeManager } from '@/domain/node'

// https://github.com/aleph-im/aleph-account/blob/main/src/components/NodesTable.vue#L163

export type NodeLinkedNodesProps = HTMLAttributes<HTMLDivElement> & {
nodes?: CRN[]
subfix?: string
max?: number
}

export const NodeLinkedNodes = ({
nodes,
subfix,
max = 3,
...rest
}: NodeLinkedNodesProps) => {
const max = NodeManager.maxLinkedPerNode

return (
<div tw="inline-flex items-center gap-3" {...rest}>
<div tw="flex items-stretch gap-0.5">
Expand Down
8 changes: 7 additions & 1 deletion src/components/pages/earn/CoreChannelNodeDetailPage/cmp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import NodeDetailLink from '@/components/common/NodeDetailLink'
import { apiServer } from '@/helpers/constants'
import Image from 'next/image'
import Price from '@/components/common/Price'
import { NodeManager } from '@/domain/node'

export const CoreChannelNodeDetailPage = () => {
const {
Expand Down Expand Up @@ -249,7 +250,12 @@ export const CoreChannelNodeDetailPage = () => {
<div tw="flex-1 w-1/3 min-w-[20rem] flex flex-col gap-9">
<Card2 title="LINKED RESOURCES">
{Array.from(
{ length: Math.max(3, node?.crnsData.length || 0) },
{
length: Math.max(
NodeManager.maxLinkedPerNode,
node?.crnsData.length || 0,
),
},
(_, i) => {
const crn = node?.crnsData[i]

Expand Down
6 changes: 5 additions & 1 deletion src/components/pages/earn/CoreChannelNodesPage/cmp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import NetworkHealthChart from '@/components/common/NetworkHealthChart'
import EstimatedNodeRewardsChart from '@/components/common/EstimatedNodeRewardsChart'
import { useLazyRender } from '@/hooks/common/useLazyRender'
import AvailableCRNSpotChart from '@/components/common/AvailableCRNSpotChart'
import { StakeManager } from '@/domain/stake'

export const CoreChannelNodesPage = (props: UseCoreChannelNodesPageProps) => {
const {
Expand Down Expand Up @@ -45,7 +46,10 @@ export const CoreChannelNodesPage = (props: UseCoreChannelNodesPageProps) => {
variant="secondary"
size="md"
tw="gap-2.5"
disabled={!account || (accountBalance || 0) <= 200_000}
disabled={
!account ||
(accountBalance || 0) <= StakeManager.minStakeToActivateNode
}
>
<Icon name="key" />
Create core node
Expand Down
6 changes: 4 additions & 2 deletions src/domain/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '@/helpers/schemas'
import { FileManager } from './file'
import { subscribeSocketFeed } from '@/helpers/socket'
import { StakeManager } from './stake'

const { post } = messages

Expand Down Expand Up @@ -278,6 +279,7 @@ export class NodeManager {
static updateCRNSchema = updateCRNSchema

static maxStakedPerNode = 1_000_000
static maxLinkedPerNode = 5

constructor(
protected account?: Account,
Expand Down Expand Up @@ -643,7 +645,7 @@ export class NodeManager {
if (!!node.parent)
return [false, `The node is already linked to ${node.parent} ccn`]

if (userNode.resource_nodes.length >= 3)
if (userNode.resource_nodes.length >= NodeManager.maxLinkedPerNode)
return [
false,
`The user node is already linked to ${userNode.resource_nodes.length} nodes`,
Expand All @@ -660,7 +662,7 @@ export class NodeManager {
return 'The linked CCN is underperforming'
} else {
if (node.score < 0.8) return 'The CCN is underperforming'
if ((node?.crnsData.length || 0) < 3)
if ((node?.crnsData.length || 0) < StakeManager.minLinkedNodesForPenalty)
return 'The CCN has less than three linked CRNs'
if (!staking && node?.crnsData.some((crn) => crn.score < 0.8))
return 'One of the linked CRN is underperforming'
Expand Down
67 changes: 44 additions & 23 deletions src/domain/stake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ export type RewardsResponse = {
}

export class StakeManager {
static dailyCCNRewardsPool = 15_000
static dailyCRNRewardsBase = 250 / (365 / 12)
static dailyCRNRewardsVariable = 1250 / (365 / 12)

static minStakeToActivateNode = 200_000
static minLinkedNodesForPenalty = 3

constructor(
protected account?: Account,
protected channel = defaultAccountChannel,
Expand Down Expand Up @@ -161,7 +168,7 @@ export class StakeManager {
}

totalStakedByOperators(nodes: AlephNode[]): number {
return nodes.length * 200_000
return nodes.length * StakeManager.minStakeToActivateNode
}

totalStakedInActive(nodes: CCN[]): number {
Expand All @@ -170,9 +177,12 @@ export class StakeManager {

totalPerDay(nodes: CCN[]): number {
const activeNodes = this.activeNodes(nodes).length
if (!activeNodes) return activeNodes
if (!activeNodes) return 0

return 15000 * ((Math.log10(activeNodes) + 1) / 3)
// @note: https://medium.com/aleph-im/aleph-im-staking-go-live-part-2-stakers-tokenomics-663164b5ec78
return (
StakeManager.dailyCCNRewardsPool * ((Math.log10(activeNodes) + 1) / 3)
)
}

totalPerAlephPerDay(nodes: CCN[]): number {
Expand All @@ -190,15 +200,10 @@ export class StakeManager {
let estAPY = 0

if (node.score) {
const linkedCRN = Math.min(
node.crnsData.filter((x) => x.score >= 0.2).length,
3,
)

const normalizedScore = normalizeValue(node.score, 0.2, 0.8, 0, 1)
const linkedCRNPenalty = (3 - linkedCRN) / 10
const linkedCRNPenalty = this.totalLinkedCRNPenaltyFactor(node)

estAPY = this.currentAPY(nodes) * normalizedScore * (1 - linkedCRNPenalty)
estAPY = this.currentAPY(nodes) * normalizedScore * linkedCRNPenalty
}

return estAPY
Expand All @@ -208,29 +213,45 @@ export class StakeManager {
return stake * this.totalPerAlephPerDay(nodes)
}

CCNRewardsPerDay(node: CCN, nodes: CCN[]): number {
let estRewards = 0
totalLinkedCRNPenaltyFactor(node: CCN): number {
/** @note:
* 3 to 5 linked > 100%
* 2 linked > 90%
* 1 linked > 80%
* 0 linked > 70%
**/

const linkedCRN = Math.min(
node.crnsData.filter((x) => x.score >= 0.2).length,
StakeManager.minLinkedNodesForPenalty,
)

if (node.score) {
const linkedCRN = Math.min(node.crnsData.length, 3)
const activeNodes = this.activeNodes(nodes).length
const pool = 15_000 / activeNodes
const normalizedScore = normalizeValue(node.score, 0.2, 0.8, 0, 1)
const linkedCRNPenalty = (3 - linkedCRN) / 10
return 1 - (StakeManager.minLinkedNodesForPenalty - linkedCRN) / 10
}

estRewards = pool * normalizedScore * (1 - linkedCRNPenalty)
}
CCNRewardsPerDay(node: CCN, nodes: CCN[]): number {
if (!node.score) return 0

return estRewards
const activeNodes = this.activeNodes(nodes).length
const nodePool = StakeManager.dailyCCNRewardsPool / activeNodes
const normalizedScore = normalizeValue(node.score, 0.2, 0.8, 0, 1)
const linkedCRNPenalty = this.totalLinkedCRNPenaltyFactor(node)

return nodePool * normalizedScore * linkedCRNPenalty
}

CRNRewardsPerDay(node: CRN): number {
if (!node.parent) return 0
if (!node.score || !node.decentralization) return 0

const { decentralization, score } = node
const maxRewards = 500 + decentralization * 2500

return maxRewards * normalizeValue(score, 0.2, 0.8, 0, 1)
const maxRewards =
StakeManager.dailyCRNRewardsBase +
StakeManager.dailyCRNRewardsVariable * decentralization

const normalizedScore = normalizeValue(score, 0.2, 0.8, 0, 1)

return maxRewards * normalizedScore
}
}

0 comments on commit 999cd55

Please sign in to comment.