Compare commits
No commits in common. "5b32bd5500129fbf41c5f791f552bd947bd6fb84" and "c8c1ef2739682a168d23c8d61a3883660c8873e3" have entirely different histories.
5b32bd5500
...
c8c1ef2739
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghost-lite",
|
"name": "ghost-lite",
|
||||||
"version": "0.1.1",
|
"version": "0.0.18",
|
||||||
"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": [
|
||||||
@ -29,7 +29,6 @@
|
|||||||
"@polkadot-api/view-builder": "~0.4.3",
|
"@polkadot-api/view-builder": "~0.4.3",
|
||||||
"@polkadot-labs/hdkd-helpers": "^0.0.11",
|
"@polkadot-labs/hdkd-helpers": "^0.0.11",
|
||||||
"@radix-ui/react-accordion": "^1.2.3",
|
"@radix-ui/react-accordion": "^1.2.3",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
|
||||||
"@radix-ui/react-select": "^2.1.6",
|
"@radix-ui/react-select": "^2.1.6",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@substrate/connect-discovery": "^0.2.2",
|
"@substrate/connect-discovery": "^0.2.2",
|
||||||
|
@ -35,9 +35,6 @@ importers:
|
|||||||
'@radix-ui/react-accordion':
|
'@radix-ui/react-accordion':
|
||||||
specifier: ^1.2.3
|
specifier: ^1.2.3
|
||||||
version: 1.2.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 1.2.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@radix-ui/react-checkbox':
|
|
||||||
specifier: ^1.3.3
|
|
||||||
version: 1.3.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
||||||
'@radix-ui/react-select':
|
'@radix-ui/react-select':
|
||||||
specifier: ^2.1.6
|
specifier: ^2.1.6
|
||||||
version: 2.2.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 2.2.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@ -1349,9 +1346,6 @@ packages:
|
|||||||
'@radix-ui/primitive@1.1.2':
|
'@radix-ui/primitive@1.1.2':
|
||||||
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
||||||
|
|
||||||
'@radix-ui/primitive@1.1.3':
|
|
||||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
|
||||||
|
|
||||||
'@radix-ui/react-accordion@1.2.11':
|
'@radix-ui/react-accordion@1.2.11':
|
||||||
resolution: {integrity: sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==}
|
resolution: {integrity: sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1378,19 +1372,6 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-checkbox@1.3.3':
|
|
||||||
resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': '*'
|
|
||||||
'@types/react-dom': '*'
|
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
'@types/react-dom':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@radix-ui/react-collapsible@1.1.11':
|
'@radix-ui/react-collapsible@1.1.11':
|
||||||
resolution: {integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==}
|
resolution: {integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1527,19 +1508,6 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-presence@1.1.5':
|
|
||||||
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': '*'
|
|
||||||
'@types/react-dom': '*'
|
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
'@types/react-dom':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@radix-ui/react-primitive@2.1.3':
|
'@radix-ui/react-primitive@2.1.3':
|
||||||
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -5560,8 +5528,6 @@ snapshots:
|
|||||||
|
|
||||||
'@radix-ui/primitive@1.1.2': {}
|
'@radix-ui/primitive@1.1.2': {}
|
||||||
|
|
||||||
'@radix-ui/primitive@1.1.3': {}
|
|
||||||
|
|
||||||
'@radix-ui/react-accordion@1.2.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-accordion@1.2.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.2
|
'@radix-ui/primitive': 1.1.2
|
||||||
@ -5588,22 +5554,6 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-checkbox@1.3.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
|
||||||
dependencies:
|
|
||||||
'@radix-ui/primitive': 1.1.3
|
|
||||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
|
||||||
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
|
||||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
||||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
||||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
|
|
||||||
'@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
|
||||||
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
|
||||||
react: 18.3.1
|
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/react': 18.3.23
|
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
|
||||||
|
|
||||||
'@radix-ui/react-collapsible@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-collapsible@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.2
|
'@radix-ui/primitive': 1.1.2
|
||||||
@ -5725,16 +5675,6 @@ snapshots:
|
|||||||
'@types/react': 18.3.23
|
'@types/react': 18.3.23
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
|
||||||
dependencies:
|
|
||||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
|
||||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
|
||||||
react: 18.3.1
|
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/react': 18.3.23
|
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
|
||||||
|
|
||||||
'@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
|
||||||
|
@ -17,8 +17,8 @@ import {
|
|||||||
|
|
||||||
const chainData = [
|
const chainData = [
|
||||||
{
|
{
|
||||||
label: "CASPER",
|
label: "Casper",
|
||||||
value: "0xa217f4ee58a944470e9633ca5bd6d28a428ed64cd9b6f3e413565f359f89af90",
|
value: "0x07074eb5f47a6f4dd70430674e5174d5414bc055292b90392fb6f0a28c7524d1",
|
||||||
chainSpec: casperDevelopment,
|
chainSpec: casperDevelopment,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -69,7 +69,7 @@ export const ChainSelect = () => {
|
|||||||
const api = select.connect(state, send, normalizeProps)
|
const api = select.connect(state, send, normalizeProps)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 w-full items-end">
|
<div className="flex flex-col gap-2">
|
||||||
<Select
|
<Select
|
||||||
value={state.context.value[0]}
|
value={state.context.value[0]}
|
||||||
onValueChange={(chainId) => {
|
onValueChange={(chainId) => {
|
||||||
@ -78,7 +78,7 @@ export const ChainSelect = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger
|
<SelectTrigger
|
||||||
className={"text-muted-foreground sm:w-[200px] w-full"}
|
className={"text-muted-foreground w-[200px]"}
|
||||||
data-testid="chain-select"
|
data-testid="chain-select"
|
||||||
>
|
>
|
||||||
<SelectValue placeholder="Select Chain" />
|
<SelectValue placeholder="Select Chain" />
|
||||||
@ -93,8 +93,8 @@ export const ChainSelect = () => {
|
|||||||
</Select>
|
</Select>
|
||||||
{state.context.value[0] && (
|
{state.context.value[0] && (
|
||||||
isConnected
|
isConnected
|
||||||
? <div className="text-center text-sm text-secondary font-bold tracking-wide bg-accent rounded sm:w-[200px] w-full">Connected</div>
|
? <div className="text-center text-sm text-secondary font-bold tracking-wide bg-accent rounded">Connected</div>
|
||||||
: <div className="text-center text-sm text-secondary font-bold tracking-wide bg-destructive rounded sm:w-[200px] w-full">Connecting...</div>
|
: <div className="text-center text-sm text-secondary font-bold tracking-wide bg-destructive rounded">Connecting...</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -12,8 +12,6 @@ export const Header = () => {
|
|||||||
return "Transactions"
|
return "Transactions"
|
||||||
case "book":
|
case "book":
|
||||||
return "Address Book"
|
return "Address Book"
|
||||||
case "nominations":
|
|
||||||
return "Nominations"
|
|
||||||
default:
|
default:
|
||||||
return "Health Check";
|
return "Health Check";
|
||||||
}
|
}
|
||||||
@ -21,8 +19,8 @@ export const Header = () => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex sm:flex-row flex-col gap-4 justify-between items-center pb-6 sm:w-full w-[85%] self-center">
|
<div className="flex flex-row w-full justify-between">
|
||||||
<h2 className="sm:text-3xl text-2xl font-semibold w-full">
|
<h2 className="text-3xl font-semibold">
|
||||||
{currentPath}
|
{currentPath}
|
||||||
</h2>
|
</h2>
|
||||||
<ChainSelect />
|
<ChainSelect />
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HeartPulse, SendToBack, Book, Users } from "lucide-react"
|
import { HeartPulse, SendToBack, Book } from "lucide-react"
|
||||||
import { FaGithub } from "react-icons/fa"
|
import { FaGithub } from "react-icons/fa"
|
||||||
import { Link, useLocation } from "react-router-dom"
|
import { Link, useLocation } from "react-router-dom"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
@ -118,12 +118,6 @@ export const Sidebar = () => {
|
|||||||
<span className={`md:block hidden ${cName("title", currentPath, "book")}`} >Address Book</span>
|
<span className={`md:block hidden ${cName("title", currentPath, "book")}`} >Address Book</span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/nominations" className="relative">
|
|
||||||
<div className={cName("item", currentPath, "nominations")}>
|
|
||||||
<Users className={cName("icon", currentPath, "nominations")} />
|
|
||||||
<span className={`md:block hidden ${cName("title", currentPath, "nominations")}`} >Nominations</span>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className="w-full text-center flex-grow flex flex-col justify-end">
|
<div className="w-full text-center flex-grow flex flex-col justify-end">
|
||||||
@ -137,7 +131,7 @@ export const Sidebar = () => {
|
|||||||
>
|
>
|
||||||
<FaGithub className="w-8 h-8" />
|
<FaGithub className="w-8 h-8" />
|
||||||
<div className="block float-left text-xs text-left">
|
<div className="block float-left text-xs text-left">
|
||||||
<div className="md:block hidden text-primary">GHOST Lite Git</div>
|
<div className="md:block hidden text-primary">Ghost Lite Git</div>
|
||||||
<div className="text-accent">v {pckg.version}</div>
|
<div className="text-accent">v {pckg.version}</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,31 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
|
||||||
import { Check } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "../../lib/utils"
|
|
||||||
|
|
||||||
const Checkbox = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CheckboxPrimitive.Root
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background",
|
|
||||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
||||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
||||||
"data-[state=checked]:bg-accent data-[state=checked]:border-secondary data-[state=checked]:text-secondary",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<CheckboxPrimitive.Indicator
|
|
||||||
className={cn("flex items-center justify-center text-current")}
|
|
||||||
>
|
|
||||||
<Check className="h-4 w-4" />
|
|
||||||
</CheckboxPrimitive.Indicator>
|
|
||||||
</CheckboxPrimitive.Root>
|
|
||||||
))
|
|
||||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
|
||||||
|
|
||||||
export { Checkbox }
|
|
@ -1,215 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
|
|
||||||
import { Unstable } from "@substrate/connect-discovery"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectValue,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
} from "../components/ui/select"
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "../components/ui/accordion"
|
|
||||||
import { Input } from "../components/ui/input"
|
|
||||||
import type { SystemAccountStorage } from "../hooks"
|
|
||||||
|
|
||||||
import { Row } from "./Row"
|
|
||||||
|
|
||||||
interface SenderProps {
|
|
||||||
account: string
|
|
||||||
accounts: string[]
|
|
||||||
senderAccount: SystemAccountStorage | undefined
|
|
||||||
senderBalance: string
|
|
||||||
tokenDecimals: number
|
|
||||||
tokenSymbol: string
|
|
||||||
connectAccount: (account: Unstable.Account) => void
|
|
||||||
applyDecimals: (value: bigint, decimals: number, tokenSymbol: string) => string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Sender: React.FC<SenderProps> = ({
|
|
||||||
account,
|
|
||||||
accounts,
|
|
||||||
senderAccount,
|
|
||||||
senderBalance,
|
|
||||||
tokenDecimals,
|
|
||||||
tokenSymbol,
|
|
||||||
connectAccount,
|
|
||||||
applyDecimals
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Row title="Account" element={<Select
|
|
||||||
value={account}
|
|
||||||
disabled={!accounts || accounts.length === 0}
|
|
||||||
onValueChange={(address) => {
|
|
||||||
try {
|
|
||||||
const unstableAccount: Unstable.Account = { address }
|
|
||||||
connectAccount(unstableAccount)
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger
|
|
||||||
className={"text-muted-foreground sm:w-[300px] w-full"}
|
|
||||||
data-testid="chain-select"
|
|
||||||
>
|
|
||||||
<SelectValue placeholder="Select Account" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
{accounts?.map((address, index) => (
|
|
||||||
<SelectItem
|
|
||||||
key={index}
|
|
||||||
data-testid={`address-${address}`}
|
|
||||||
value={address}>{address.slice(0, 10)}...{address.slice(-10)}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>} />
|
|
||||||
<Row title="Balance" element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Account Balance"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={senderBalance}
|
|
||||||
/>} />
|
|
||||||
{senderAccount && (<Accordion type="multiple" className="w-full flex flex-col gap-4 mb-4">
|
|
||||||
<AccordionItem className="bg-muted rounded text-sm" value="Balance Details">
|
|
||||||
<AccordionTrigger>
|
|
||||||
<div className="flex items-center gap-2 space-x-2 cursor-pointer">
|
|
||||||
Balance Details
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="flex flex-col gap-2 pl-8">
|
|
||||||
<Row title={"Free"} element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Free"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={applyDecimals(
|
|
||||||
senderAccount?.data?.free - senderAccount?.data?.frozen - senderAccount?.data?.reserved,
|
|
||||||
tokenDecimals,
|
|
||||||
tokenSymbol
|
|
||||||
)}
|
|
||||||
/>} />
|
|
||||||
<Row title={"Frozen"} element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Frozen"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={applyDecimals(senderAccount?.data?.frozen, tokenDecimals, tokenSymbol)}
|
|
||||||
/>} />
|
|
||||||
<Row title={"Reserved"} element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Reserved"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={applyDecimals(senderAccount?.data?.reserved, tokenDecimals, tokenSymbol)}
|
|
||||||
/>} />
|
|
||||||
<Row title={"Nonce"} element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Nonce"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={senderAccount?.nonce ? senderAccount.nonce.toString() : ""}
|
|
||||||
/>} />
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ReceiverProps {
|
|
||||||
receiver: string
|
|
||||||
receiverAccount: SystemAccountStorage | undefined
|
|
||||||
amount: string
|
|
||||||
tokenDecimals: number
|
|
||||||
tokenSymbol: string
|
|
||||||
isSubmittingTransaction: boolean
|
|
||||||
setReceiver: (receiver: string) => void
|
|
||||||
setAmount: (amount: string) => void
|
|
||||||
applyDecimals: (value: bigint, decimals: number, tokenSymbol: string) => string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Receiver: React.FC<ReceiverProps> = ({
|
|
||||||
receiver,
|
|
||||||
receiverAccount,
|
|
||||||
amount,
|
|
||||||
tokenDecimals,
|
|
||||||
tokenSymbol,
|
|
||||||
isSubmittingTransaction,
|
|
||||||
setReceiver,
|
|
||||||
setAmount,
|
|
||||||
applyDecimals
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Row title="Receiver" element={<Input
|
|
||||||
value={receiver}
|
|
||||||
onChange={e => setReceiver(e.target.value)}
|
|
||||||
disabled={isSubmittingTransaction}
|
|
||||||
aria-label="Transfer Receiver"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder="Input receiver address"
|
|
||||||
/>} />
|
|
||||||
<Row title="Amount" element={<Input
|
|
||||||
value={amount}
|
|
||||||
onChange={e => setAmount(e.target.value)}
|
|
||||||
disabled={isSubmittingTransaction}
|
|
||||||
aria-label="Transfer Amount"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder="Input amount to send"
|
|
||||||
/>} />
|
|
||||||
{receiverAccount && (<Accordion type="multiple" className="w-full flex flex-col gap-4">
|
|
||||||
<AccordionItem className="bg-muted rounded text-sm" value="Balance Details">
|
|
||||||
<AccordionTrigger>
|
|
||||||
<div className="flex items-center gap-2 space-x-2 cursor-pointer">
|
|
||||||
Receiver Details
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="flex flex-col gap-2 pl-8">
|
|
||||||
<Row title={"Free"} element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Fee"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={applyDecimals(receiverAccount?.data?.free, tokenDecimals, tokenSymbol)}
|
|
||||||
/>} />
|
|
||||||
<Row title={"Frozen"} element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Frozen"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={applyDecimals(receiverAccount?.data?.frozen, tokenDecimals, tokenSymbol)}
|
|
||||||
/>} />
|
|
||||||
<Row title={"Reserved"} element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Reserved"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={applyDecimals(receiverAccount?.data?.reserved, tokenDecimals, tokenSymbol)}
|
|
||||||
/>} />
|
|
||||||
<Row title={"Nonce"} element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Nonce"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={receiverAccount?.nonce ? receiverAccount.nonce.toString() : ""}
|
|
||||||
/>} />
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -12,7 +12,10 @@ import {
|
|||||||
import { Input } from "../components/ui/input"
|
import { Input } from "../components/ui/input"
|
||||||
import { Button } from "../components/ui/button"
|
import { Button } from "../components/ui/button"
|
||||||
|
|
||||||
import { AddressBookRecord } from "../types"
|
type AddressBookRecord = {
|
||||||
|
name: string
|
||||||
|
address: string
|
||||||
|
}
|
||||||
|
|
||||||
interface AddressRecordProps {
|
interface AddressRecordProps {
|
||||||
name: string
|
name: string
|
||||||
@ -25,7 +28,7 @@ const AddressRecord: React.FC<AddressRecordProps> = ({ name, address, removeReco
|
|||||||
return (
|
return (
|
||||||
<AccordionItem className="bg-muted rounded px-4" value={name}>
|
<AccordionItem className="bg-muted rounded px-4" value={name}>
|
||||||
<AccordionTrigger className="w-full hover:no-underline">
|
<AccordionTrigger className="w-full hover:no-underline">
|
||||||
<div className="w-[90%] sm:text-base text-xs overflow-hidden whitespace-nowrap overflow-ellipsis text-left">
|
<div className="flex flex-row items-center gap-2 w-[90%] justify-between">
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
@ -33,22 +36,22 @@ const AddressRecord: React.FC<AddressRecordProps> = ({ name, address, removeReco
|
|||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="full"
|
size="full"
|
||||||
className="flex-1 text-accent flex text-xs sm:flex hidden"
|
className="flex-1 text-accent text-xs"
|
||||||
onClick={() => openTransfer({ address })}
|
onClick={() => openTransfer({ address })}
|
||||||
>
|
>
|
||||||
<Send className="w-4 h-4 mr-1" />
|
<Send className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
value={address}
|
value={address}
|
||||||
aria-label={name}
|
aria-label={name}
|
||||||
type="text"
|
type="text"
|
||||||
className="sm:w-[300px] flex-6"
|
className="w-[300px] flex-6"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="full"
|
size="full"
|
||||||
className="flex-2 text-accent text-xs sm:flex hidden"
|
className="flex-2 text-accent text-xs"
|
||||||
onClick={() => removeRecord({ name })}
|
onClick={() => removeRecord({ name })}
|
||||||
>
|
>
|
||||||
<Trash className="w-4 h-4 mr-2" />
|
<Trash className="w-4 h-4 mr-2" />
|
||||||
@ -86,12 +89,9 @@ export const AddressBook = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const [, prefix] = ss58Decode(address)
|
ss58Decode(address)
|
||||||
if (prefix !== 1995 && prefix !== 1996) {
|
|
||||||
throw new Error("bad prefix")
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError("Incorrect Ghost or Casper address provided")
|
setError("Incorrect Ghost address provided")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const newRecord = { name, address }
|
const newRecord = { name, address }
|
||||||
@ -111,7 +111,7 @@ export const AddressBook = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion type="multiple" className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-4 justify-center self-center py-2">
|
<Accordion type="multiple" className="w-[500px] h-fit flex flex-col flex-1 gap-4 justify-center self-center py-8">
|
||||||
{addressBook.map(({ name, address }: AddressBookRecord, idx: number) => (
|
{addressBook.map(({ name, address }: AddressBookRecord, idx: number) => (
|
||||||
<AddressRecord
|
<AddressRecord
|
||||||
key={idx}
|
key={idx}
|
||||||
@ -121,23 +121,23 @@ export const AddressBook = () => {
|
|||||||
openTransfer={openTransfer}
|
openTransfer={openTransfer}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div className="bg-muted flex flex-col sm:gap-2 gap-4 w-full rounded px-4 py-6">
|
<div className="bg-muted flex flex-col gap-2 w-full rounded px-4 py-6">
|
||||||
<div className="flex sm:flex-row flex-col gap-4">
|
<div className="flex flex-row gap-4">
|
||||||
<Input
|
<Input
|
||||||
value={name}
|
value={name}
|
||||||
onChange={e => setName(e.target.value)}
|
onChange={e => setName(e.target.value)}
|
||||||
aria-label="New Name"
|
aria-label="New Name"
|
||||||
type="text"
|
type="text"
|
||||||
className="w-full sm:text-base text-xs sm:placeholder:text-base placeholder:text-xs"
|
className="w-full"
|
||||||
placeholder="Contact Name"
|
placeholder="Record Name"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
value={address}
|
value={address}
|
||||||
onChange={e => setAddress(e.target.value)}
|
onChange={e => setAddress(e.target.value)}
|
||||||
aria-label="New Address"
|
aria-label="New Address"
|
||||||
type="text"
|
type="text"
|
||||||
className="w-full sm:text-base text-xs sm:placeholder:text-base placeholder:text-xs"
|
className="w-full"
|
||||||
placeholder="Contact Address"
|
placeholder="Record Address"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -147,7 +147,7 @@ export const AddressBook = () => {
|
|||||||
onClick={() => addRecord({ name, address })}
|
onClick={() => addRecord({ name, address })}
|
||||||
>
|
>
|
||||||
<CirclePlus className="w-4 h-4 mr-2" />
|
<CirclePlus className="w-4 h-4 mr-2" />
|
||||||
Add Contact
|
Add Record
|
||||||
</Button>
|
</Button>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="text-xs text-destructive">{error}</div>
|
<div className="text-xs text-destructive">{error}</div>
|
||||||
|
@ -7,7 +7,6 @@ import { DEFAULT_CHAIN_ID } from "../settings"
|
|||||||
|
|
||||||
const HealthCheck = lazy(() => import("./HealthCheck").then(module => ({ default: module.HealthCheck })))
|
const HealthCheck = lazy(() => import("./HealthCheck").then(module => ({ default: module.HealthCheck })))
|
||||||
const Transactions = lazy(() => import("./Transactions").then(module => ({ default: module.Transactions })))
|
const Transactions = lazy(() => import("./Transactions").then(module => ({ default: module.Transactions })))
|
||||||
const Nominations = lazy(() => import("./Nominations").then(module => ({ default: module.Nominations })))
|
|
||||||
const AddressBook = lazy(() => import("./AddressBook").then(module => ({ default: module.AddressBook })))
|
const AddressBook = lazy(() => import("./AddressBook").then(module => ({ default: module.AddressBook })))
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
@ -18,13 +17,12 @@ export const App = () => {
|
|||||||
<MetadataProviderProvider>
|
<MetadataProviderProvider>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div style={{ maxWidth: "calc(100% - 100px)"}} className="w-full h-full flex flex-col sm:px-6 px-2 sm:py-8 py-2">
|
<div className="w-full h-full flex flex-col px-6 py-8">
|
||||||
<Header />
|
<Header />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/health" element={<HealthCheck />} />
|
<Route path="/health" element={<HealthCheck />} />
|
||||||
<Route path="/transactions" element={<Transactions />} />
|
<Route path="/transactions" element={<Transactions />} />
|
||||||
<Route path="/book" element={<AddressBook />} />
|
<Route path="/book" element={<AddressBook />} />
|
||||||
<Route path="/nominations" element={<Nominations />} />
|
|
||||||
<Route path="*" element={<Navigate to="/health" replace />} />
|
<Route path="*" element={<Navigate to="/health" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,9 +30,8 @@ const Item: React.FC<ItemProps> = ({ value, elements, icon }) => {
|
|||||||
return (
|
return (
|
||||||
<AccordionItem className="bg-muted rounded px-4" value={value}>
|
<AccordionItem className="bg-muted rounded px-4" value={value}>
|
||||||
<AccordionTrigger>
|
<AccordionTrigger>
|
||||||
<div className="flex flex-row items-center gap-2 space-x-2 cursor-pointer">
|
<div className="flex items-center gap-2 space-x-2 cursor-pointer">
|
||||||
<div>{icon}</div>
|
{icon} {value}
|
||||||
<div className="sm:block hidden">{value}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="flex flex-col gap-2">
|
<AccordionContent className="flex flex-col gap-2">
|
||||||
@ -51,8 +50,8 @@ interface RowProps {
|
|||||||
|
|
||||||
const Row: React.FC<RowProps> = ({ title, element }) => {
|
const Row: React.FC<RowProps> = ({ title, element }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex sm:flex-row flex-col gap-2 justify-between sm:items-center items-start">
|
<div className="flex flex-row gap-2 justify-between items-center">
|
||||||
<div className="sm:text-sm text-xs">{title}</div>
|
<div className="text-sm">{title}</div>
|
||||||
{element}
|
{element}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -63,8 +62,6 @@ export const HealthCheck = () => {
|
|||||||
const blocks = useBlocks()
|
const blocks = useBlocks()
|
||||||
const health = useSystemHealth()
|
const health = useSystemHealth()
|
||||||
|
|
||||||
const widthClass = "sm:w-[300px] w-full"
|
|
||||||
|
|
||||||
const metadataElements = [
|
const metadataElements = [
|
||||||
{
|
{
|
||||||
title: "Chain Name",
|
title: "Chain Name",
|
||||||
@ -72,7 +69,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Chain Name"
|
aria-label="Chain Name"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={chainSpec?.chainName}
|
placeholder={chainSpec?.chainName}
|
||||||
/>
|
/>
|
||||||
},
|
},
|
||||||
@ -82,7 +79,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Genesis Hash"
|
aria-label="Genesis Hash"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={chainSpec?.genesisHash}
|
placeholder={chainSpec?.genesisHash}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -94,7 +91,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Sync State"
|
aria-label="Sync State"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={!health ? "" : health.isSyncing ? "Syncing..." : "Synced"}
|
placeholder={!health ? "" : health.isSyncing ? "Syncing..." : "Synced"}
|
||||||
/>
|
/>
|
||||||
},
|
},
|
||||||
@ -104,7 +101,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Peers Connected"
|
aria-label="Peers Connected"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={health?.peers}
|
placeholder={health?.peers}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -116,7 +113,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Block Number"
|
aria-label="Block Number"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={blocks?.latest?.number}
|
placeholder={blocks?.latest?.number}
|
||||||
/>
|
/>
|
||||||
},
|
},
|
||||||
@ -126,7 +123,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Block Hash"
|
aria-label="Block Hash"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={blocks?.latest?.hash}
|
placeholder={blocks?.latest?.hash}
|
||||||
/>
|
/>
|
||||||
},
|
},
|
||||||
@ -136,7 +133,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Parent Hash"
|
aria-label="Parent Hash"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={blocks?.latest?.parent}
|
placeholder={blocks?.latest?.parent}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -148,7 +145,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Block Number"
|
aria-label="Block Number"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={blocks?.finalized?.number}
|
placeholder={blocks?.finalized?.number}
|
||||||
/>
|
/>
|
||||||
},
|
},
|
||||||
@ -158,7 +155,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="BlockHash"
|
aria-label="BlockHash"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={blocks?.finalized?.hash}
|
placeholder={blocks?.finalized?.hash}
|
||||||
/>
|
/>
|
||||||
},
|
},
|
||||||
@ -168,7 +165,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Parent Hash"
|
aria-label="Parent Hash"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={blocks?.finalized?.parent}
|
placeholder={blocks?.finalized?.parent}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -180,7 +177,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Coin Symbol"
|
aria-label="Coin Symbol"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={chainSpec?.properties?.tokenSymbol}
|
placeholder={chainSpec?.properties?.tokenSymbol}
|
||||||
/>
|
/>
|
||||||
},
|
},
|
||||||
@ -190,7 +187,7 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Block Hash"
|
aria-label="Block Hash"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={chainSpec?.properties?.tokenDecimals?.toString() ?? ""}
|
placeholder={chainSpec?.properties?.tokenDecimals?.toString() ?? ""}
|
||||||
/>
|
/>
|
||||||
},
|
},
|
||||||
@ -200,14 +197,14 @@ export const HealthCheck = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
aria-label="Base58 Prefix"
|
aria-label="Base58 Prefix"
|
||||||
type="text"
|
type="text"
|
||||||
className={widthClass}
|
className="w-[300px]"
|
||||||
placeholder={chainSpec?.properties?.ss58Format?.toString() ?? ""}
|
placeholder={chainSpec?.properties?.ss58Format?.toString() ?? ""}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion type="single" defaultValue="Chain metadata" className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-4 justify-center self-center sm:text-base text-xs">
|
<Accordion type="single" defaultValue="Chain metadata" className="w-[500px] h-fit flex flex-col flex-1 gap-4 justify-center self-center">
|
||||||
<Item value="Chain metadata" elements={metadataElements} icon={<Info className="w-4 h-4" />} />
|
<Item value="Chain metadata" elements={metadataElements} icon={<Info className="w-4 h-4" />} />
|
||||||
<Item value="Peers information" elements={peersElements} icon={<Binary className="w-4 h-4" />} />
|
<Item value="Peers information" elements={peersElements} icon={<Binary className="w-4 h-4" />} />
|
||||||
<Item value="Latest block" elements={bestBlockElements} icon={<Cuboid className="w-4 h-4" />} />
|
<Item value="Latest block" elements={bestBlockElements} icon={<Cuboid className="w-4 h-4" />} />
|
||||||
|
@ -1,470 +0,0 @@
|
|||||||
import React, { useEffect, useState, useMemo, useCallback } from "react"
|
|
||||||
import { Nut, NutOff, Users } from "lucide-react"
|
|
||||||
|
|
||||||
import { ss58Decode } from "@polkadot-labs/hdkd-helpers"
|
|
||||||
import { toHex } from "@polkadot-api/utils"
|
|
||||||
|
|
||||||
import { lastValueFrom, tap } from "rxjs"
|
|
||||||
import { submitTransaction$ } from "../api"
|
|
||||||
|
|
||||||
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,
|
|
||||||
useMetadata,
|
|
||||||
useEraIndex,
|
|
||||||
useNominations,
|
|
||||||
useEraRewardPoints,
|
|
||||||
useCurrentValidators,
|
|
||||||
useValidatorsOverview,
|
|
||||||
useBondedAddress,
|
|
||||||
useNominateCalldata,
|
|
||||||
useSystemAccount,
|
|
||||||
useBondCalldata,
|
|
||||||
useUnstableProvider,
|
|
||||||
RewardPoints,
|
|
||||||
} from "../hooks"
|
|
||||||
|
|
||||||
import {
|
|
||||||
AddressBookRecord,
|
|
||||||
FollowTransaction,
|
|
||||||
TransactionError,
|
|
||||||
} 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<React.SetStateAction<string[]>>
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const Item: React.FC<ItemProps> = (props) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
address,
|
|
||||||
points,
|
|
||||||
commission,
|
|
||||||
blocked,
|
|
||||||
nominatorCount,
|
|
||||||
totalStake,
|
|
||||||
ownStake,
|
|
||||||
decimals,
|
|
||||||
symbol,
|
|
||||||
nominated,
|
|
||||||
checkedAddresses,
|
|
||||||
setIsCheckedAddresses,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
const [copied, setCopied] = useState<boolean>(false);
|
|
||||||
const convertToFixed = (value: bigint, decimals: number) => {
|
|
||||||
if (!value || !decimals) {
|
|
||||||
return parseFloat("0").toFixed(5)
|
|
||||||
}
|
|
||||||
const power = Math.pow(10, decimals)
|
|
||||||
const number = Number(value / BigInt(power))
|
|
||||||
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 (
|
|
||||||
<AccordionItem className="bg-muted rounded px-4 flex flew-row items-center justify-between" value={address}>
|
|
||||||
<Checkbox checked={checkedAddresses.includes(address)} onCheckedChange={handleOnCheck} />
|
|
||||||
<div>
|
|
||||||
<AccordionTrigger>
|
|
||||||
<div className="w-100 flex flex-row items-center justify-start gap-4 space-x-2 cursor-pointer">
|
|
||||||
<div className={`w-[300px] overflow-hidden whitespace-nowrap text-ellipsis text-left ${nominated ? "text-foreground" : ""}`}>{name ?? address}</div>
|
|
||||||
<div>{points}</div>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="flex flex-col gap-2">
|
|
||||||
<hr className="my-4" />
|
|
||||||
<Row title="Commission" element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Validator Commission"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[320px] w-full"
|
|
||||||
placeholder={`${(commission / 10000000).toFixed(2)}%`}
|
|
||||||
/>} />
|
|
||||||
<Row title="Nominators" element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Validator Nominators"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[320px] w-full"
|
|
||||||
placeholder={nominatorCount.toString()}
|
|
||||||
/>} />
|
|
||||||
<Row title="Status" element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Validator Status"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[320px] w-full"
|
|
||||||
placeholder={blocked ? "Blocked" : "Available"}
|
|
||||||
/>} />
|
|
||||||
<hr className="my-4" />
|
|
||||||
<Row title="Total Stake" element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Validator Total Stake"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[320px] w-full"
|
|
||||||
placeholder={`${convertToFixed(totalStake, decimals)} ${symbol}`}
|
|
||||||
/>} />
|
|
||||||
<Row title="Own Stake" element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Validator Own Stake"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[320px] w-full"
|
|
||||||
placeholder={`${convertToFixed(ownStake, decimals)} ${symbol}`}
|
|
||||||
/>} />
|
|
||||||
<hr className="my-4" />
|
|
||||||
<div
|
|
||||||
onClick={() => handleCopy(address)}
|
|
||||||
className="flex justify-center items-center cursor-pointer hover:text-foreground"
|
|
||||||
>
|
|
||||||
{copied ? "Address copid to clipboard" : address}
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</div>
|
|
||||||
</AccordionItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const HeaderInfo = ({ text, value }: { text: string, value: string }) => {
|
|
||||||
return (
|
|
||||||
<div className="w-[40%] flex flex-col justify-center items-center">
|
|
||||||
<span>{text}</span>
|
|
||||||
<span>{value}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Nominations = () => {
|
|
||||||
const addressBook: AddressBookRecord[] = JSON.parse(localStorage.getItem('addressBook') ?? '[]')
|
|
||||||
|
|
||||||
const [checkedAddresses, setIsCheckedAddresses] = useState<string[]>([])
|
|
||||||
const [isSubmittingTransaction, setIsSubmittingTransaction] = useState<boolean>(false)
|
|
||||||
const [transactionStatus, setTransactionStatus] = useState<FollowTransaction | undefined>()
|
|
||||||
const [error, setError] = useState<TransactionError | undefined>()
|
|
||||||
|
|
||||||
const [interestingValidator, setInterestingValidator] = useState<string | undefined>(undefined)
|
|
||||||
const [amount, setAmount] = useState<string>("")
|
|
||||||
|
|
||||||
const {
|
|
||||||
provider,
|
|
||||||
clientFull,
|
|
||||||
chainId,
|
|
||||||
account,
|
|
||||||
accounts,
|
|
||||||
connectAccount
|
|
||||||
} = useUnstableProvider()
|
|
||||||
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const chainSpecV1 = useChainSpecV1()
|
|
||||||
const eraIndex = useEraIndex()
|
|
||||||
const nominations = useNominations({ address: account?.address })
|
|
||||||
const eraRewardPoints = useEraRewardPoints({ eraIndex: eraIndex?.index })
|
|
||||||
const currentValidators = useCurrentValidators({ address: interestingValidator })
|
|
||||||
const validatorOverview = useValidatorsOverview({ eraIndex: eraIndex?.index, address: interestingValidator })
|
|
||||||
|
|
||||||
const tokenDecimals: number = chainSpecV1?.properties?.tokenDecimals ?? 0
|
|
||||||
const tokenSymbol: string = chainSpecV1?.properties?.tokenSymbol ?? ""
|
|
||||||
|
|
||||||
const senderAccount = useSystemAccount({
|
|
||||||
account: account
|
|
||||||
? account.address
|
|
||||||
: undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
const convertedAmount = useMemo(() => {
|
|
||||||
try {
|
|
||||||
return BigInt(Number(amount) * Math.pow(10, tokenDecimals))
|
|
||||||
} catch {
|
|
||||||
return 0n
|
|
||||||
}
|
|
||||||
}, [amount, tokenDecimals])
|
|
||||||
|
|
||||||
const bondedAddress = useBondedAddress({ address: account?.address })
|
|
||||||
|
|
||||||
const nominateCalldata = useNominateCalldata(checkedAddresses)
|
|
||||||
const bondCalldata = useBondCalldata(
|
|
||||||
convertedAmount > 0n ? convertedAmount : undefined
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (checkedAddresses.length === 0 && nominations) {
|
|
||||||
setIsCheckedAddresses(nominations?.targets ?? [])
|
|
||||||
}
|
|
||||||
}, [checkedAddresses, nominations])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setError(undefined)
|
|
||||||
setTransactionStatus(undefined)
|
|
||||||
}, [amount])
|
|
||||||
|
|
||||||
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 = useCallback(async () => {
|
|
||||||
setIsSubmittingTransaction(true)
|
|
||||||
setTransactionStatus(undefined)
|
|
||||||
setError(undefined)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tx = await provider!.createTx(
|
|
||||||
chainId ?? "",
|
|
||||||
account ? toHex(ss58Decode(account.address)[0]) : "",
|
|
||||||
bondCalldata ?? ""
|
|
||||||
)
|
|
||||||
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, bondCalldata, chainId, clientFull, provider])
|
|
||||||
|
|
||||||
const handleOnNominate = useCallback(async () => {
|
|
||||||
setIsSubmittingTransaction(true)
|
|
||||||
setTransactionStatus(undefined)
|
|
||||||
setError(undefined)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tx = await provider!.createTx(
|
|
||||||
chainId ?? "",
|
|
||||||
account ? toHex(ss58Decode(account.address)[0]) : "",
|
|
||||||
nominateCalldata ?? ""
|
|
||||||
)
|
|
||||||
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, nominateCalldata, provider])
|
|
||||||
|
|
||||||
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="bg-muted p-4 rounded flex flex-col gap-4">
|
|
||||||
<div className="flex flex-row justify-between items-center gap-2">
|
|
||||||
<HeaderInfo text="Current Era" value={`#${eraIndex?.index.toString() ?? "..."}`} />
|
|
||||||
<HeaderInfo text="Total Points" value={eraRewardPoints?.total.toString() ?? "..."} />
|
|
||||||
{nominations && (
|
|
||||||
<HeaderInfo text="Submitted at" value={`#${nominations?.submitted_in.toString() ?? "..."}`} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<Sender
|
|
||||||
account={account?.address ?? ""}
|
|
||||||
accounts={accounts?.map(acc => acc?.address ?? "") ?? []}
|
|
||||||
senderAccount={senderAccount}
|
|
||||||
senderBalance={applyDecimals(senderAccount?.data.free ?? 0n, tokenDecimals, tokenSymbol)}
|
|
||||||
tokenDecimals={tokenDecimals}
|
|
||||||
tokenSymbol={tokenSymbol}
|
|
||||||
connectAccount={connectAccount}
|
|
||||||
applyDecimals={applyDecimals}
|
|
||||||
/>
|
|
||||||
<Row title="Bonded Amount" element={<Input
|
|
||||||
readOnly
|
|
||||||
aria-label="Bonded Amount"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder={applyDecimals(senderAccount?.data.frozen, tokenDecimals, tokenSymbol)}
|
|
||||||
/>} />
|
|
||||||
<hr />
|
|
||||||
<Row title="Amount" element={<Input
|
|
||||||
value={amount}
|
|
||||||
onChange={e => setAmount(e.target.value)}
|
|
||||||
disabled={isSubmittingTransaction}
|
|
||||||
aria-label="Transfer Amount"
|
|
||||||
type="text"
|
|
||||||
className="sm:w-[300px] w-full"
|
|
||||||
placeholder="Input amount to bond or unbond"
|
|
||||||
/>} />
|
|
||||||
<div className="flex justify-between gap-2">
|
|
||||||
{bondedAddress && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="secondary"
|
|
||||||
className="text-sm p-4 w-full"
|
|
||||||
onClick={handleOnNominate}
|
|
||||||
>
|
|
||||||
<Users className="w-4 h-4 inline-block mr-2" />
|
|
||||||
Nominate
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!bondedAddress && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="secondary"
|
|
||||||
className="text-sm p-4 w-full"
|
|
||||||
onClick={handleOnBond}
|
|
||||||
>
|
|
||||||
<Nut className="w-4 h-4 inline-block mr-2" />
|
|
||||||
Bond
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="secondary"
|
|
||||||
disabled={!bondedAddress}
|
|
||||||
className="text-sm p-4 w-full"
|
|
||||||
>
|
|
||||||
<NutOff className="w-4 h-4 inline-block mr-2" />
|
|
||||||
Unbond
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{!error && transactionStatus && (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<p className="text-xs text-accent overflow-hidden whitespace-nowrap text-ellipsis">
|
|
||||||
Transaction status: {`${transactionStatus.status}`}
|
|
||||||
</p>
|
|
||||||
{transactionStatus.hash && (<p className="text-xs text-accent overflow-hidden whitespace-nowrap text-ellipsis">
|
|
||||||
{transactionStatus.hash}
|
|
||||||
</p>)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
)}
|
|
||||||
{!error && !metadata && (
|
|
||||||
<p className="text-xs text-accent overflow-hidden whitespace-nowrap text-ellipsis">
|
|
||||||
Downloading chain metadata...
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{error && (
|
|
||||||
<p className="text-xs text-destructive overflow-hidden whitespace-nowrap text-ellipsis">
|
|
||||||
Error: {error.error}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{eraRewardPoints && (
|
|
||||||
<Accordion onValueChange={setInterestingValidator} type="single" className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-4 justify-center self-center sm:text-base text-xs">
|
|
||||||
{eraRewardPoints?.individual.map((indivial: RewardPoints, idx: number) => (
|
|
||||||
<Item
|
|
||||||
key={idx}
|
|
||||||
name={addressBook?.find((record: AddressBookRecord) => 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}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Accordion>
|
|
||||||
)}
|
|
||||||
{!eraRewardPoints && (
|
|
||||||
<div className="flex justify-center items-center">
|
|
||||||
Waiting for validators list...
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import React, { ReactNode } from "react"
|
|
||||||
|
|
||||||
interface RowProps {
|
|
||||||
title: string
|
|
||||||
element: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Row: React.FC<RowProps> = ({ title, element }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex sm:flex-row flex-col gap-2 justify-between sm:items-center items-left">
|
|
||||||
<div className="text-sm">{title}</div>
|
|
||||||
{element}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -3,15 +3,15 @@ import {
|
|||||||
Send,
|
Send,
|
||||||
Trash,
|
Trash,
|
||||||
Settings2,
|
Settings2,
|
||||||
ArrowBigRightDash,
|
ArrowBigRightDash
|
||||||
ArrowBigDownDash
|
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import React, { useState, useEffect, useCallback, useMemo } from "react"
|
import React, { useState, useEffect, useCallback, useMemo, ReactNode } from "react"
|
||||||
import { useLocation } from "react-router-dom"
|
import { useLocation } from "react-router-dom"
|
||||||
import { lastValueFrom, tap } from "rxjs"
|
import { lastValueFrom, tap } from "rxjs"
|
||||||
|
|
||||||
import { ss58Decode } from "@polkadot-labs/hdkd-helpers"
|
import { ss58Decode } from "@polkadot-labs/hdkd-helpers"
|
||||||
import { toHex } from "@polkadot-api/utils"
|
import { toHex } from "@polkadot-api/utils"
|
||||||
|
import { Unstable } from "@substrate/connect-discovery"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useChainSpecV1,
|
useChainSpecV1,
|
||||||
@ -21,14 +21,18 @@ import {
|
|||||||
useTransferCalldata,
|
useTransferCalldata,
|
||||||
useExistentialDeposit
|
useExistentialDeposit
|
||||||
} from "../hooks"
|
} from "../hooks"
|
||||||
|
import type { SystemAccountStorage } from "../hooks"
|
||||||
|
|
||||||
import { submitTransaction$ } from "../api"
|
import { submitTransaction$ } from "../api"
|
||||||
import {
|
|
||||||
FollowTransaction,
|
|
||||||
TransactionError,
|
|
||||||
TransactionHistory,
|
|
||||||
} from "../types"
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectValue,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
} from "../components/ui/select"
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
@ -38,7 +42,232 @@ import {
|
|||||||
import { Input } from "../components/ui/input"
|
import { Input } from "../components/ui/input"
|
||||||
import { Button } from "../components/ui/button"
|
import { Button } from "../components/ui/button"
|
||||||
|
|
||||||
import { Sender, Receiver } from "./Accounts"
|
type TransactionHistory = {
|
||||||
|
sender: string
|
||||||
|
receiver: string
|
||||||
|
status: string
|
||||||
|
calldata: string
|
||||||
|
tokenSymbol: string
|
||||||
|
timestamp: number
|
||||||
|
amount: string
|
||||||
|
txHash?: string
|
||||||
|
blockHash?: string
|
||||||
|
blockNumber?: number
|
||||||
|
error?: TransactionError
|
||||||
|
}
|
||||||
|
|
||||||
|
type FollowTransaction = {
|
||||||
|
hash: string
|
||||||
|
status: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionError = {
|
||||||
|
type: string
|
||||||
|
error: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
title: string
|
||||||
|
element: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const Row: React.FC<RowProps> = ({ title, element }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row gap-2 justify-between items-center">
|
||||||
|
<div className="text-sm">{title}</div>
|
||||||
|
{element}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SenderProps {
|
||||||
|
account: string
|
||||||
|
accounts: string[]
|
||||||
|
senderAccount: SystemAccountStorage | undefined
|
||||||
|
senderBalance: string
|
||||||
|
tokenDecimals: number
|
||||||
|
tokenSymbol: string
|
||||||
|
connectAccount: (account: Unstable.Account) => void
|
||||||
|
applyDecimals: (value: bigint, decimals: number, tokenSymbol: string) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sender: React.FC<SenderProps> = ({
|
||||||
|
account,
|
||||||
|
accounts,
|
||||||
|
senderAccount,
|
||||||
|
senderBalance,
|
||||||
|
tokenDecimals,
|
||||||
|
tokenSymbol,
|
||||||
|
connectAccount,
|
||||||
|
applyDecimals
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row title="Account" element={<Select
|
||||||
|
value={account}
|
||||||
|
disabled={!accounts || accounts.length === 0}
|
||||||
|
onValueChange={(address) => {
|
||||||
|
try {
|
||||||
|
const unstableAccount: Unstable.Account = { address }
|
||||||
|
connectAccount(unstableAccount)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className={"text-muted-foreground w-[300px]"}
|
||||||
|
data-testid="chain-select"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select Account" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{accounts?.map((address, index) => (
|
||||||
|
<SelectItem
|
||||||
|
key={index}
|
||||||
|
data-testid={`address-${address}`}
|
||||||
|
value={address}>{address.slice(0, 10)}...{address.slice(-10)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>} />
|
||||||
|
<Row title="Balance" element={<Input
|
||||||
|
readOnly
|
||||||
|
aria-label="Account Balance"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder={senderBalance}
|
||||||
|
/>} />
|
||||||
|
{senderAccount && (<Accordion type="multiple" className="w-full flex flex-col gap-4 mb-4">
|
||||||
|
<AccordionItem className="bg-muted rounded text-sm" value="Balance Details">
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center gap-2 space-x-2 cursor-pointer">
|
||||||
|
Balance Details
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="flex flex-col gap-2 pl-8">
|
||||||
|
<Row title={"Free"} element={<Input
|
||||||
|
readOnly
|
||||||
|
aria-label="Fee"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder={applyDecimals(senderAccount?.data?.free, tokenDecimals, tokenSymbol)}
|
||||||
|
/>} />
|
||||||
|
<Row title={"Frozen"} element={<Input
|
||||||
|
readOnly
|
||||||
|
aria-label="Frozen"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder={applyDecimals(senderAccount?.data?.frozen, tokenDecimals, tokenSymbol)}
|
||||||
|
/>} />
|
||||||
|
<Row title={"Reserved"} element={<Input
|
||||||
|
readOnly
|
||||||
|
aria-label="Reserved"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder={applyDecimals(senderAccount?.data?.reserved, tokenDecimals, tokenSymbol)}
|
||||||
|
/>} />
|
||||||
|
<Row title={"Nonce"} element={<Input
|
||||||
|
readOnly
|
||||||
|
aria-label="Nonce"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder={senderAccount?.nonce ? senderAccount.nonce.toString() : ""}
|
||||||
|
/>} />
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReceiverProps {
|
||||||
|
receiver: string
|
||||||
|
receiverAccount: SystemAccountStorage | undefined
|
||||||
|
amount: string
|
||||||
|
tokenDecimals: number
|
||||||
|
tokenSymbol: string
|
||||||
|
isSubmittingTransaction: boolean
|
||||||
|
setReceiver: (receiver: string) => void
|
||||||
|
setAmount: (amount: string) => void
|
||||||
|
applyDecimals: (value: bigint, decimals: number, tokenSymbol: string) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Receiver: React.FC<ReceiverProps> = ({
|
||||||
|
receiver,
|
||||||
|
receiverAccount,
|
||||||
|
amount,
|
||||||
|
tokenDecimals,
|
||||||
|
tokenSymbol,
|
||||||
|
isSubmittingTransaction,
|
||||||
|
setReceiver,
|
||||||
|
setAmount,
|
||||||
|
applyDecimals
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row title="Receiver" element={<Input
|
||||||
|
value={receiver}
|
||||||
|
onChange={e => setReceiver(e.target.value)}
|
||||||
|
disabled={isSubmittingTransaction}
|
||||||
|
aria-label="Transfer Receiver"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder="Input receiver address"
|
||||||
|
/>} />
|
||||||
|
<Row title="Amount" element={<Input
|
||||||
|
value={amount}
|
||||||
|
onChange={e => setAmount(e.target.value)}
|
||||||
|
disabled={isSubmittingTransaction}
|
||||||
|
aria-label="Transfer Amount"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder="Input amount to send"
|
||||||
|
/>} />
|
||||||
|
{receiverAccount && (<Accordion type="multiple" className="w-full flex flex-col gap-4">
|
||||||
|
<AccordionItem className="bg-muted rounded text-sm" value="Balance Details">
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center gap-2 space-x-2 cursor-pointer">
|
||||||
|
Receiver Details
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="flex flex-col gap-2 pl-8">
|
||||||
|
<Row title={"Free"} element={<Input
|
||||||
|
readOnly
|
||||||
|
aria-label="Fee"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder={applyDecimals(receiverAccount?.data?.free, tokenDecimals, tokenSymbol)}
|
||||||
|
/>} />
|
||||||
|
<Row title={"Frozen"} element={<Input
|
||||||
|
readOnly
|
||||||
|
aria-label="Frozen"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder={applyDecimals(receiverAccount?.data?.frozen, tokenDecimals, tokenSymbol)}
|
||||||
|
/>} />
|
||||||
|
<Row title={"Reserved"} element={<Input
|
||||||
|
readOnly
|
||||||
|
aria-label="Reserved"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder={applyDecimals(receiverAccount?.data?.reserved, tokenDecimals, tokenSymbol)}
|
||||||
|
/>} />
|
||||||
|
<Row title={"Nonce"} element={<Input
|
||||||
|
readOnly
|
||||||
|
aria-label="Nonce"
|
||||||
|
type="text"
|
||||||
|
className="w-[300px]"
|
||||||
|
placeholder={receiverAccount?.nonce ? receiverAccount.nonce.toString() : ""}
|
||||||
|
/>} />
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const Transactions = () => {
|
export const Transactions = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
@ -104,10 +333,7 @@ export const Transactions = () => {
|
|||||||
|
|
||||||
const receiverObject = useMemo(() => {
|
const receiverObject = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
const [, prefix] = ss58Decode(receiver)
|
ss58Decode(receiver)
|
||||||
if (prefix !== 1995 && prefix !== 1996) {
|
|
||||||
throw new Error("bad prefix")
|
|
||||||
}
|
|
||||||
return { isValid: true, address: receiver }
|
return { isValid: true, address: receiver }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { isValid: false, address: receiver }
|
return { isValid: false, address: receiver }
|
||||||
@ -129,6 +355,11 @@ export const Transactions = () => {
|
|||||||
? receiverObject.address
|
? receiverObject.address
|
||||||
: undefined
|
: undefined
|
||||||
})
|
})
|
||||||
|
const senderBalance = !account ? undefined : (
|
||||||
|
senderAccount?.data.free +
|
||||||
|
senderAccount?.data.frozen +
|
||||||
|
senderAccount?.data.reserved
|
||||||
|
)
|
||||||
|
|
||||||
const applyDecimals = (value = 0n, decimals = 0, tokenSymbol = "CSPR") => {
|
const applyDecimals = (value = 0n, decimals = 0, tokenSymbol = "CSPR") => {
|
||||||
if (!value) return `0 ${tokenSymbol}`
|
if (!value) return `0 ${tokenSymbol}`
|
||||||
@ -241,10 +472,10 @@ export const Transactions = () => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-2 justify-center self-center rounded py-2">
|
<div className="w-[500px] h-fit flex flex-col flex-1 gap-2 justify-center self-center rounded py-8">
|
||||||
<div className="bg-muted p-4 rounded flex flex-col gap-2">
|
<div className="bg-muted p-4 rounded flex flex-col gap-2">
|
||||||
<div className="w-full flex flex-row justify-between">
|
<div className="w-full flex flex-row justify-between">
|
||||||
<div className="flex sm:flex-row flex-col gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -271,7 +502,6 @@ export const Transactions = () => {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
disabled={isSubmittingTransaction}
|
disabled={isSubmittingTransaction}
|
||||||
onClick={() => onActiveTabChanged("settings")}
|
onClick={() => onActiveTabChanged("settings")}
|
||||||
className="sm:block hidden"
|
|
||||||
>
|
>
|
||||||
<Settings2 className="w-4 h-4 inline-block" />
|
<Settings2 className="w-4 h-4 inline-block" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -284,7 +514,7 @@ export const Transactions = () => {
|
|||||||
account={account?.address ?? ""}
|
account={account?.address ?? ""}
|
||||||
accounts={accounts?.map(acc => acc?.address ?? "") ?? []}
|
accounts={accounts?.map(acc => acc?.address ?? "") ?? []}
|
||||||
senderAccount={senderAccount}
|
senderAccount={senderAccount}
|
||||||
senderBalance={applyDecimals(senderAccount?.data.free ?? 0n, tokenDecimals, tokenSymbol)}
|
senderBalance={applyDecimals(senderBalance, tokenDecimals, tokenSymbol)}
|
||||||
tokenDecimals={tokenDecimals}
|
tokenDecimals={tokenDecimals}
|
||||||
tokenSymbol={tokenSymbol}
|
tokenSymbol={tokenSymbol}
|
||||||
connectAccount={connectAccount}
|
connectAccount={connectAccount}
|
||||||
@ -351,18 +581,17 @@ export const Transactions = () => {
|
|||||||
{transactionHistory.map((props: TransactionHistory) => (
|
{transactionHistory.map((props: TransactionHistory) => (
|
||||||
<AccordionItem key={props.txHash} className="bg-muted rounded px-4" value={props.txHash ?? ""}>
|
<AccordionItem key={props.txHash} className="bg-muted rounded px-4" value={props.txHash ?? ""}>
|
||||||
<AccordionTrigger className="w-full hover:no-underline">
|
<AccordionTrigger className="w-full hover:no-underline">
|
||||||
<div className="flex sm:flex-row flex-col gap-2 items-center w-[90%] justify-between cursor-pointer">
|
<div className="flex flex-row gap-2 items-center w-[90%] justify-between cursor-pointer">
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
className="overflow-hidden whitespace-nowrap text-ellipsis sm:w-[45%] w-full"
|
className="overflow-hidden whitespace-nowrap text-ellipsis w-[45%]"
|
||||||
value={props.sender}
|
value={props.sender}
|
||||||
placeholder="Sender not found"
|
placeholder="Sender not found"
|
||||||
/>
|
/>
|
||||||
<ArrowBigRightDash className="w-6 h-6 sm:block hidden" />
|
<ArrowBigRightDash className="w-6 h-6" />
|
||||||
<ArrowBigDownDash className="w-6 h-6 sm:hidden block" />
|
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
className="overflow-hidden whitespace-nowrap text-ellipsis sm:w-[45%] w-full"
|
className="overflow-hidden whitespace-nowrap text-ellipsis w-[45%]"
|
||||||
value={props.receiver}
|
value={props.receiver}
|
||||||
placeholder="Receiver not found"
|
placeholder="Receiver not found"
|
||||||
/>
|
/>
|
||||||
@ -384,28 +613,28 @@ export const Transactions = () => {
|
|||||||
</span>
|
</span>
|
||||||
<hr className="my-4" />
|
<hr className="my-4" />
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex sm:flex-row flex-col justify-between sm:items-center items-left gap-2">
|
<div className="flex flex-row justify-between items-center gap-2">
|
||||||
Tx hash:
|
Tx hash:
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
value={props.txHash}
|
value={props.txHash}
|
||||||
className="text-accent sm:w-[350px] w-full h-[30px]"
|
className="text-accent w-[350px] h-[30px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex sm:flex-row flex-col justify-between sm:items-center items-left gap-2">
|
<div className="flex flex-row justify-between items-center gap-2">
|
||||||
Block hash:
|
Block hash:
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
value={props.blockHash}
|
value={props.blockHash}
|
||||||
className="text-accent sm:w-[350px] w-full h-[30px]"
|
className="text-accent w-[350px] h-[30px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex sm:flex-row flex-col justify-between sm:items-center items-left gap-2">
|
<div className="flex flex-row justify-between items-center gap-2">
|
||||||
Calldata:
|
Calldata:
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
value={props.calldata}
|
value={props.calldata}
|
||||||
className="text-accent sm:w-[350px] w-full h-[30px]"
|
className="text-accent w-[350px] h-[30px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -433,23 +662,23 @@ export const Transactions = () => {
|
|||||||
)}
|
)}
|
||||||
{activeTab === "settings" && (
|
{activeTab === "settings" && (
|
||||||
<div className="min-h-[248px] text-sm flex flex-col gap-2">
|
<div className="min-h-[248px] text-sm flex flex-col gap-2">
|
||||||
<div className="flex sm:flex-row flex-col gap-2 justify-between sm:items-center items-left">
|
<div className="flex flex-row gap-2 justify-between items-center">
|
||||||
<span className="sm:pb-4 pb-0">Max lifetime:</span>
|
<span className="pb-4">Max lifetime:</span>
|
||||||
<div className="sm:w-[350px] w-full flex flex-col">
|
<div className="w-[350px] flex flex-col">
|
||||||
<Input
|
<Input
|
||||||
value={historyLifetimeDuration}
|
value={historyLifetimeDuration}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newValue = +e.target.value
|
const newValue = +e.target.value
|
||||||
if (newValue) setHistoryLifetimeDuration(newValue)
|
if (newValue) setHistoryLifetimeDuration(newValue)
|
||||||
}}
|
}}
|
||||||
className="sm:w-[350px] w-full"
|
className="w-[350px]"
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-accent my-2">
|
<span className="text-xs text-accent my-2">
|
||||||
{convertedTimestamp({ timestamp: historyLifetimeDuration})}
|
{convertedTimestamp({ timestamp: historyLifetimeDuration})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex sm:flex-row flex-col gap-2 justify-between sm:items-center items-left">
|
<div className="flex flex-row gap-2 justify-between items-center">
|
||||||
<span>Max records:</span>
|
<span>Max records:</span>
|
||||||
<Input
|
<Input
|
||||||
value={historyMaxRecords}
|
value={historyMaxRecords}
|
||||||
@ -457,10 +686,10 @@ export const Transactions = () => {
|
|||||||
const newValue = +e.target.value
|
const newValue = +e.target.value
|
||||||
if (newValue) setHistoryMaxRecords(newValue)
|
if (newValue) setHistoryMaxRecords(newValue)
|
||||||
}}
|
}}
|
||||||
className="sm:w-[350px] w-full"
|
className="w-[350px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex sm:flex-row flex-col gap-2 justify-between sm:items-center items-left">
|
<div className="flex flex-row gap-2 justify-between items-center">
|
||||||
<span>Default amount:</span>
|
<span>Default amount:</span>
|
||||||
<Input
|
<Input
|
||||||
value={defaultTransactAmount}
|
value={defaultTransactAmount}
|
||||||
@ -468,7 +697,7 @@ export const Transactions = () => {
|
|||||||
setDefaultTransactAmount(e.target.value)
|
setDefaultTransactAmount(e.target.value)
|
||||||
setAmount(e.target.value)
|
setAmount(e.target.value)
|
||||||
}}
|
}}
|
||||||
className="sm:w-[350px] w-full"
|
className="w-[350px]"
|
||||||
placeholder="Amount will be empty"
|
placeholder="Amount will be empty"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,9 +8,3 @@ export * from "./useBlocks"
|
|||||||
export * from "./useSystemHealth"
|
export * from "./useSystemHealth"
|
||||||
export * from "./useCalldata"
|
export * from "./useCalldata"
|
||||||
export * from "./useConstants"
|
export * from "./useConstants"
|
||||||
export * from "./useEraIndex"
|
|
||||||
export * from "./useEraRewardPoints"
|
|
||||||
export * from "./useCurrentValidators"
|
|
||||||
export * from "./useValidatorsOverview"
|
|
||||||
export * from "./useBondedAddress"
|
|
||||||
export * from "./useNominations"
|
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
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 BondedAddress = string | undefined
|
|
||||||
|
|
||||||
export const useBondedAddress = ({ address }: { address: string | undefined }) => {
|
|
||||||
const { chainHead$, chainId } = useUnstableProvider()
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const { data: bondedAddress } = useSWRSubscription(
|
|
||||||
chainHead$ && address && chainId && metadata
|
|
||||||
? ["bonded", 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 bondedAddress = builder.buildStorage("Staking", "Bonded")
|
|
||||||
return storage$(blockInfo?.hash, "value", () =>
|
|
||||||
bondedAddress?.keys.enc(address)
|
|
||||||
).pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
distinct(),
|
|
||||||
map((value: string) => bondedAddress?.value.dec(value) as BondedAddress)
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.subscribe({
|
|
||||||
next(bondedAddress: BondedAddress) {
|
|
||||||
next(null, bondedAddress)
|
|
||||||
},
|
|
||||||
error: next,
|
|
||||||
})
|
|
||||||
return () => subscription.unsubscribe()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return bondedAddress
|
|
||||||
}
|
|
@ -15,14 +15,6 @@ const AccountId = (value: SS58String) => Enum<
|
|||||||
"Id"
|
"Id"
|
||||||
>("Id", value)
|
>("Id", value)
|
||||||
|
|
||||||
const RewardDestination = () => Enum<
|
|
||||||
{
|
|
||||||
type: "Staked"
|
|
||||||
value: []
|
|
||||||
},
|
|
||||||
"Staked"
|
|
||||||
>("Staked", [])
|
|
||||||
|
|
||||||
export const useTransferCalldata = (destination: SS58String | undefined, amount: bigint | undefined) => {
|
export const useTransferCalldata = (destination: SS58String | undefined, amount: bigint | undefined) => {
|
||||||
const { client, chainId } = useUnstableProvider()
|
const { client, chainId } = useUnstableProvider()
|
||||||
const metadata = useMetadata()
|
const metadata = useMetadata()
|
||||||
@ -47,51 +39,3 @@ export const useTransferCalldata = (destination: SS58String | undefined, amount:
|
|||||||
)
|
)
|
||||||
return calldata
|
return calldata
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBondCalldata = (amount: bigint | undefined) => {
|
|
||||||
const { client, chainId } = useUnstableProvider()
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const { data: calldata } = useSWR(
|
|
||||||
client && chainId && amount && metadata
|
|
||||||
? ["metadata", client, chainId, metadata, amount]
|
|
||||||
: null,
|
|
||||||
([_, client, _chainId, metadata, amount]) => {
|
|
||||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
|
||||||
const { codec, location } = builder.buildCall("Staking", "bond")
|
|
||||||
|
|
||||||
return toHex(
|
|
||||||
mergeUint8(
|
|
||||||
new Uint8Array(location),
|
|
||||||
codec.enc({
|
|
||||||
value: amount,
|
|
||||||
payee: RewardDestination(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return calldata
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useNominateCalldata = (addresses: string[]) => {
|
|
||||||
const { client, chainId } = useUnstableProvider()
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const { data: calldata } = useSWR(
|
|
||||||
client && chainId && addresses && metadata
|
|
||||||
? ["metadata", client, chainId, metadata, addresses]
|
|
||||||
: null,
|
|
||||||
([_, client, _chainId, metadata, addresses]) => {
|
|
||||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
|
||||||
const { codec, location } = builder.buildCall("Staking", "nominate")
|
|
||||||
|
|
||||||
const targets = addresses.map(address => AccountId(address))
|
|
||||||
return toHex(
|
|
||||||
mergeUint8(
|
|
||||||
new Uint8Array(location),
|
|
||||||
codec.enc({ targets }),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return calldata
|
|
||||||
}
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
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 ValidatorDetails = {
|
|
||||||
commission: number
|
|
||||||
blocked: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCurrentValidators = ({ address }: { address: string | undefined }) => {
|
|
||||||
const { chainHead$, chainId } = useUnstableProvider()
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const { data: currentValidators } = useSWRSubscription(
|
|
||||||
chainHead$ && address && chainId && metadata
|
|
||||||
? ["validators", 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 currentValidators = builder.buildStorage("Staking", "Validators")
|
|
||||||
return storage$(blockInfo?.hash, "value", () =>
|
|
||||||
currentValidators?.keys.enc(address)
|
|
||||||
).pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
distinct(),
|
|
||||||
map((value: string) => currentValidators?.value.dec(value) as ValidatorDetails)
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.subscribe({
|
|
||||||
next(currentValidators: ValidatorDetails) {
|
|
||||||
next(null, currentValidators)
|
|
||||||
},
|
|
||||||
error: next,
|
|
||||||
})
|
|
||||||
return () => subscription.unsubscribe()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return currentValidators
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
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 EraIndexStorage = {
|
|
||||||
index: number
|
|
||||||
start: bigint
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useEraIndex = () => {
|
|
||||||
const { chainHead$, chainId } = useUnstableProvider()
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const { data: eraIndex } = useSWRSubscription(
|
|
||||||
chainHead$ && chainId && metadata
|
|
||||||
? ["eraIndex", chainHead$, chainId, metadata]
|
|
||||||
: null,
|
|
||||||
([_, chainHead$, chainId, metadata], { next }) => {
|
|
||||||
const { finalized$, storage$ } = chainHead$
|
|
||||||
const subscription = finalized$.pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
mergeMap((blockInfo: BlockInfo) => {
|
|
||||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
|
||||||
const eraIndex = builder.buildStorage("Staking", "ActiveEra")
|
|
||||||
return storage$(blockInfo?.hash, "value", () =>
|
|
||||||
eraIndex?.keys.enc()
|
|
||||||
).pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
distinct(),
|
|
||||||
map((value: string) => eraIndex?.value.dec(value) as EraIndexStorage)
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.subscribe({
|
|
||||||
next(eraIndex: EraIndexStorage) {
|
|
||||||
next(null, eraIndex)
|
|
||||||
},
|
|
||||||
error: next,
|
|
||||||
})
|
|
||||||
return () => subscription.unsubscribe()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return eraIndex
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
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 RewardPoints = [string, number]
|
|
||||||
|
|
||||||
export type EraRewardPoints = {
|
|
||||||
total: number
|
|
||||||
indivial: RewardPoints[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useEraRewardPoints = ({ eraIndex }: { eraIndex: number | undefined }) => {
|
|
||||||
const { chainHead$, chainId } = useUnstableProvider()
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const { data: eraRewardPoints } = useSWRSubscription(
|
|
||||||
chainHead$ && eraIndex && chainId && metadata
|
|
||||||
? ["eraRewardPoints", chainHead$, eraIndex, chainId, metadata]
|
|
||||||
: null,
|
|
||||||
([_, chainHead$, eraIndex, chainId, metadata], { next }) => {
|
|
||||||
const { finalized$, storage$ } = chainHead$
|
|
||||||
const subscription = finalized$.pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
mergeMap((blockInfo: BlockInfo) => {
|
|
||||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
|
||||||
const eraRewardPoints = builder.buildStorage("Staking", "ErasRewardPoints")
|
|
||||||
return storage$(blockInfo?.hash, "value", () =>
|
|
||||||
eraRewardPoints?.keys.enc(eraIndex)
|
|
||||||
).pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
distinct(),
|
|
||||||
map((value: string) => eraRewardPoints?.value.dec(value) as EraRewardPoints)
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.subscribe({
|
|
||||||
next(eraRewardPoints: EraRewardPoints) {
|
|
||||||
next(null, eraRewardPoints)
|
|
||||||
},
|
|
||||||
error: next,
|
|
||||||
})
|
|
||||||
return () => subscription.unsubscribe()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return eraRewardPoints
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
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 NominationDetails = {
|
|
||||||
targets: string[]
|
|
||||||
submittedIn: number
|
|
||||||
suppressed: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useNominations = ({ address }: { address: string | undefined }) => {
|
|
||||||
const { chainHead$, chainId } = useUnstableProvider()
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const { data: nominations } = useSWRSubscription(
|
|
||||||
chainHead$ && address && chainId && metadata
|
|
||||||
? ["validators", 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 nominations = builder.buildStorage("Staking", "Nominators")
|
|
||||||
return storage$(blockInfo?.hash, "value", () =>
|
|
||||||
nominations?.keys.enc(address)
|
|
||||||
).pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
distinct(),
|
|
||||||
map((value: string) => nominations?.value.dec(value) as NominationDetails)
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.subscribe({
|
|
||||||
next(nominations: NominationDetails) {
|
|
||||||
next(null, nominations)
|
|
||||||
},
|
|
||||||
error: next,
|
|
||||||
})
|
|
||||||
return () => subscription.unsubscribe()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return nominations
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
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 ValidatorOverview = {
|
|
||||||
page_count: number
|
|
||||||
nominator_count: number
|
|
||||||
own: bigint
|
|
||||||
total: bigint
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useValidatorsOverview = ({ eraIndex, address }: { eraIndex: number | undefined, address: string | undefined }) => {
|
|
||||||
const { chainHead$, chainId } = useUnstableProvider()
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const { data: validatorOverview } = useSWRSubscription(
|
|
||||||
chainHead$ && eraIndex && address && chainId && metadata
|
|
||||||
? ["validatorOverview", chainHead$, eraIndex, address, chainId, metadata]
|
|
||||||
: null,
|
|
||||||
([_, chainHead$, eraIndex, address, chainId, metadata], { next }) => {
|
|
||||||
const { finalized$, storage$ } = chainHead$
|
|
||||||
const subscription = finalized$.pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
mergeMap((blockInfo: BlockInfo) => {
|
|
||||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
|
||||||
const validatorOverview = builder.buildStorage("Staking", "ErasStakersOverview")
|
|
||||||
return storage$(blockInfo?.hash, "value", () =>
|
|
||||||
validatorOverview?.keys.enc(eraIndex, address)
|
|
||||||
).pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
distinct(),
|
|
||||||
map((value: string) => validatorOverview?.value.dec(value) as ValidatorOverview)
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.subscribe({
|
|
||||||
next(validatorOverview: ValidatorOverview) {
|
|
||||||
next(null, validatorOverview)
|
|
||||||
},
|
|
||||||
error: next,
|
|
||||||
})
|
|
||||||
return () => subscription.unsubscribe()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return validatorOverview
|
|
||||||
}
|
|
@ -1 +1 @@
|
|||||||
export const DEFAULT_CHAIN_ID = "0xa217f4ee58a944470e9633ca5bd6d28a428ed64cd9b6f3e413565f359f89af90"
|
export const DEFAULT_CHAIN_ID = "0x07074eb5f47a6f4dd70430674e5174d5414bc055292b90392fb6f0a28c7524d1"
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export type AddressBookRecord = {
|
|
||||||
name: string
|
|
||||||
address: string
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from "./addressBook"
|
|
||||||
export * from "./transaction"
|
|
@ -1,23 +0,0 @@
|
|||||||
export type FollowTransaction = {
|
|
||||||
hash: string
|
|
||||||
status: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TransactionError = {
|
|
||||||
type: string
|
|
||||||
error: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TransactionHistory = {
|
|
||||||
sender: string
|
|
||||||
receiver: string
|
|
||||||
status: string
|
|
||||||
calldata: string
|
|
||||||
tokenSymbol: string
|
|
||||||
timestamp: number
|
|
||||||
amount: string
|
|
||||||
txHash?: string
|
|
||||||
blockHash?: string
|
|
||||||
blockNumber?: number
|
|
||||||
error?: TransactionError
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user