Skip to content

Commit

Permalink
Merge pull request #94 from TalismanSociety/feat/manage-multiple-nomp…
Browse files Browse the repository at this point in the history
…ools

feat: support managing multiple nom pools
  • Loading branch information
chrisling-dev authored Oct 30, 2024
2 parents 163fc1b + 3b859c7 commit d3f2b86
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 267 deletions.
2 changes: 0 additions & 2 deletions apps/multisig/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import router from './routes'
import { AccountWatcher } from '@domains/auth'
import { OffchainDataWatcher } from '@domains/offchain-data/offchain-watcher'
import { ActiveMultisigWatcher } from './domains/multisig'
import { NomPoolsWatcher } from './domains/staking'
import { ValidatorsWatcher } from './domains/staking/ValidatorsWatcher'
import ConstsWatcher from './domains/chains/ConstsWatcher'
import { Toaster as NewToaster } from '@components/ui/toaster'
Expand Down Expand Up @@ -54,7 +53,6 @@ const App: React.FC = () => {
<ExtensionWatcher />
<AccountWatcher />
<OffchainDataWatcher />
<NomPoolsWatcher />
<ValidatorsWatcher />
<ActiveMultisigWatcher />
<ConstsWatcher />
Expand Down
2 changes: 1 addition & 1 deletion apps/multisig/src/components/ui/dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-[12px] border bg-gray-900 p-[4px] text-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'z-50 min-w-[8rem] overflow-hidden rounded-[12px] border border-gray-700 bg-gray-900 p-[4px] text-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
Expand Down
130 changes: 130 additions & 0 deletions apps/multisig/src/domains/nomination-pools/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { atom, atomFamily, selectorFamily } from 'recoil'
import { pjsApiSelector } from '../chains/pjs-api'
import { Address } from '../../util/addresses'
import { ApiPromise } from '@polkadot/api'
import type { StorageKey, u32 } from '@polkadot/types'
import BigNumber from 'bignumber.js'
import { BN, bnToU8a, stringToU8a, u8aConcat } from '@polkadot/util'

export type BondedPool = {
id: number
memberCounter: number
points: bigint
roles: {
depositor: Address
root: Address
nominator: Address
bouncer: Address
}
stash: Address
reward: Address
state: 'Open' | 'Destroying' | 'Blocked'
metadata?: string
}

type BondedPoolRaw = {
memberCounter: string
points: string
roles: {
depositor: string
root: string
nominator: string
bouncer: string
}
state: 'Open' | 'Destroying' | 'Blocked'
}

export const EmptyH256 = new Uint8Array(32)
export const ModPrefix = stringToU8a('modl')
export const U32Opts = { bitLength: 32, isLe: true }

const getPoolId = (raw: StorageKey<[u32]>): number => {
const idRaw = raw.toHuman()
if (Array.isArray(idRaw) && idRaw[0]) {
return +idRaw[0]
}
return 0
}

const createAccount = (api: ApiPromise, poolId: BigNumber, index: number): string => {
return api.registry
.createType(
'AccountId32',
u8aConcat(
ModPrefix,
api.consts.nominationPools.palletId.toU8a() ?? new Uint8Array(0),
new Uint8Array([index]),
bnToU8a(new BN(poolId.toString()), U32Opts),
EmptyH256
)
)
.toString()
}

const createAccounts = (api: ApiPromise, poolId: number) => {
const poolIdBigNumber = new BigNumber(poolId)
return {
stash: createAccount(api, poolIdBigNumber, 0),
reward: createAccount(api, poolIdBigNumber, 1),
}
}

export const bondedPoolsAtom = atomFamily({
key: 'bondedPoolsAtom',
default: selectorFamily({
key: 'bondedPoolsAtomDefault',
get:
(chainGenesisHash: string) =>
async ({ get }) => {
const api = get(pjsApiSelector(chainGenesisHash))

const pools = await api.query.nominationPools.bondedPools.entries()
const ids = pools.map(([key]) => getPoolId(key))
const metadata = await api.query.nominationPools.metadata.multi(ids)
const metadataMulti = Object.fromEntries(metadata.map((m, i) => [ids[i], String(m.toHuman())]))

return pools
.map(([key, value]): BondedPool | null => {
const id = getPoolId(key)

const bondedPoolRaw = value.toHuman() as BondedPoolRaw
const depositor = Address.fromSs58(bondedPoolRaw.roles.depositor)
const root = Address.fromSs58(bondedPoolRaw.roles.root)
const nominator = Address.fromSs58(bondedPoolRaw.roles.nominator)
const bouncer = Address.fromSs58(bondedPoolRaw.roles.bouncer)

if (id === undefined || !depositor || !root || !nominator || !bouncer) return null

const { stash: stashString, reward: rewardString } = createAccounts(api, id)
const stash = Address.fromSs58(stashString)
const reward = Address.fromSs58(rewardString)
if (!stash || !reward) return null

const metadata = metadataMulti[id]

const pool = {
id,
memberCounter: +bondedPoolRaw.memberCounter.replaceAll(',', ''),
points: BigInt(bondedPoolRaw.points.replaceAll(',', '')),
roles: {
depositor,
root,
nominator,
bouncer,
},
stash,
reward,
state: bondedPoolRaw.state,
metadata: metadata === '' ? undefined : metadata,
}
return pool
})
.filter((pool): pool is BondedPool => pool !== null)
},
}),
})

export const selectedPoolIdAtom = atom<number | undefined>({
key: 'selectedPoolIdAtom',
default: undefined,
})
144 changes: 0 additions & 144 deletions apps/multisig/src/domains/staking/NomPoolsWatcher.tsx

This file was deleted.

1 change: 0 additions & 1 deletion apps/multisig/src/domains/staking/index.tsx

This file was deleted.

38 changes: 0 additions & 38 deletions apps/multisig/src/domains/staking/useNomPool.ts

This file was deleted.

26 changes: 26 additions & 0 deletions apps/multisig/src/domains/staking/useNomPoolsOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useRecoilValueLoadable } from 'recoil'
import { Address } from '@util/addresses'
import { useMemo } from 'react'
import { Chain } from '@domains/chains'
import { bondedPoolsAtom } from '@domains/nomination-pools'

/**
* Returns a nom pool which the given address has a role for.
* Returns undefined while loading, null if the address has no role in any pool.
*/
export const useNomPoolsOf = (address: Address, chain: Chain) => {
const bondedPools = useRecoilValueLoadable(bondedPoolsAtom(chain.genesisHash))

return useMemo(() => {
if (bondedPools.state !== 'hasValue') return undefined
return bondedPools.contents.filter(pool => {
if (!pool) return false
return (
pool.roles.root.isEqual(address) ||
pool.roles.nominator.isEqual(address) ||
pool.roles.depositor.isEqual(address) ||
pool.roles.bouncer.isEqual(address)
)
})
}, [address, bondedPools])
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useMemo } from 'react'
import { useSelectedMultisig } from '../multisig'
import { BondedPool } from './NomPoolsWatcher'
import { useApi } from '../chains/pjs-api'
import { Address } from '../../util/addresses'
import { BondedPool } from '@domains/nomination-pools'

export const useNominateTransaction = (
address: Address,
Expand Down
Loading

0 comments on commit d3f2b86

Please sign in to comment.