e5f41731ae
* bootstrap next app + add overview page * fix AssetList type * fix up nav stuff * Refactor Nav component and add network components pages * Refactor WorldMap component and update TelegramIcon, GitHubIcon, NymVpnIcon, DiscordIcon, and TwitterIcon components * add service providers page * mixnodes page * delegations page + use material react table for all tables * nodes map page * Refactor StyledLink component and remove unnecessary console.log statements * Refactor ESLint configuration, remove unused dependencies, and update component imports * update deps * Refactor imports and update dependencies * fix dark mode * build single mixnode page * build single gateway page * Refactor handleOnDelegate function to use useCallback in mixnodes page.tsx * Add defaults for constants --------- Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
253 lines
6.2 KiB
TypeScript
253 lines
6.2 KiB
TypeScript
'use client'
|
|
|
|
import React, {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useMemo,
|
|
useState,
|
|
} from 'react'
|
|
import {
|
|
Delegation,
|
|
PendingEpochEvent,
|
|
PendingEpochEventKind,
|
|
} from '@nymproject/contract-clients/Mixnet.types'
|
|
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
|
|
import { useWalletContext } from './wallet'
|
|
import { useMainContext } from './main'
|
|
|
|
const fee = { gas: '1000000', amount: [{ amount: '1000000', denom: 'unym' }] }
|
|
|
|
export type PendingEvent = ReturnType<typeof getEventsByAddress>
|
|
|
|
export type DelegationWithRewards = Delegation & {
|
|
rewards: string
|
|
identityKey: string
|
|
pending: PendingEvent
|
|
}
|
|
|
|
const getEventsByAddress = (kind: PendingEpochEventKind, address: String) => {
|
|
if ('delegate' in kind && kind.delegate.owner === address) {
|
|
return {
|
|
kind: 'delegate' as const,
|
|
mixId: kind.delegate.mix_id,
|
|
amount: kind.delegate.amount,
|
|
}
|
|
}
|
|
|
|
if ('undelegate' in kind && kind.undelegate.owner === address) {
|
|
return {
|
|
kind: 'undelegate' as const,
|
|
mixId: kind.undelegate.mix_id,
|
|
}
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
interface DelegationsState {
|
|
delegations?: DelegationWithRewards[]
|
|
handleGetDelegations: () => Promise<void>
|
|
handleDelegate: (
|
|
mixId: number,
|
|
amount: string
|
|
) => Promise<ExecuteResult | undefined>
|
|
handleUndelegate: (mixId: number) => Promise<ExecuteResult | undefined>
|
|
}
|
|
|
|
export const DelegationsContext = createContext<DelegationsState>({
|
|
delegations: undefined,
|
|
handleGetDelegations: async () => {
|
|
throw new Error('Please connect your wallet')
|
|
},
|
|
handleDelegate: async () => {
|
|
throw new Error('Please connect your wallet')
|
|
},
|
|
handleUndelegate: async () => {
|
|
throw new Error('Please connect your wallet')
|
|
},
|
|
})
|
|
|
|
export const DelegationsProvider = ({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode
|
|
}) => {
|
|
const [delegations, setDelegations] = useState<DelegationWithRewards[]>()
|
|
const { address, nymQueryClient, nymClient } = useWalletContext()
|
|
const { fetchMixnodes } = useMainContext()
|
|
|
|
const handleGetPendingEvents = async () => {
|
|
if (!nymQueryClient) {
|
|
return undefined
|
|
}
|
|
|
|
if (!address) {
|
|
return undefined
|
|
}
|
|
|
|
const response = await nymQueryClient.getPendingEpochEvents({})
|
|
const pendingEvents: PendingEvent[] = []
|
|
|
|
response.events.forEach((e: PendingEpochEvent) => {
|
|
const event = getEventsByAddress(e.event.kind, address)
|
|
if (event) {
|
|
pendingEvents.push(event)
|
|
}
|
|
})
|
|
|
|
return pendingEvents
|
|
}
|
|
|
|
const handleGetDelegationRewards = async (mixId: number) => {
|
|
if (!nymQueryClient) {
|
|
return undefined
|
|
}
|
|
|
|
if (!address) {
|
|
return undefined
|
|
}
|
|
|
|
const response = await nymQueryClient.getPendingDelegatorReward({
|
|
address,
|
|
mixId,
|
|
})
|
|
|
|
return response
|
|
}
|
|
|
|
const handleGetDelegations = useCallback(async () => {
|
|
if (!nymQueryClient) {
|
|
setDelegations(undefined)
|
|
return undefined
|
|
}
|
|
|
|
if (!address) {
|
|
setDelegations(undefined)
|
|
return undefined
|
|
}
|
|
|
|
// Get all mixnodes - Required to get the identity key for each delegation
|
|
const mixnodes = await fetchMixnodes()
|
|
|
|
// Get delegations
|
|
const delegationsResponse = await nymQueryClient.getDelegatorDelegations({
|
|
delegator: address,
|
|
})
|
|
|
|
// Get rewards for each delegation
|
|
const rewardsResponse = await Promise.all(
|
|
delegationsResponse.delegations.map((d: Delegation) =>
|
|
handleGetDelegationRewards(d.mix_id)
|
|
)
|
|
)
|
|
|
|
// Get all pending events
|
|
const pendingEvents = await handleGetPendingEvents()
|
|
|
|
const delegationsWithRewards: DelegationWithRewards[] = []
|
|
|
|
// Merge delegations with rewards and pending events
|
|
delegationsResponse.delegations.forEach((d: Delegation, index: number) => {
|
|
delegationsWithRewards.push({
|
|
...d,
|
|
pending: pendingEvents?.find((e: PendingEvent) =>
|
|
e?.mixId === d.mix_id ? e.kind : undefined
|
|
),
|
|
identityKey:
|
|
mixnodes?.find((m) => m.mix_id === d.mix_id)?.mix_node.identity_key ||
|
|
'',
|
|
rewards: rewardsResponse[index]?.amount_earned_detailed || '0',
|
|
})
|
|
})
|
|
|
|
// Add pending events that are not in the delegations list
|
|
pendingEvents?.forEach((e) => {
|
|
if (
|
|
e &&
|
|
!delegationsWithRewards.find(
|
|
(d: DelegationWithRewards) => d.mix_id === e.mixId
|
|
)
|
|
) {
|
|
delegationsWithRewards.push({
|
|
mix_id: e.mixId,
|
|
height: 0,
|
|
cumulative_reward_ratio: '0',
|
|
owner: address,
|
|
amount: {
|
|
amount: '0',
|
|
denom: 'unym',
|
|
},
|
|
rewards: '0',
|
|
identityKey:
|
|
mixnodes?.find((m) => m.mix_id === e.mixId)?.mix_node
|
|
.identity_key || '',
|
|
pending: e,
|
|
})
|
|
}
|
|
})
|
|
|
|
setDelegations(delegationsWithRewards)
|
|
|
|
return undefined
|
|
}, [address, nymQueryClient])
|
|
|
|
const handleDelegate = async (mixId: number, amount: string) => {
|
|
if (!address) {
|
|
throw new Error('Please connect your wallet')
|
|
}
|
|
|
|
const amountToDelegate = (Number(amount) * 1000000).toString()
|
|
const uNymFunds = [{ amount: amountToDelegate, denom: 'unym' }]
|
|
try {
|
|
const tx = await nymClient?.delegateToMixnode(
|
|
{ mixId },
|
|
fee,
|
|
'Delegation from Nym Explorer',
|
|
uNymFunds
|
|
)
|
|
|
|
return tx as unknown as ExecuteResult
|
|
} catch (e) {
|
|
console.error('Failed to delegate to mixnode', e)
|
|
throw e
|
|
}
|
|
}
|
|
|
|
const handleUndelegate = async (mixId: number) => {
|
|
const tx = await nymClient?.undelegateFromMixnode(
|
|
{ mixId },
|
|
fee,
|
|
'Undelegation from Nym Explorer'
|
|
)
|
|
|
|
return tx as unknown as ExecuteResult
|
|
}
|
|
|
|
const contextValue: DelegationsState = useMemo(
|
|
() => ({
|
|
delegations,
|
|
handleGetDelegations,
|
|
handleDelegate,
|
|
handleUndelegate,
|
|
}),
|
|
[delegations, handleGetDelegations]
|
|
)
|
|
|
|
return (
|
|
<DelegationsContext.Provider value={contextValue}>
|
|
{children}
|
|
</DelegationsContext.Provider>
|
|
)
|
|
}
|
|
|
|
export const useDelegationsContext = () => {
|
|
const context = useContext(DelegationsContext)
|
|
if (!context) {
|
|
throw new Error(
|
|
'useDelegationsContext must be used within a DelegationsProvider'
|
|
)
|
|
}
|
|
return context
|
|
}
|