Skip to content

WIP: Payouts page #164

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 19 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
{
"name": "ksm-app",
"version": "0.2.0",
"version": "2.0.1",
"homepage": "https://KappaSigmaMu.github.io/",
"types": "src/types.d.ts",
"dependencies": {
"@kappasigmamu/canary-component": "^0.4.7",
"@polkadot/api": "^11.3.1",
"@polkadot/api-derive": "^11.3.1",
"@polkadot/extension-dapp": "^0.47.5",
"@polkadot/extension-inject": "^0.47.5",
"@polkadot/keyring": "^12.6.2",
"@polkadot/networks": "^12.6.2",
"@polkadot/react-identicon": "^3.6.6",
"@polkadot/rpc-core": "^11.3.1",
"@polkadot/types": "^11.3.1",
"@polkadot/types-augment": "^11.3.1",
"@polkadot/types-support": "^11.3.1",
"@polkadot/ui-keyring": "^3.6.6",
"@polkadot/ui-settings": "^3.6.6",
"@polkadot/util": "^12.6.2",
"@polkadot/util-crypto": "^12.6.2",
"@polkadot/api": "^14.3.1",
"@polkadot/api-derive": "^14.3.1",
"@polkadot/extension-dapp": "^0.56.2",
"@polkadot/extension-inject": "^0.56.2",
"@polkadot/keyring": "^13.2.3",
"@polkadot/networks": "^13.2.3",
"@polkadot/react-identicon": "^3.11.3",
"@polkadot/rpc-core": "^14.3.1",
"@polkadot/types": "^14.3.1",
"@polkadot/types-augment": "^14.3.1",
"@polkadot/types-support": "^14.3.1",
"@polkadot/ui-keyring": "^3.11.3",
"@polkadot/ui-settings": "^3.11.3",
"@polkadot/util": "^13.2.3",
"@polkadot/util-crypto": "^13.2.3",
"@popperjs/core": "^2.11.8",
"@talismn/connect-wallets": "1.2.5",
"bfj": "^8.0.0",
Expand Down Expand Up @@ -69,6 +69,7 @@
"web-vitals": "^3.5.2"
},
"devDependencies": {
"@acala-network/chopsticks": "^1.0.1",
"@babel/core": "^7.23.9",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.23.9",
Expand Down Expand Up @@ -129,7 +130,7 @@
},
"scripts": {
"build": "NODE_OPTIONS=--openssl-legacy-provider node scripts/build.js",
"chopsticks": "npx @acala-network/chopsticks@latest --config=config/kusama.yml",
"chopsticks": "chopsticks --config=config/kusama.yml",
"cy:open": "cypress open",
"deploy": "gh-pages -d build",
"eslint:fix": "eslint \"{src,apps,libs,test}/**/*.{ts,tsx}\" --fix",
Expand All @@ -152,5 +153,5 @@
"not dead",
"not op_mini all"
],
"packageManager": "[email protected].0"
"packageManager": "[email protected].1"
}
4 changes: 2 additions & 2 deletions src/hooks/useBlockTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export function useBlockTime(blocks: number | BN = BN_ONE, apiOverride?: ApiProm
(a.consts.timestamp?.minimumPeriod.gte(THRESHOLD)
? a.consts.timestamp.minimumPeriod.mul(BN_TWO)
: a.query.parachainSystem
? DEFAULT_TIME.mul(BN_TWO)
: DEFAULT_TIME)
? DEFAULT_TIME.mul(BN_TWO)
: DEFAULT_TIME)
const value = blockTime.mul(bnToBn(blocks)).toNumber()
const time = extractTime(Math.abs(value))
const { days, hours, minutes, seconds } = time
Expand Down
2 changes: 2 additions & 0 deletions src/pages/explore/ExplorePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CandidatesPage } from './CandidatesPage'
import { LoadingSpinner } from './components/LoadingSpinner'
import { NavigationBar } from './components/NavigationBar'
import { MembersPage } from './MembersPage'
import { PayoutsPage } from './PayoutsPage'
import { ProofOfInkPage } from './ProofOfInkPage'
import { SuspendedPage } from './SuspendedPage'
import { NavigateWithQuery } from '../../components/NavigateWithQuery'
Expand Down Expand Up @@ -79,6 +80,7 @@ const ExplorePage = (): JSX.Element => {
<Route path="/bidders" element={<BiddersPage api={api} />} />
<Route path="/candidates" element={<CandidatesPage api={api} handleUpdateTotal={handleUpdateTotal} />} />
<Route path="/members" element={<MembersPage api={api} />} />
<Route path="/payouts" element={<PayoutsPage api={api} />} />
<Route path="/suspended" element={<SuspendedPage api={api} />} />
<Route path="/poi/*" element={<ProofOfInkPage api={api} />} />
</Routes>
Expand Down
35 changes: 0 additions & 35 deletions src/pages/explore/MembersPage/components/PayoutsAccordion.tsx

This file was deleted.

146 changes: 146 additions & 0 deletions src/pages/explore/PayoutsPage/components/PayoutsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import type { ApiPromise } from '@polkadot/api'
import { WalletAccount } from '@talismn/connect-wallets'
import { useState, useEffect } from 'react'
import { Badge, Col } from 'react-bootstrap'
import styled from 'styled-components'
import { DataHeaderRow, DataRow } from '../../../../components/base'
import { FormatBalance } from '../../../../components/FormatBalance'
import { useBlockTime } from '../../../../hooks/useBlockTime'
import { Identicon } from '../../components/Identicon'

const StyledDataRow = styled(DataRow)`
background-color: ${(props) => (props.$isDefender ? props.theme.colors.black : '')};
border: ${(props) => (props.$isDefender ? `2px solid ${props.theme.colors.secondary}` : '')};
&:hover {
cursor: pointer;
}
@media (max-width: 992px) {
padding-block: 12px;
margin-inline: 2px;
}
`

type PayoutsListProps = {
api: ApiPromise
members: ExtendedSocietyMember[]
activeAccount: WalletAccount | undefined
handleUpdate: () => void
}

const TimeRemaining = ({ block, latestBlock, api }: { block: number; latestBlock: number | null; api: ApiPromise }) => {
if (!latestBlock)
return (
<Badge pill bg="black" className="me-2 p-2">
Calculating...
</Badge>
)

const blocksLeft = block - latestBlock
const [, time] = useBlockTime(blocksLeft, api)

if (!time)
return (
<Badge pill bg="black" className="me-2 p-2">
Calculating...
</Badge>
)

if (blocksLeft <= 0)
return (
<Badge pill bg="primary" className="me-2 p-2">
Matured
</Badge>
)

return (
<Badge pill bg="secondary" text="black" className="me-2 p-2">
Maturing in {time}
</Badge>
)
}

const PayoutsList = ({ api, members }: PayoutsListProps): JSX.Element => {
const [latestBlock, setLatestBlock] = useState<number | null>(null)

useEffect(() => {
const fetchLatestBlock = async () => {
const header = await api.rpc.chain.getHeader()
setLatestBlock(header.number.toNumber())
}

fetchLatestBlock()
const unsub = api.rpc.chain.subscribeNewHeads((header) => {
setLatestBlock(header.number.toNumber())
})

return () => {
unsub.then((u) => u())
}
}, [api])

if (members.length === 0) return <>No members</>

return (
<>
<DataHeaderRow className="d-none d-lg-flex text-center">
<Col lg={1}>#</Col>
<Col lg={5} className="text-center text-lg-start">
Wallet Hash
</Col>
<Col lg={2} className="text-center text-lg-start">
Paid
</Col>
<Col lg={2} className="text-center text-lg-start">
Pending
</Col>
<Col lg={2}></Col>
</DataHeaderRow>

{members.map((member: ExtendedSocietyMember) => (
<StyledDataRow key={member.accountId.toString()}>
<Col lg={1} className="text-center">
<Identicon value={member.accountId.toHuman()} size={32} theme={'polkadot'} />
</Col>
<Col lg={5} className="text-center text-lg-start">
{member.accountId.toHuman()}
</Col>
<Col lg={2} className="text-center text-lg-start">
<FormatBalance balance={member.extendedPayouts.paid} />
</Col>
<Col lg={2} className="text-center text-lg-start">
<FormatBalance balance={member.extendedPayouts.pending} />
</Col>
<Col lg={2} className="text-center text-lg-end">
{member.isFounder && (
<Badge pill bg="dark" className="me-2 p-2">
Founder
</Badge>
)}
{member.rank.toNumber() > 0 && (
<Badge pill bg="dark" className="me-2 p-2">
Ranked
</Badge>
)}
{member.hasPayouts && (
<>
<TimeRemaining block={member.extendedPayouts.block} latestBlock={latestBlock} api={api} />
</>
)}
{member.extendedPayouts.pending == 0 && member.extendedPayouts.paid > 0 && (
<Badge pill bg="black" className="me-2 p-2">
Paid
</Badge>
)}
{!member.hasPayouts && member.extendedPayouts.paid == 0 && (
<Badge pill bg="black" className="me-2 p-2">
Paid before V2
</Badge>
)}
</Col>
</StyledDataRow>
))}
</>
)
}

export { PayoutsList }
93 changes: 93 additions & 0 deletions src/pages/explore/PayoutsPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { ApiPromise } from '@polkadot/api'
import { AccountId32 } from '@polkadot/types/interfaces'
import { PalletSocietyPayoutRecord } from '@polkadot/types/lookup'
import { useEffect, useState } from 'react'
import { PayoutsList } from './components/PayoutsList'
import { useAccount } from '../../../account/AccountContext'
import { useConsts } from '../../../hooks/useConsts'
import { LoadingSpinner } from '../components/LoadingSpinner'
import { buildSocietyMembersArray, deriveMembersInfo } from '../helpers'

type PayoutsPageProps = {
api: ApiPromise | null
}

const PayoutsPage = ({ api }: PayoutsPageProps): JSX.Element => {
const { activeAccount } = useAccount()
const [members, setMembers] = useState<ExtendedSocietyMember[] | null>(null)
const [trigger, setTrigger] = useState(false)
const society = api?.derive.society

const { graceStrikes } = useConsts()

const handleUpdate = () => {
setTrigger((prev) => !prev)
}

let defender: AccountId32
let skeptic: AccountId32

useEffect(() => {
const fetchData = async () => {
if (!api || !society) return

const info: ExtendedDeriveSociety = await society.info()
const defending = await api.query.society.defending()

defender = defending.unwrap()[0]
skeptic = defending.unwrap()[1]

info.defender = defender
info.skeptic = skeptic

const responseMembers: ExtendedDeriveSociety[] = await deriveMembersInfo(api)
const membersArray = buildSocietyMembersArray(responseMembers, info, graceStrikes)

const extendedMembers: ExtendedSocietyMember[] = await Promise.all(
membersArray.map(async (member) => {
const payoutsCodec = (await api.query.society.payouts(member.accountId)) as PalletSocietyPayoutRecord

const payouts = payoutsCodec.payouts.toArray()
const highestBlockNumber = payouts.length > 0 ? Math.max(...payouts.map((payout) => payout[0].toNumber())) : 0
const totalBalance = payouts.reduce((sum: number, payout) => sum + payout[1].toBn().toNumber(), 0)

return {
...member,
extendedPayouts: {
block: highestBlockNumber,
pending: totalBalance,
paid: payoutsCodec.paid
}
}
})
)

const sortedMembers = extendedMembers.sort((a, b) => {
const blockA = a.extendedPayouts.block === 0 ? Number.MAX_SAFE_INTEGER : a.extendedPayouts.block
const blockB = b.extendedPayouts.block === 0 ? Number.MAX_SAFE_INTEGER : b.extendedPayouts.block

const blockComparison = blockA - blockB
if (blockComparison !== 0) {
return blockComparison
}

const pendingComparison = b.extendedPayouts.pending - a.extendedPayouts.pending
if (pendingComparison !== 0) {
return pendingComparison
}

return b.extendedPayouts.paid.toBn().cmp(a.extendedPayouts.paid.toBn())
})

setMembers(sortedMembers)
}

fetchData()
}, [trigger, society, api, graceStrikes])

if (members === null) return <LoadingSpinner />

return <PayoutsList api={api!} members={members} activeAccount={activeAccount} handleUpdate={handleUpdate} />
}

export { PayoutsPage }
5 changes: 5 additions & 0 deletions src/pages/explore/components/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ const NavigationBar = ({ totals }: { totals: Totals }) => {
Journey
</Nav.Link>
</StyledNavItem>
<StyledNavItem>
<Nav.Link as={LinkWithQuery} to="/explore/payouts">
Payouts
</Nav.Link>
</StyledNavItem>
</StyledNav>
{showSubNav && (
<StyledNav defaultActiveKey="/explore/poi/examples" className="py-2 mb-4">
Expand Down
4 changes: 4 additions & 0 deletions src/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ type ExtendedDeriveSociety = {
rank: u32
} & DeriveSociety

interface ExtendedSocietyMember extends SocietyMember {
extendedPayouts: any
}

interface SocietyMemberDetails {
accountId: AccountId
index?: string
Expand Down
Loading
Loading