make reward destinations more flexible for the user

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2025-08-25 19:43:11 +03:00
parent 0d2c700e1a
commit 7822556988
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
3 changed files with 118 additions and 23 deletions

View File

@ -1,6 +1,6 @@
{
"name": "ghost-lite",
"version": "0.1.5",
"version": "0.1.6",
"description": "Web application for Ghost and Casper chain.",
"author": "Uncle f4ts0 <f4ts0@ghostchain.io>",
"maintainers": [

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState, useMemo, useCallback } from "react"
import { Nut, NutOff, Users, PiggyBank } from "lucide-react"
import { Nut, NutOff, Users, PiggyBank, RefreshCcw } from "lucide-react"
import { ss58Decode } from "@polkadot-labs/hdkd-helpers"
import { toHex } from "@polkadot-api/utils"
@ -7,6 +7,14 @@ import { toHex } from "@polkadot-api/utils"
import { lastValueFrom, tap } from "rxjs"
import { submitTransaction$ } from "../api"
import {
Select,
SelectValue,
SelectTrigger,
SelectContent,
SelectGroup,
SelectItem,
} from "../components/ui/select"
import {
Accordion,
AccordionContent,
@ -37,6 +45,7 @@ import {
useUnstableProvider,
RewardPoints,
Unlocking,
RewardDestination,
} from "../hooks"
import {
@ -62,7 +71,6 @@ interface ItemProps {
nominated: boolean
checkedAddresses: string[]
setIsCheckedAddresses: React.Dispatch<React.SetStateAction<string[]>>
}
const Item: React.FC<ItemProps> = (props) => {
@ -191,6 +199,8 @@ export const Nominations = () => {
const [interestingValidator, setInterestingValidator] = useState<string | undefined>(undefined)
const [amount, setAmount] = useState<string>("")
const [destinationReceiver, setDestinationReceiver] = useState<string>("")
const [expectedPayee, setExpectedPayee] = useState<string | undefined>(undefined)
const {
provider,
@ -221,6 +231,19 @@ export const Nominations = () => {
: undefined
})
const destinationReceiverIsValid = useMemo(() => {
try {
const [, prefix] = ss58Decode(destinationReceiver)
if (prefix !== 1995 && prefix !== 1996) {
throw new Error("bad prefix")
}
return true
} catch {
return false
}
}, [destinationReceiver])
const convertedAmount = useMemo(() => {
try {
return BigInt(Number(amount) * Math.pow(10, tokenDecimals))
@ -240,24 +263,24 @@ export const Nominations = () => {
convertedAmount > 0n ? convertedAmount : undefined
)
const payeeDescription = useMemo(() => {
const payeeDescription = (destination: string | undefined) => {
let description = "Unknown reward destination"
switch (payee?.type) {
switch (destination) {
case "Staked":
description = "Re-stake rewards"
description = "Re-stake upcoming rewards"
break;
case "Stash":
description = "Withdraw rewards to free"
break;
case "Account":
description = `Rewards to => ${payee?.value}`
description = `Rewards to account`
break;
case "None":
description = "Refuse to receive rewards"
break;
}
return description
}, [payee])
}
const readyToWithdraw = useMemo(() => {
return ledger?.unlocking.reduce((acc: bigint, item: Unlocking) => {
@ -285,6 +308,10 @@ export const Nominations = () => {
setIsCheckedAddresses([])
}, [account])
useEffect(() => {
if (!expectedPayee) setExpectedPayee(payee?.type)
}, [expectedPayee, setExpectedPayee, payee])
useEffect(() => {
if (checkedAddresses.length === 0 && nominations) {
setIsCheckedAddresses(nominations?.targets ?? [])
@ -531,13 +558,53 @@ export const Nominations = () => {
</div>
</AccordionTrigger>
<AccordionContent className="flex flex-col gap-2 pl-8">
<Row title="Destination" element={<Input
readOnly
aria-label="Destination"
type="text"
className="sm:w-[300px] w-full"
placeholder={payeeDescription}
/>} />
{payee && (<Row title="Destination" element={<Select
value={expectedPayee}
onValueChange={(payeeType) => setExpectedPayee(payeeType)}
>
<SelectTrigger
className={"text-muted-foreground sm:w-[300px] w-full"}
data-testid="destination-select"
>
<SelectValue placeholder="Select Destination" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{Object.keys(RewardDestination()).map((destinationType, index) => (
<SelectItem
key={index}
data-testid={`destination-${destinationType}`}
value={destinationType}
>
{payeeDescription(destinationType)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
} />)}
{expectedPayee !== payee?.type && expectedPayee === "Account" && (
<Row title="Receiver" element={<Input
aria-label="Destination Account"
type="text"
className="sm:w-[300px] w-full"
placeholder="Input destination account"
onChange={e => setDestinationReceiver(e.target.value)}
value={destinationReceiver}
/>} />
)}
{expectedPayee !== payee?.type && (
<Button
type="button"
variant="secondary"
className="text-sm my-4 w-full"
onClick={() => alert("not ready yet")}
disabled={isSubmittingTransaction || !destinationReceiverIsValid}
>
<RefreshCcw className="w-4 h-4 inline-block mr-2" />
Change Destination
</Button>
)}
<Row title="Total Bond" element={<Input
readOnly
aria-label="Total Bond"

View File

@ -15,13 +15,41 @@ const AccountId = (value: SS58String) => Enum<
"Id"
>("Id", value)
const RewardDestination = () => Enum<
{
type: "Staked"
value: []
},
"Staked"
>("Staked", [])
export const RewardDestination = (account: SS58String | undefined) => {
return {
Staked: () => Enum<
{
type: "Staked"
value: []
},
"Staked"
>("Staked", []),
Stash: () => Enum<
{
type: "Stash"
value: []
},
"Stash"
>("Stash", []),
Account: (account: SS58String) => Enum<
{
type: "Account"
value: account
},
"Account"
>("Account", account),
None: () => Enum<
{
type: "None"
value: []
},
"None"
>("None", []),
}
}
export const useTransferCalldata = (destination: SS58String | undefined, amount: bigint | undefined) => {
const { client, chainId } = useUnstableProvider()
@ -86,7 +114,7 @@ export const useBondCalldata = (amount: bigint | undefined) => {
new Uint8Array(location),
codec.enc({
value: amount,
payee: RewardDestination(),
payee: RewardDestination.Staked(),
}),
),
)