withdraw_unbonded added and warning message for validators
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
9ab91c801d
commit
0d2c700e1a
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghost-lite",
|
"name": "ghost-lite",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"description": "Web application for Ghost and Casper chain.",
|
"description": "Web application for Ghost and Casper chain.",
|
||||||
"author": "Uncle f4ts0 <f4ts0@ghostchain.io>",
|
"author": "Uncle f4ts0 <f4ts0@ghostchain.io>",
|
||||||
"maintainers": [
|
"maintainers": [
|
||||||
|
@ -24,11 +24,13 @@ import {
|
|||||||
useNominations,
|
useNominations,
|
||||||
useLedger,
|
useLedger,
|
||||||
usePayee,
|
usePayee,
|
||||||
|
useSlasingSpans,
|
||||||
useEraRewardPoints,
|
useEraRewardPoints,
|
||||||
useCurrentValidators,
|
useCurrentValidators,
|
||||||
useValidatorsOverview,
|
useValidatorsOverview,
|
||||||
useBondedAddress,
|
useBondedAddress,
|
||||||
useNominateCalldata,
|
useNominateCalldata,
|
||||||
|
useWithdrawCalldata,
|
||||||
useSystemAccount,
|
useSystemAccount,
|
||||||
useBondCalldata,
|
useBondCalldata,
|
||||||
useUnbondCalldata,
|
useUnbondCalldata,
|
||||||
@ -205,6 +207,7 @@ export const Nominations = () => {
|
|||||||
const nominations = useNominations({ address: account?.address })
|
const nominations = useNominations({ address: account?.address })
|
||||||
const ledger = useLedger({ address: account?.address })
|
const ledger = useLedger({ address: account?.address })
|
||||||
const payee = usePayee({ address: account?.address })
|
const payee = usePayee({ address: account?.address })
|
||||||
|
const slashingSpans = useSlasingSpans({ address: account?.address })
|
||||||
const eraRewardPoints = useEraRewardPoints({ eraIndex: eraIndex?.index })
|
const eraRewardPoints = useEraRewardPoints({ eraIndex: eraIndex?.index })
|
||||||
const currentValidators = useCurrentValidators({ address: interestingValidator })
|
const currentValidators = useCurrentValidators({ address: interestingValidator })
|
||||||
const validatorOverview = useValidatorsOverview({ eraIndex: eraIndex?.index, address: interestingValidator })
|
const validatorOverview = useValidatorsOverview({ eraIndex: eraIndex?.index, address: interestingValidator })
|
||||||
@ -228,6 +231,7 @@ export const Nominations = () => {
|
|||||||
|
|
||||||
const bondedAddress = useBondedAddress({ address: account?.address })
|
const bondedAddress = useBondedAddress({ address: account?.address })
|
||||||
|
|
||||||
|
const withdrawCalldata = useWithdrawCalldata({ numberOfSpans: slashingSpans?.length ?? 0 })
|
||||||
const nominateCalldata = useNominateCalldata(checkedAddresses)
|
const nominateCalldata = useNominateCalldata(checkedAddresses)
|
||||||
const bondCalldata = useBondCalldata(
|
const bondCalldata = useBondCalldata(
|
||||||
convertedAmount > 0n ? convertedAmount : undefined
|
convertedAmount > 0n ? convertedAmount : undefined
|
||||||
@ -274,7 +278,7 @@ export const Nominations = () => {
|
|||||||
}, [ledger, eraIndex])
|
}, [ledger, eraIndex])
|
||||||
|
|
||||||
const latestWithdrawEra = useMemo(() => {
|
const latestWithdrawEra = useMemo(() => {
|
||||||
return Math.max(ledger?.unlocking.map((el: Unlocking) => el.era - (eraIndex?.index ?? 0)) ?? [])
|
return Math.max(ledger?.unlocking.map((el: Unlocking) => el.era - (eraIndex?.index ?? 0)), [])
|
||||||
}, [eraIndex, ledger])
|
}, [eraIndex, ledger])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -449,6 +453,55 @@ export const Nominations = () => {
|
|||||||
}
|
}
|
||||||
}, [account, chainId, clientFull, nominateCalldata, provider])
|
}, [account, chainId, clientFull, nominateCalldata, provider])
|
||||||
|
|
||||||
|
const handleOnWithdraw = useCallback(async () => {
|
||||||
|
setIsSubmittingTransaction(true)
|
||||||
|
setTransactionStatus(undefined)
|
||||||
|
setError(undefined)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tx = await provider!.createTx(
|
||||||
|
chainId ?? "",
|
||||||
|
account ? toHex(ss58Decode(account.address)[0]) : "",
|
||||||
|
withdrawCalldata ?? ""
|
||||||
|
)
|
||||||
|
await lastValueFrom(
|
||||||
|
submitTransaction$(clientFull, tx)
|
||||||
|
.pipe(
|
||||||
|
tap(({ txEvent }) => {
|
||||||
|
let status: string = ""
|
||||||
|
switch (txEvent.type) {
|
||||||
|
case "broadcasted":
|
||||||
|
status = "broadcasted to available peers"
|
||||||
|
break
|
||||||
|
case "txBestBlocksState":
|
||||||
|
status = `included in block #${txEvent.block.number}`
|
||||||
|
break
|
||||||
|
case "finalized":
|
||||||
|
status = `finalized at block #${txEvent.block.number}`
|
||||||
|
break
|
||||||
|
case "throttled":
|
||||||
|
status = "throttling to detect chain head..."
|
||||||
|
break
|
||||||
|
}
|
||||||
|
setTransactionStatus({
|
||||||
|
status,
|
||||||
|
hash: txEvent.block?.hash,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
const currentError = { type: "error", error: err.message }
|
||||||
|
setError(currentError)
|
||||||
|
}
|
||||||
|
console.error(err)
|
||||||
|
} finally {
|
||||||
|
setAmount("")
|
||||||
|
setIsSubmittingTransaction(false)
|
||||||
|
}
|
||||||
|
}, [account, chainId, clientFull, withdrawCalldata, provider])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-8 justify-center self-center rounded py-2">
|
<div className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-8 justify-center self-center rounded py-2">
|
||||||
<div className="bg-muted p-4 rounded flex flex-col gap-4">
|
<div className="bg-muted p-4 rounded flex flex-col gap-4">
|
||||||
@ -509,18 +562,18 @@ export const Nominations = () => {
|
|||||||
className="sm:w-[300px] w-full"
|
className="sm:w-[300px] w-full"
|
||||||
placeholder={applyDecimals(readyToWithdraw, tokenDecimals, tokenSymbol)}
|
placeholder={applyDecimals(readyToWithdraw, tokenDecimals, tokenSymbol)}
|
||||||
/>} />
|
/>} />
|
||||||
<Row title={`After ${latestWithdrawEra} era${latestWithdrawEra > 1 ? "s" : ""}`} element={<Input
|
{latestWithdrawEra > 0 && (<Row title={`After ${latestWithdrawEra} era${latestWithdrawEra > 1 ? "s" : ""}`} element={<Input
|
||||||
readOnly
|
readOnly
|
||||||
aria-label="Pending"
|
aria-label="Pending"
|
||||||
type="text"
|
type="text"
|
||||||
className="sm:w-[300px] w-full"
|
className="sm:w-[300px] w-full"
|
||||||
placeholder={applyDecimals(waitingForWithdraw, tokenDecimals, tokenSymbol)}
|
placeholder={applyDecimals(waitingForWithdraw, tokenDecimals, tokenSymbol)}
|
||||||
/>} />
|
/>} />)}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="text-sm my-4 w-full"
|
className="text-sm my-4 w-full"
|
||||||
onClick={() => alert("Not implemented yet")}
|
onClick={handleOnWithdraw}
|
||||||
disabled={isSubmittingTransaction || readyToWithdraw === 0n}
|
disabled={isSubmittingTransaction || readyToWithdraw === 0n}
|
||||||
>
|
>
|
||||||
<PiggyBank className="w-4 h-4 inline-block mr-2" />
|
<PiggyBank className="w-4 h-4 inline-block mr-2" />
|
||||||
@ -547,7 +600,7 @@ export const Nominations = () => {
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="text-sm p-4 w-full"
|
className="text-sm p-4 w-full"
|
||||||
onClick={handleOnNominate}
|
onClick={handleOnNominate}
|
||||||
disabled={isSubmittingTransaction || convertedAmount === 0n}
|
disabled={isSubmittingTransaction || checkedAddresses.length === 0}
|
||||||
>
|
>
|
||||||
<Users className="w-4 h-4 inline-block mr-2" />
|
<Users className="w-4 h-4 inline-block mr-2" />
|
||||||
Nominate
|
Nominate
|
||||||
@ -576,6 +629,11 @@ export const Nominations = () => {
|
|||||||
Unbond
|
Unbond
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
{eraRewardPoints?.individual.some((indivial: RewardPoints) => indivial?.at(0) === account?.address) && (
|
||||||
|
<p className="text-xs text-destructive">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{!error && transactionStatus && (
|
{!error && transactionStatus && (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-xs text-accent overflow-hidden whitespace-nowrap text-ellipsis">
|
<p className="text-xs text-accent overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
|
@ -16,3 +16,4 @@ export * from "./useBondedAddress"
|
|||||||
export * from "./useNominations"
|
export * from "./useNominations"
|
||||||
export * from "./useLedger"
|
export * from "./useLedger"
|
||||||
export * from "./usePayee"
|
export * from "./usePayee"
|
||||||
|
export * from "./useSlasingSpans"
|
||||||
|
@ -117,3 +117,25 @@ export const useNominateCalldata = (addresses: string[]) => {
|
|||||||
)
|
)
|
||||||
return calldata
|
return calldata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useWithdrawCalldata = (numberOfSpans: number) => {
|
||||||
|
const { client, chainId } = useUnstableProvider()
|
||||||
|
const metadata = useMetadata()
|
||||||
|
const { data: calldata } = useSWR(
|
||||||
|
client && chainId && numberOfSpans && metadata
|
||||||
|
? ["withdraw_unbonded", client, chainId, metadata, numberOfSpans]
|
||||||
|
: null,
|
||||||
|
([_, client, _chainId, metadata, numberOfSpans]) => {
|
||||||
|
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||||
|
const { codec, location } = builder.buildCall("Staking", "withdraw_unbonded")
|
||||||
|
|
||||||
|
return toHex(
|
||||||
|
mergeUint8(
|
||||||
|
new Uint8Array(location),
|
||||||
|
codec.enc({ num_slashing_spans: numberOfSpans }),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return calldata
|
||||||
|
}
|
||||||
|
47
src/hooks/useSlasingSpans.tsx
Normal file
47
src/hooks/useSlasingSpans.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import useSWRSubscription from "swr/subscription"
|
||||||
|
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
|
||||||
|
import type { BlockInfo } from "@polkadot-api/observable-client"
|
||||||
|
import { distinct, filter, map, mergeMap } from "rxjs"
|
||||||
|
|
||||||
|
import { useUnstableProvider } from "./useUnstableProvider"
|
||||||
|
import { useMetadata } from "./useMetadata"
|
||||||
|
|
||||||
|
export type Payee = {
|
||||||
|
type: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSlasingSpans = ({ address }: { address: string | undefined }) => {
|
||||||
|
const { chainHead$, chainId } = useUnstableProvider()
|
||||||
|
const metadata = useMetadata()
|
||||||
|
const { data: slasingSpans } = useSWRSubscription(
|
||||||
|
chainHead$ && address && chainId && metadata
|
||||||
|
? ["slasingSpans", chainHead$, address, chainId, metadata]
|
||||||
|
: null,
|
||||||
|
([_, chainHead$, address, chainId, metadata], { next }) => {
|
||||||
|
const { finalized$, storage$ } = chainHead$
|
||||||
|
const subscription = finalized$.pipe(
|
||||||
|
filter(Boolean),
|
||||||
|
mergeMap((blockInfo: BlockInfo) => {
|
||||||
|
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||||
|
const slasingSpans = builder.buildStorage("Staking", "SlashingSpans")
|
||||||
|
return storage$(blockInfo?.hash, "value", () =>
|
||||||
|
slasingSpans?.keys.enc(address)
|
||||||
|
).pipe(
|
||||||
|
filter(Boolean),
|
||||||
|
distinct(),
|
||||||
|
map((value: string) => slasingSpans?.value.dec(value))
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next(slasingSpans) {
|
||||||
|
next(null, slasingSpans)
|
||||||
|
},
|
||||||
|
error: next,
|
||||||
|
})
|
||||||
|
return () => subscription.unsubscribe()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return slasingSpans
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user