import React, { useEffect, useState, useMemo } from "react" import { Nut, NutOff, Users, PiggyBank, RefreshCcw } from "lucide-react" import { ss58Decode } from "@polkadot-labs/hdkd-helpers" import { Select, SelectValue, SelectTrigger, SelectContent, SelectGroup, SelectItem, } from "../components/ui/select" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "../components/ui/accordion" import { Checkbox } from "../components/ui/checkbox" import { Input } from "../components/ui/input" import { Button } from "../components/ui/button" import { useChainSpecV1, useEraIndex, useNominations, useLedger, usePayee, useSlasingSpans, useEraRewardPoints, useCurrentValidators, useValidatorsOverview, useBondedAddress, useNominateCalldata, useWithdrawCalldata, usePayeeCalldata, useSystemAccount, useBondCalldata, useUnbondCalldata, useBondExtraCalldata, useUnstableProvider, useTransactionStatusProvider, RewardPoints, Unlocking, RewardDestination, } from "../hooks" import { AddressBookRecord } from "../types" import { Sender } from "./Accounts" import { Row } from "./Row" interface ItemProps { name: string | undefined address: string symbol: string points: number commission: number nominatorCount: number decimals: number totalStake: bigint ownStake: bigint blocked: boolean nominated: boolean checkedAddresses: string[] setIsCheckedAddresses: React.Dispatch> } const Item: React.FC = (props) => { const { name, address, points, commission, blocked, nominatorCount, totalStake, ownStake, decimals, symbol, nominated, checkedAddresses, setIsCheckedAddresses, } = props const [copied, setCopied] = useState(false) const convertToFixed = (value: bigint, decimals: number) => { if (!value || !decimals) { return parseFloat("0").toFixed(5) } const number = Number(value) / Math.pow(10, decimals) return parseFloat(number.toString()).toFixed(5) } const handleCopy = (textToCopy: string) => { if (!textToCopy) return navigator.clipboard.writeText(textToCopy).then(() => { setCopied(true) setTimeout(() => setCopied(false), 2000) }) } const handleOnCheck = () => { setIsCheckedAddresses((prev: string[]) => { if (address && prev.includes(address ?? "")) { return prev.filter(item => item !== address) } else { return [...prev, address] } }) } return (
{name ?? (address.slice(0, 15) + "..." + address.slice(-15))}
{points}

} /> } /> } />
} /> } />
handleCopy(address)} className="flex justify-center items-center cursor-pointer hover:text-foreground" > {copied ? "Address copid to clipboard" : address}
) } const HeaderInfo = ({ text, value }: { text: string, value: string }) => { return (
{text} {value}
) } export const Nominations = () => { const addressBook: AddressBookRecord[] = JSON.parse(localStorage.getItem('addressBook') ?? '[]') const [checkedAddresses, setIsCheckedAddresses] = useState([]) const [interestingValidator, setInterestingValidator] = useState(undefined) const [amount, setAmount] = useState("") const [destinationReceiver, setDestinationReceiver] = useState("") const [expectedPayee, setExpectedPayee] = useState("") const { setTransactionStatus, setError, isSubmittingTransaction, handleTransaction, renderStatus, } = useTransactionStatusProvider() const { account, accounts, connectAccount } = useUnstableProvider() const chainSpecV1 = useChainSpecV1() const eraIndex = useEraIndex() const nominations = useNominations({ address: account?.address }) const ledger = useLedger({ address: account?.address }) const payee = usePayee({ address: account?.address }) const slashingSpans = useSlasingSpans({ address: account?.address }) const eraRewardPoints = useEraRewardPoints({ eraIndex: eraIndex?.index }) const currentValidators = useCurrentValidators({ address: interestingValidator }) const validatorOverview = useValidatorsOverview({ eraIndex: eraIndex?.index, address: interestingValidator }) console.log(eraRewardPoints) const tokenDecimals: number = chainSpecV1?.properties?.tokenDecimals ?? 0 const tokenSymbol: string = chainSpecV1?.properties?.tokenSymbol ?? "" const ss58Format: number = chainSpecV1?.properties?.ss58Format ?? 1995 const senderAccount = useSystemAccount({ account: account ? account.address : undefined }) const destinationReceiverIsValid = useMemo(() => { try { const [, prefix] = ss58Decode(destinationReceiver) if (prefix !== ss58Format) { throw new Error("bad prefix") } return true } catch { return false } }, [destinationReceiver, ss58Format]) const convertedAmount = useMemo(() => { try { return BigInt(Number(amount) * Math.pow(10, tokenDecimals)) } catch { return 0n } }, [amount, tokenDecimals]) const bondedAddress = useBondedAddress({ address: account?.address }) const payeeCalldata = usePayeeCalldata(expectedPayee, destinationReceiver) const withdrawCalldata = useWithdrawCalldata((slashingSpans?.prior?.length ?? 0) + 1) const nominateCalldata = useNominateCalldata(checkedAddresses) const bondExtraCalldata = useBondExtraCalldata( convertedAmount > 0n ? convertedAmount : undefined ) const bondCalldata = useBondCalldata( convertedAmount > 0n ? convertedAmount : undefined ) const unbondCalldata = useUnbondCalldata( convertedAmount > 0n ? convertedAmount : undefined ) const payeeDescription = (destination: string | undefined) => { let description = "Unknown reward destination" switch (destination) { case "Staked": description = "Re-stake upcoming rewards" break case "Stash": description = "Withdraw rewards to free" break case "Account": description = `Rewards to account` break case "None": description = "Refuse to receive rewards" break } return description } const readyToWithdraw = useMemo(() => { return ledger?.unlocking.reduce((acc: bigint, item: Unlocking) => { if ((eraIndex?.index ?? 0) >= item.era) { return item.value } return 0n }, 0n) }, [ledger, eraIndex]) const waitingForWithdraw = useMemo(() => { return ledger?.unlocking.reduce((acc: bigint, item: Unlocking) => { if ((eraIndex?.index ?? 0) < item.era) { return item.value } return 0n }, 0n) }, [ledger, eraIndex]) const latestWithdrawEra = useMemo(() => { if (!ledger || ledger.unlocking.length === 0) { return 0 } const index = eraIndex?.index ?? 0 return Math.max(...ledger.unlocking.map((el: Unlocking) => el.era - index), 0) }, [eraIndex, ledger]) useEffect(() => { setIsCheckedAddresses([]) }, [account]) useEffect(() => { if (expectedPayee === "") setExpectedPayee(payee?.type ?? "") }, [expectedPayee, setExpectedPayee, payee]) useEffect(() => { if (checkedAddresses.length === 0 && nominations) { setIsCheckedAddresses(nominations?.targets ?? []) } }, [checkedAddresses, nominations]) useEffect(() => { if (amount !== "") { setError(undefined) setTransactionStatus(undefined) } }, [amount, setError, setTransactionStatus]) useEffect(() => { if (!isSubmittingTransaction) { setAmount("") } }, [isSubmittingTransaction]) useEffect(() => { setDestinationReceiver("") }, [expectedPayee]) const applyDecimals = (value = 0n, decimals = 0, tokenSymbol = "CSPR") => { if (!value) return `0 ${tokenSymbol}` const numberValue = Number(value) / Math.pow(10, decimals) const formatter = new Intl.NumberFormat("en-US", { minimumFractionDigits: 6, maximumFractionDigits: 6, }) return `${formatter.format(numberValue)} ${tokenSymbol}` } const handleOnBond = () => handleTransaction({ calldata: bondCalldata, txName: "bond" }) const handleOnExtraBond = () => handleTransaction({ calldata: bondExtraCalldata, txName: "bond_extra" }) const handleOnUnbond = () => handleTransaction({ calldata: unbondCalldata, txName: "unbond" }) const handleOnNominate = () => handleTransaction({ calldata: nominateCalldata, txName: "nominate" }) const handleOnWithdraw = () => handleTransaction({ calldata: withdrawCalldata, txName: "withdraw" }) const handleOnSetPayee = () => handleTransaction({ calldata: payeeCalldata, txName: "set_payee" }) return (
{nominations && ( )}

acc?.address ?? "") ?? []} senderAccount={senderAccount} senderBalance={applyDecimals(senderAccount?.data.free ?? 0n, tokenDecimals, tokenSymbol)} tokenDecimals={tokenDecimals} tokenSymbol={tokenSymbol} connectAccount={connectAccount} applyDecimals={applyDecimals} /> {ledger && ledger.total > 0n && (
Bonding Details
{payee && ( setExpectedPayee(payeeType)} > {Object.keys(RewardDestination()).map((destinationType, index) => ( {payeeDescription(destinationType)} ))} } />)} {payee?.type === "Account" && ( } /> )} {expectedPayee !== payee?.type && expectedPayee === "Account" && ( setDestinationReceiver(e.target.value)} value={destinationReceiver} />} /> )} {expectedPayee !== payee?.type && ( )} } /> } /> {ledger && ledger.unlocking.length > 0 && ( <>
} /> {latestWithdrawEra > 0 && ( 1 ? "s" : ""}`} element={} />)} )}
)} setAmount(e.target.value)} disabled={isSubmittingTransaction} aria-label="Transfer Amount" type="text" className="w-full" placeholder="Input amount to bond or unbond" />
{bondedAddress && ( )}
{eraRewardPoints?.individual.some((indivial: RewardPoints) => indivial?.at(0) === account?.address) && (

You are attempting to use the nomination functionality from the current validator account, which is mutually exclusive. Please switch accounts or proceed at your own risk.

)} {renderStatus()}
{eraRewardPoints && ( {eraRewardPoints?.individual.map((indivial: RewardPoints, idx: number) => ( record.address === indivial.at(0))?.name} address={indivial.at(0) as string ?? ""} points={indivial.at(1) as number ?? 0} commission={currentValidators?.commission ?? 0} blocked={currentValidators?.blocked ?? false} totalStake={validatorOverview?.total ?? 0n} ownStake={validatorOverview?.own ?? 0n} nominatorCount={validatorOverview?.nominator_count ?? 0} decimals={tokenDecimals ?? 18} symbol={tokenSymbol ?? "CSPR"} setIsCheckedAddresses={setIsCheckedAddresses} checkedAddresses={checkedAddresses} nominated={nominations?.targets.includes(indivial.at(0)) ?? false} /> ))} )} {!eraRewardPoints && (
Waiting for validators list...
)}
) }