-
+ {value.startsWith("0x") ? (
+
+ ) : (
+
+ )}
{name && (
@@ -42,3 +47,7 @@ export const AccountIdDisplay: FC<{
)
}
+const getPublicKey = (address: string) => {
+ const info = getSs58AddressInfo(address)
+ return info.isValid ? info.publicKey : null
+}
diff --git a/src/components/AccountIdInput.tsx b/src/components/AccountIdInput.tsx
deleted file mode 100644
index c6132c8..0000000
--- a/src/components/AccountIdInput.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-import { AccountIdDisplay } from "@/components/AccountIdDisplay"
-import { Button } from "@/components/ui/button"
-import {
- Command,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
-} from "@/components/ui/command"
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover"
-import { groupBy } from "@/lib/groupBy"
-import { accounts$ } from "@/state/accounts.state"
-import { getPublicKey } from "@/state/extension-accounts.state"
-import { identity$, isVerified } from "@/state/identity.state"
-import { cn } from "@/utils/cn"
-import {
- getSs58AddressInfo,
- SS58String,
-} from "@polkadot-api/substrate-bindings"
-import { toHex } from "@polkadot-api/utils"
-import { state, useStateObservable } from "@react-rxjs/core"
-import { combineKeys } from "@react-rxjs/utils"
-import { Check, ChevronsUpDown } from "lucide-react"
-import { FC, useState } from "react"
-import { filter, map, switchMap, take } from "rxjs"
-
-const acconutMap$ = accounts$.pipeState(
- map((accounts) => groupBy(accounts, (acc) => acc.accountId)),
- map((groups) =>
- Object.fromEntries(
- Object.entries(groups)
- .filter(([accountId]) => !accountId.startsWith("0x"))
- .map(([accountId, [groupRep]]) => [accountId, groupRep]),
- ),
- ),
-)
-
-const hintedAccounts$ = state(
- combineKeys(
- acconutMap$.pipe(map((groups) => Object.keys(groups))),
- (address) =>
- acconutMap$.pipe(
- map((v) => v[address]),
- filter((v) => !!v),
- take(1),
- switchMap((details) =>
- identity$(details.accountId).pipe(
- map((identity) => ({
- address: details.accountId,
- name: identity?.displayName ?? details.name,
- isVerified: isVerified(identity),
- })),
- ),
- ),
- ),
- ).pipe(map((v) => new Map(v))),
- new Map<
- string,
- {
- address: string
- name: string | undefined
- isVerified: boolean | undefined
- }
- >(),
-)
-
-export const AccountIdInput: FC<{
- value: SS58String | null
- onValueChanged: (value: SS58String | null) => void
- className?: string
-}> = ({ value, onValueChanged, className }) => {
- const accounts = useStateObservable(hintedAccounts$)
-
- const [query, setQuery] = useState("")
- const queryInfo = getSs58AddressInfo(query)
- const [open, _setOpen] = useState(false)
-
- const setOpen = (value: boolean) => {
- _setOpen(value)
- setQuery("")
- }
-
- const valueIsNew =
- value !== null &&
- !accounts.has(toHex(getPublicKey(value) ?? new Uint8Array()))
-
- const accountList = Array.from(accounts.entries())
- if (value !== null) {
- accountList.sort(([, a], [, b]) =>
- a.address === value ? -1 : b.address === value ? 1 : 0,
- )
- }
-
- const onTriggerKeyDown = (evt: React.KeyboardEvent) => {
- if (evt.key.length === 1) {
- setOpen(true)
- }
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
- The value is not a valid Account ID
-
-
-
- {valueIsNew && (
- setOpen(false)}
- />
- )}
- {accountList.map(([key, account]) => (
- {
- onValueChanged(account.address)
- setOpen(false)
- }}
- />
- ))}
- {queryInfo.isValid && (
- {
- onValueChanged(query)
- setOpen(false)
- }}
- />
- )}
-
-
-
-
-
- )
-}
-
-const accountDetail$ = state(
- (accountId: SS58String) => acconutMap$.pipe(map((v) => v[accountId] ?? null)),
- null,
-)
-
-const AccountOption: FC<{
- account: string
- selected: boolean
- onSelect: () => void
-}> = ({ account, selected, onSelect }) => {
- const details = useStateObservable(accountDetail$(account))
- const identity = useStateObservable(identity$(account))
-
- const name = identity?.displayName ?? details?.name
-
- return (
-
-
-
-
- )
-}
diff --git a/src/components/CommandPopover.tsx b/src/components/CommandPopover.tsx
index 859fa38..770e68a 100644
--- a/src/components/CommandPopover.tsx
+++ b/src/components/CommandPopover.tsx
@@ -78,6 +78,7 @@ export function CommandPopover({
onClick={() => setOpen(true)}
placeholder={placeholder}
className="border-none focus:ring-0"
+ autoFocus
/>
{open && (
diff --git a/src/components/EthAccountDisplay.tsx b/src/components/EthAccountDisplay.tsx
index 926040a..dd4d45b 100644
--- a/src/components/EthAccountDisplay.tsx
+++ b/src/components/EthAccountDisplay.tsx
@@ -1,9 +1,9 @@
-import { accountDetail$ } from "@/state/extension-accounts.state"
+import { accountDetail$ } from "@/state/polkahub"
+import { EthIdenticon } from "@polkadot-api/react-components"
import { useStateObservable } from "@react-rxjs/core"
import { HexString } from "polkadot-api"
import { FC } from "react"
import { twMerge } from "tailwind-merge"
-import { EthIdenticon } from "./EthIdenticon"
export const EthAccountDisplay: FC<{
value: HexString
diff --git a/src/components/EthIdenticon.tsx b/src/components/EthIdenticon.tsx
deleted file mode 100644
index ace4432..0000000
--- a/src/components/EthIdenticon.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { HexString } from "polkadot-api"
-import { FC } from "react"
-import { blo } from "blo"
-
-export const EthIdenticon: FC<{ address: HexString; size?: number }> = ({
- address,
- size,
-}) => {
- return

-}
diff --git a/src/components/PolkadotIdenticon.tsx b/src/components/PolkadotIdenticon.tsx
deleted file mode 100644
index 3fd8a30..0000000
--- a/src/components/PolkadotIdenticon.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-// Based from https://github.com/paritytech/oo7/blob/251ba2b7c45503b68eab4320c270b5afa9bccb60/packages/polkadot-identicon/src/index.jsx
-
-import { blake2b } from "@noble/hashes/blake2.js"
-import { FC, SVGAttributes } from "react"
-
-export const PolkadotIdenticon: FC<
- Omit
, "viewBox"> & {
- sixPoint?: boolean
- publicKey: Uint8Array | null
- size?: number
- }
-> = ({ sixPoint, publicKey, ...props }) => {
- const svgProps = {
- ...props,
- width: props.size ?? props.width ?? SIZE,
- height: props.size ?? props.height ?? SIZE,
- viewBox: `0 0 ${SIZE} ${SIZE}`,
- }
- if (!publicKey) {
- return (
-
- )
- }
-
- const colors = getColors(publicKey)
- const circles = getPositions(sixPoint)
-
- return (
-
- )
-}
-
-const SIZE = 64
-const CENTER = SIZE / 2
-
-const z = (SIZE / 64) * 5
-type Schema = { freq: number; colors: number[] }
-const schema: Record = {
- target: {
- freq: 1,
- colors: [0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 1],
- },
- cube: {
- freq: 20,
- colors: [0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 5],
- },
- quazar: {
- freq: 16,
- colors: [1, 2, 3, 1, 2, 4, 5, 5, 4, 1, 2, 3, 1, 2, 4, 5, 5, 4, 0],
- },
- flower: {
- freq: 32,
- colors: [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 3],
- },
- cyclic: {
- freq: 32,
- colors: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6],
- },
- vmirror: {
- freq: 128,
- colors: [0, 1, 2, 3, 4, 5, 3, 4, 2, 0, 1, 6, 7, 8, 9, 7, 8, 6, 10],
- },
- hmirror: {
- freq: 128,
- colors: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 8, 6, 7, 5, 3, 4, 2, 11],
- },
-}
-const totalFreq = Object.keys(schema)
- .map((k) => schema[k].freq)
- .reduce((a, b) => a + b)
-
-const findScheme = (d: number) => {
- let cum = 0
- for (const scheme of Object.values(schema)) {
- cum += scheme.freq
- if (d < cum) {
- return scheme
- }
- }
- throw "Unreachable"
-}
-const blake2512 = (input: Uint8Array) => blake2b(input, { dkLen: 64 })
-const zero = blake2512(
- new Uint8Array([
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,
- ]),
-)
-
-const getColors = (publicKey: Uint8Array) => {
- const id = Array.from(blake2512(publicKey)).map(
- (x, i) => (x + 256 - zero[i]) % 256,
- )
-
- const sat = (Math.floor((id[29] * 70) / 256 + 26) % 80) + 30
- const d = Math.floor((id[30] + id[31] * 256) % totalFreq)
- const scheme = findScheme(d)
- const palette = Array.from(id).map((x, i) => {
- const b = (x + (i % 28) * 58) % 256
- if (b == 0) {
- return "#444"
- }
- if (b == 255) {
- return "transparent"
- }
- const h = Math.floor(((b % 64) * 360) / 64)
- const l = [53, 15, 35, 75][Math.floor(b / 64)]
- return `hsl(${h}, ${sat}%, ${l}%)`
- })
-
- const rot = (id[28] % 6) * 3
-
- return scheme.colors.map(
- (_, i) => palette[scheme.colors[i < 18 ? (i + rot) % 18 : 18]],
- )
-}
-
-const getPositions = (sixPoint = false) => {
- const r = sixPoint ? (SIZE / 2 / 8) * 5 : (SIZE / 2 / 4) * 3
- const rroot3o2 = (r * Math.sqrt(3)) / 2
- const ro2 = r / 2
- const rroot3o4 = (r * Math.sqrt(3)) / 4
- const ro4 = r / 4
- const r3o4 = (r * 3) / 4
-
- return [
- { x: CENTER, y: CENTER - r },
- { x: CENTER, y: CENTER - ro2 },
- { x: CENTER - rroot3o4, y: CENTER - r3o4 },
- { x: CENTER - rroot3o2, y: CENTER - ro2 },
- { x: CENTER - rroot3o4, y: CENTER - ro4 },
- { x: CENTER - rroot3o2, y: CENTER },
- { x: CENTER - rroot3o2, y: CENTER + ro2 },
- { x: CENTER - rroot3o4, y: CENTER + ro4 },
- { x: CENTER - rroot3o4, y: CENTER + r3o4 },
- { x: CENTER, y: CENTER + r },
- { x: CENTER, y: CENTER + ro2 },
- { x: CENTER + rroot3o4, y: CENTER + r3o4 },
- { x: CENTER + rroot3o2, y: CENTER + ro2 },
- { x: CENTER + rroot3o4, y: CENTER + ro4 },
- { x: CENTER + rroot3o2, y: CENTER },
- { x: CENTER + rroot3o2, y: CENTER - ro2 },
- { x: CENTER + rroot3o4, y: CENTER - ro4 },
- { x: CENTER + rroot3o4, y: CENTER - r3o4 },
- { x: CENTER, y: CENTER },
- ]
-}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index 20bdfc0..a59ecaf 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -1,71 +1 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
-const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
- destructive:
- "bg-destructive text-destructive-foreground hover:bg-destructive/90",
- outline:
- "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
- secondary:
- "border bg-secondary text-secondary-foreground hover:bg-secondary/80",
- ghost: "hover:bg-accent hover:text-accent-foreground",
- link: "text-primary underline-offset-4 hover:underline",
- },
- size: {
- default: "h-10 px-4 py-2",
- sm: "h-9 rounded-md px-3",
- lg: "h-11 rounded-md px-8",
- icon: "h-10 w-10",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- },
-)
-
-export interface ButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
- asChild?: boolean
- forceSvgSize?: boolean
-}
-
-const Button = React.forwardRef(
- (
- {
- className,
- variant,
- size,
- asChild = false,
- forceSvgSize = true,
- ...props
- },
- ref,
- ) => {
- const Comp = asChild ? Slot : "button"
- return (
-
- )
- },
-)
-Button.displayName = "Button"
-
-export { Button, buttonVariants }
+export { Button } from "@polkahub/ui-components"
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
index b26d306..2065286 100644
--- a/src/components/ui/command.tsx
+++ b/src/components/ui/command.tsx
@@ -1,157 +1,8 @@
-import * as React from "react"
-import { type DialogProps } from "@radix-ui/react-dialog"
-import { Command as CommandPrimitive } from "cmdk"
-import { Search } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-import { Dialog, DialogContent } from "@/components/ui/dialog"
-
-const Command = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-Command.displayName = CommandPrimitive.displayName
-
-interface CommandDialogProps extends DialogProps {}
-
-const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
- return (
-
- )
-}
-
-const CommandInput = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-
-))
-
-CommandInput.displayName = CommandPrimitive.Input.displayName
-
-const CommandList = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-
-CommandList.displayName = CommandPrimitive.List.displayName
-
-const CommandEmpty = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->((props, ref) => (
-
-))
-
-CommandEmpty.displayName = CommandPrimitive.Empty.displayName
-
-const CommandGroup = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-
-CommandGroup.displayName = CommandPrimitive.Group.displayName
-
-const CommandSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-CommandSeparator.displayName = CommandPrimitive.Separator.displayName
-
-const CommandItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- forceSvgSize?: boolean
- }
->(({ className, forceSvgSize = true, ...props }, ref) => (
-
-))
-
-CommandItem.displayName = CommandPrimitive.Item.displayName
-
-const CommandShortcut = ({
- className,
- ...props
-}: React.HTMLAttributes) => {
- return (
-
- )
-}
-CommandShortcut.displayName = "CommandShortcut"
-
export {
Command,
- CommandDialog,
+ CommandGroup,
CommandInput,
+ CommandItem,
CommandList,
CommandEmpty,
- CommandGroup,
- CommandItem,
- CommandShortcut,
- CommandSeparator,
-}
+} from "@polkahub/ui-components"
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
index cedce26..5c39b36 100644
--- a/src/components/ui/dialog.tsx
+++ b/src/components/ui/dialog.tsx
@@ -1,120 +1,20 @@
-import * as React from "react"
-import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { X } from "lucide-react"
-
import { cn } from "@/lib/utils"
-
-const Dialog = DialogPrimitive.Root
-
-const DialogTrigger = DialogPrimitive.Trigger
-
-const DialogPortal = DialogPrimitive.Portal
-
-const DialogClose = DialogPrimitive.Close
-
-const DialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
-
-const DialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
-
- {children}
-
-
- Close
-
-
-
-))
-DialogContent.displayName = DialogPrimitive.Content.displayName
-
-const DialogHeader = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-)
-DialogHeader.displayName = "DialogHeader"
-
-const DialogFooter = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-)
-DialogFooter.displayName = "DialogFooter"
-
-const DialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-DialogTitle.displayName = DialogPrimitive.Title.displayName
-
-const DialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-DialogDescription.displayName = DialogPrimitive.Description.displayName
-
export {
Dialog,
- DialogPortal,
- DialogOverlay,
- DialogClose,
- DialogTrigger,
DialogContent,
DialogHeader,
- DialogFooter,
DialogTitle,
- DialogDescription,
+ DialogTrigger,
+} from "@polkahub/ui-components"
+
+export function DialogBody({
+ children,
+ id,
+ className,
+}: React.PropsWithChildren<{ id?: string; className?: string }>) {
+ return (
+
+ {children}
+
+ )
}
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
index c5270e7..d5264bd 100644
--- a/src/components/ui/popover.tsx
+++ b/src/components/ui/popover.tsx
@@ -1,29 +1,5 @@
-import * as React from "react"
-import * as PopoverPrimitive from "@radix-ui/react-popover"
-
-import { cn } from "@/lib/utils"
-
-const Popover = PopoverPrimitive.Root
-
-const PopoverTrigger = PopoverPrimitive.Trigger
-
-const PopoverContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
-
-
-
-))
-PopoverContent.displayName = PopoverPrimitive.Content.displayName
-
-export { Popover, PopoverTrigger, PopoverContent }
+export {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@polkahub/ui-components"
diff --git a/src/index.css b/src/index.css
index f0950ab..2b261d8 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,4 +1,5 @@
@import 'tailwindcss';
+@import "polkahub";
@custom-variant dark (&:is(.dark *));
@@ -73,7 +74,7 @@
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0 / 50%);
+ --input: oklch(0.922 0 0 / 70%);
--ring: oklch(0.708 0 0 / 50%);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
@@ -106,8 +107,8 @@
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 8%);
- --input: oklch(1 0 0 / 5%);
+ --border: oklch(1 0 0 / 12%);
+ --input: oklch(1 0 0 / 10%);
--ring: oklch(0.556 0 0 / 50%);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
diff --git a/src/main.tsx b/src/main.tsx
index ce4489d..6a95d9d 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,5 +1,8 @@
+import { Buffer } from "buffer"
+globalThis.Buffer = Buffer
import { dynamicBuilder$ } from "@/state/chains/chain.state"
import { RemoveSubscribe, Subscribe } from "@react-rxjs/core"
+import { PolkaHubProvider } from "polkahub"
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { BrowserRouter } from "react-router-dom"
@@ -10,6 +13,7 @@ import "./index.css"
import { explorer$ } from "./pages/Explorer"
import { storage$ } from "./pages/Storage/storage.state.ts"
import { transactions$ } from "./pages/Transactions"
+import { polkaHub } from "./state/polkahub.ts"
import { ThemeProvider } from "./ThemeProvider.tsx"
createRoot(document.getElementById("root")!).render(
@@ -19,11 +23,13 @@ createRoot(document.getElementById("root")!).render(
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/pages/Accounts/AccountList.tsx b/src/pages/Accounts/AccountList.tsx
index 8306d89..1965cae 100644
--- a/src/pages/Accounts/AccountList.tsx
+++ b/src/pages/Accounts/AccountList.tsx
@@ -1,12 +1,6 @@
import { AccountIdDisplay } from "@/components/AccountIdDisplay"
import { TokenAmount } from "@/components/TokenAmount"
import { Button } from "@/components/ui/button"
-import {
- Account,
- accounts$,
- AccountSource,
- accountSourceTypeToName,
-} from "@/state/accounts.state"
import { chainProperties$ } from "@/state/chain-props.state"
import { client$ } from "@/state/chains/chain.state"
import { MultiAddress, polkadot_people } from "@polkadot-api/descriptors"
@@ -18,17 +12,54 @@ import {
} from "@react-rxjs/core"
import { Send } from "lucide-react"
import { CompatibilityLevel, SS58String } from "polkadot-api"
+import {
+ Account,
+ ledgerProviderId,
+ PjsWalletAccount,
+ polkadotVaultProviderId,
+ readOnlyProviderId,
+ useAvailableAccounts,
+ walletConnectProviderId,
+} from "polkahub"
import { FC } from "react"
import { Link } from "react-router-dom"
import { catchError, combineLatest, map, switchMap } from "rxjs"
-import { knownExtensions } from "./Providers"
+
+const knownGroupsNames: Record = {
+ "pjs-wallet": "Browser Extensions",
+ [ledgerProviderId]: "Ledger",
+ [readOnlyProviderId]: "Read Only",
+ [polkadotVaultProviderId]: "Polkadot Vault",
+ [walletConnectProviderId]: "Wallet Connect",
+}
+const knownGroupsColors: Record = {
+ "pjs-wallet": "var(--color-lime-700)",
+ [readOnlyProviderId]: "var(--color-orange-700)",
+ [walletConnectProviderId]: "var(--color-blue-700)",
+}
export const AccountList = () => {
- const accounts = useStateObservable(accounts$)
+ const accountGroups = useAvailableAccounts()
return (
Accounts
+ {Object.entries(accountGroups)
+ .filter(([_, accounts]) => accounts.length > 0)
+ .map(([id, accounts]) => (
+
+ ))}
+
+ )
+}
+
+const AccountGroup: FC<{ id: string; accounts: Account[] }> = ({
+ id,
+ accounts,
+}) => {
+ return (
+
+
{getGroupName(id)}
{accounts.map((account, i) => (
@@ -45,24 +76,25 @@ const AccountCard: FC<{
-
- {account.accountId.startsWith("0x") ? null : (
-
+ {account.address.startsWith("0x") ? null : (
+
)}
)
}
-
-const sourceColors: Record = {
- extension: "var(--color-lime-700)",
- walletconnect: "var(--color-blue-700)",
- readonly: "var(--color-orange-700)",
+export const knownExtensions: Record = {
+ "polkadot-js": "Polkadot JS",
+ "nova-wallet": "Nova Wallet",
+ talisman: "Talisman",
+ "subwallet-js": "Subwallet",
}
-const getExtensionName = (id: string) => knownExtensions[id]?.name ?? id
+const getExtensionName = (id: string) => knownExtensions[id] ?? id
+const getGroupName = (id: string) => knownGroupsNames[id] ?? id
const SourceTag: FC<{
account: Account
@@ -70,14 +102,18 @@ const SourceTag: FC<{
return (
- {account.type === "extension"
- ? getExtensionName(account.extensionId)
- : accountSourceTypeToName[account.type]}
+ {account.provider === "pjs-wallet"
+ ? getExtensionName((account as PjsWalletAccount).extensionId)
+ : getGroupName(account.provider)}
)
}
diff --git a/src/pages/Accounts/AddressProvider.tsx b/src/pages/Accounts/AddressProvider.tsx
deleted file mode 100644
index 4ab775a..0000000
--- a/src/pages/Accounts/AddressProvider.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { AccountIdDisplay } from "@/components/AccountIdDisplay"
-import { AccountIdInput } from "@/components/AccountIdInput"
-import { Button } from "@/components/ui/button"
-import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"
-import { readOnlyAccounts$, setAddresses } from "@/state/accounts.state"
-import { useStateObservable } from "@react-rxjs/core"
-import { Eye, Trash2 } from "lucide-react"
-import { SS58String } from "polkadot-api"
-import { useState } from "react"
-import { SourceButton } from "./SourceButton"
-
-export const AddressProvider = () => (
-
-)
-
-const ManageAddresses = () => {
- const [addressInput, setAddressInput] = useState(null)
- const accounts = useStateObservable(readOnlyAccounts$)
-
- return (
-
-
- {accounts.length ? (
-
-
Added addresses
-
- {accounts.map((account) => (
- -
-
-
-
- ))}
-
-
- ) : null}
-
- )
-}
diff --git a/src/pages/Accounts/Providers.tsx b/src/pages/Accounts/Providers.tsx
index edbc60e..38ad4af 100644
--- a/src/pages/Accounts/Providers.tsx
+++ b/src/pages/Accounts/Providers.tsx
@@ -1,131 +1,83 @@
-import { Spinner } from "@/components/Icons"
import {
- availableExtensions$,
- onToggleExtension,
- selectedExtensions$,
-} from "@/state/extension-accounts.state"
-import { state, useStateObservable } from "@react-rxjs/core"
-import { CircleQuestionMark } from "lucide-react"
-import { FC } from "react"
-import { defer, from, switchMap } from "rxjs"
-import { AddressProvider } from "./AddressProvider"
-import { SourceButton } from "./SourceButton"
+ Button,
+ Dialog,
+ DialogBody,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "@polkahub/ui-components"
+import { ChevronLeft } from "lucide-react"
+import {
+ ManageLedger,
+ ManageReadOnly,
+ ManageVault,
+ ModalContext,
+ PjsWalletButtons,
+ usePolkaHubModalState,
+ WalletConnectButton,
+} from "polkahub"
+import { FC, PropsWithChildren } from "react"
-const lazyWalletConnectStatus$ = state(
- defer(() =>
- from(import("@/state/walletconnect.state")).pipe(
- switchMap(({ walletConnectStatus$ }) => walletConnectStatus$),
- ),
- ),
- {
- type: "disconnected",
- },
-)
-
-export const knownExtensions: Record = {
- "polkadot-js": {
- name: "Polkadot JS",
- logo: import.meta.env.BASE_URL + "providers/polkadotjs.webp",
- },
- "nova-wallet": {
- name: "Nova Wallet",
- logo: import.meta.env.BASE_URL + "providers/novawallet.webp",
- },
- talisman: {
- name: "Talisman",
- logo: import.meta.env.BASE_URL + "providers/talisman.webp",
- },
- "subwallet-js": {
- name: "Subwallet",
- logo: import.meta.env.BASE_URL + "providers/subwallet.webp",
- },
-}
-
-export const Providers = () => {
- const availableExtensions = useStateObservable(availableExtensions$).sort(
- (a, b) => (b in knownExtensions ? 1 : 0) - (a in knownExtensions ? 1 : 0),
- )
- const walletConnectStatus = useStateObservable(lazyWalletConnectStatus$)
+const ManageToDialog: FC = ({ children }) => {
+ const { contentStack, contextValue, setContentStack } =
+ usePolkaHubModalState()
+ const activeContent = contentStack.length
+ ? contentStack[contentStack.length - 1]
+ : null
return (
-
-
Account Providers
-
- {availableExtensions.map((id) => (
- -
-
-
- ))}
-
-
- {/*
-
- */}
- {/*
-
- */}
- {
- const { toggleWalletConnect } = await import(
- "@/state/walletconnect.state"
- )
- toggleWalletConnect()
+ <>
+ {children}
+
+
+ {contentStack.length > 1 ? (
+
+ ) : null}
+ {activeContent?.title}
+
+
+
+ {activeContent?.element}
+
+
+
+
+ >
+ )
+}
+
+export const Providers = () => (
+
+
Account Providers
+
+
+
-
- )
-}
-
-const ExtensionButton: FC<{
- id: string
-}> = ({ id }) => {
- const knownExtension = knownExtensions[id]
- const connectedExtensions = Array.from(
- useStateObservable(selectedExtensions$).keys(),
- )
- const isSelected = connectedExtensions.includes(id)
-
- return (
- onToggleExtension(id)}
- >
- {knownExtension ? (
-
- ) : (
-
-
-
- )}
-
- )
-}
+
+
+)
diff --git a/src/pages/Accounts/SourceButton.tsx b/src/pages/Accounts/SourceButton.tsx
deleted file mode 100644
index e34aeb8..0000000
--- a/src/pages/Accounts/SourceButton.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Button } from "@/components/ui/button"
-import { cn } from "@/utils"
-import { FC, MouseEvent, PropsWithChildren } from "react"
-
-export const SourceButton: FC<
- PropsWithChildren<{
- label: string
- isSelected?: boolean
- className?: string
- onClick?: (evt: MouseEvent) => void
- disabled?: boolean
- }>
-> = ({ label, isSelected, onClick, className, children, disabled }) => (
-
-)
diff --git a/src/pages/Extrinsics/SubmitTx/AccountProvider.tsx b/src/pages/Extrinsics/SubmitTx/AccountProvider.tsx
deleted file mode 100644
index 3ae06e0..0000000
--- a/src/pages/Extrinsics/SubmitTx/AccountProvider.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import { AccountIdDisplay } from "@/components/AccountIdDisplay"
-import { EthAccountDisplay } from "@/components/EthAccountDisplay"
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
-import { groupBy } from "@/lib/groupBy"
-import { knownExtensions } from "@/pages/Accounts/Providers"
-import {
- Account,
- accounts$,
- AccountSource,
- accountSourceTypeToName,
-} from "@/state/accounts.state"
-import { selectedExtensions$ } from "@/state/extension-accounts.state"
-import { walletConnectStatus$ } from "@/state/walletconnect.state"
-import { state, useStateObservable, withDefault } from "@react-rxjs/core"
-import { createSignal } from "@react-rxjs/utils"
-import React from "react"
-import {
- combineLatest,
- defer,
- distinctUntilChanged,
- map,
- merge,
- of,
- tap,
-} from "rxjs"
-
-const [valueSelected$, selectValue] = createSignal()
-
-const LS_KEY = "selected-signer"
-const selectedValue$ = state(
- merge(
- defer(() => of(localStorage.getItem(LS_KEY))),
- valueSelected$.pipe(tap((v) => localStorage.setItem(LS_KEY, v))),
- ),
- null,
-)
-
-const allAccounts$ = accounts$.pipeState(
- map((v) =>
- v.map((acc) => ({
- ...acc,
- id: `${acc.accountId}-${acc.type === "extension" ? acc.extensionId : acc.type}`,
- })),
- ),
- withDefault([]),
-)
-
-export const selectedAccount$ = state(
- combineLatest([selectedValue$, allAccounts$]).pipe(
- map(([selectedAccount, accounts]): Account | null => {
- if (!selectedAccount) return null
- const account = accounts.find((account) => account.id === selectedAccount)
-
- return account ?? null
- }),
- distinctUntilChanged(),
- ),
- null,
-)
-
-const groupedAccounts$ = allAccounts$.pipeState(
- map((v) =>
- groupBy(
- // Accept only signer accounts for submitting transactions
- v.filter((v) => v.signer != null),
- (acc) => (acc.type === "extension" ? acc.extensionId : acc.type),
- ),
- ),
-)
-
-const groupIdToName = (groupId: string) => {
- if (groupId in knownExtensions) {
- return knownExtensions[groupId].name
- }
- return accountSourceTypeToName[groupId as AccountSource] ?? groupId
-}
-
-export const AccountProvider: React.FC = () => {
- const value = useStateObservable(selectedValue$)
- const extensions = useStateObservable(selectedExtensions$)
- const walletConnect = useStateObservable(walletConnectStatus$)
- const allAccounts = useStateObservable(allAccounts$)
- const groupedAccounts = useStateObservable(groupedAccounts$)
-
- const activeExtensions = [...extensions.values()].filter((v) => !!v)
- const valueExists = value && allAccounts.some((acc) => acc.id === value)
-
- if (!activeExtensions.length && walletConnect.type !== "connected")
- return null
-
- return (
-
- )
-}
diff --git a/src/pages/Extrinsics/SubmitTx/ExtensionProvider.tsx b/src/pages/Extrinsics/SubmitTx/ExtensionProvider.tsx
index 4c8b4b2..82829eb 100644
--- a/src/pages/Extrinsics/SubmitTx/ExtensionProvider.tsx
+++ b/src/pages/Extrinsics/SubmitTx/ExtensionProvider.tsx
@@ -2,14 +2,14 @@ import { Label } from "@/components/ui/label"
import { TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
availableExtensions$,
- onToggleExtension,
selectedExtensions$,
-} from "@/state/extension-accounts.state"
+ toggleExtension,
+} from "@/state/polkahub"
import { useStateObservable } from "@react-rxjs/core"
export const ExtensionProvider: React.FC = () => {
const availableExtensions = useStateObservable(availableExtensions$)
- const selectedExtensions = useStateObservable(selectedExtensions$)
+ const selectedExtensions = new Set(useStateObservable(selectedExtensions$))
return (
<>
@@ -18,7 +18,7 @@ export const ExtensionProvider: React.FC = () => {
{availableExtensions.map((extensionName) => (
onToggleExtension(extensionName)}
+ onClick={() => toggleExtension(extensionName)}
active={selectedExtensions.has(extensionName)}
key={extensionName}
>
diff --git a/src/pages/Extrinsics/SubmitTx/SubmitTx.tsx b/src/pages/Extrinsics/SubmitTx/SubmitTx.tsx
index 6df215b..3a5cccc 100644
--- a/src/pages/Extrinsics/SubmitTx/SubmitTx.tsx
+++ b/src/pages/Extrinsics/SubmitTx/SubmitTx.tsx
@@ -1,7 +1,9 @@
import { ActionButton } from "@/components/ActionButton"
import {
Dialog,
+ DialogBody,
DialogContent,
+ DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
@@ -30,17 +32,21 @@ export const ExtrinsicModal: React.FC<{
)
evt.preventDefault()
}}
- className="flex flex-col overflow-hidden"
+ className="flex flex-col overflow-hidden max-w-lg sm:max-w-lg md:max-w-lg"
>
- Create TX
-
- setOpen(false)}
- />
-
+
+ Create TX
+
+
+
+ setOpen(false)}
+ />
+
+
)
diff --git a/src/pages/Extrinsics/SubmitTx/SubmitTxForm.tsx b/src/pages/Extrinsics/SubmitTx/SubmitTxForm.tsx
index c366d2e..ef0cca7 100644
--- a/src/pages/Extrinsics/SubmitTx/SubmitTxForm.tsx
+++ b/src/pages/Extrinsics/SubmitTx/SubmitTxForm.tsx
@@ -1,15 +1,22 @@
import { ActionButton } from "@/components/ActionButton"
import { Spinner } from "@/components/Icons"
+import { cn } from "@/lib/utils"
import { trackSignedTx, trackUnsignedTx } from "@/pages/Transactions"
import { unsafeApi$ } from "@/state/chains/chain.state"
+import { selectedAccount$ } from "@/state/polkahub"
import { compactNumber } from "@polkadot-api/substrate-bindings"
import { fromHex, mergeUint8, toHex } from "@polkadot-api/utils"
+import { AccountPicker } from "@polkahub/ui-components"
import { useStateObservable } from "@react-rxjs/core"
import { Binary } from "polkadot-api"
+import {
+ AddressIdentity,
+ useAvailableAccounts,
+ useSelectedAccount,
+} from "polkahub"
import { FC, useState } from "react"
import { firstValueFrom } from "rxjs"
import { twMerge } from "tailwind-merge"
-import { AccountProvider, selectedAccount$ } from "./AccountProvider"
import { ExtensionProvider } from "./ExtensionProvider"
const SignAndSubmit: FC<{ callData: string; onClose: () => void }> = ({
@@ -77,7 +84,7 @@ export default function SubmitTxF(props: {
return (
<>
-
+
@@ -85,3 +92,47 @@ export default function SubmitTxF(props: {
>
)
}
+
+const groupLabels: Record
= {
+ ledger: "Ledger",
+ readonly: "Read Only",
+ "polkadot-vault": "Vault",
+ walletconnect: "Wallet Connect",
+}
+
+const SelectAccount: FC<{
+ className?: string
+}> = ({ className }) => {
+ const availableAccounts = useAvailableAccounts()
+ const [account, setAccount] = useSelectedAccount()
+
+ const groups = Object.entries(availableAccounts)
+ .map(
+ ([group, accounts]) =>
+ [group, accounts.filter((acc) => acc.signer)] as const,
+ )
+ .filter(([, accounts]) => accounts.length > 0)
+ .map(([key, accounts]) => ({
+ name: groupLabels[key] ?? key,
+ accounts,
+ }))
+
+ if (!groups.length && !account) return null
+
+ return (
+ (
+
+ )}
+ disableClear
+ />
+ )
+}
diff --git a/src/pages/Network/Network.tsx b/src/pages/Network/Network.tsx
index 3e7ad45..dc6b3a9 100644
--- a/src/pages/Network/Network.tsx
+++ b/src/pages/Network/Network.tsx
@@ -12,6 +12,7 @@ import {
} from "@/components/ui/command"
import {
Dialog,
+ DialogBody,
DialogContent,
DialogHeader,
DialogTitle,
@@ -119,7 +120,7 @@ const NetworkSwitchDialogContent: FC<{
return (
{
if (
evt.target instanceof HTMLElement &&
@@ -133,131 +134,137 @@ const NetworkSwitchDialogContent: FC<{
Switch Network
-
-
-
-
- No networks found.
-
-
- {networkCategories.map((category) => (
-
- {category.networks.map((network) => (
- handleNetworkSelect(network)}
- value={
- network.display.includes(category.name)
- ? network.display
- : `${category.name} ${network.display}`
- }
- >
-
- {network.display}
-
- ))}
- {category.name === "Custom" && isValidUri(enteredText) ? (
- {
- handleNetworkSelect({
- id: "custom-network",
- lightclient: false,
- endpoints: { custom: enteredText },
- display: enteredText,
- })
- }}
- >
-
- {enteredText}
-
- ) : null}
-
- ))}
-
-
-
-
- {selectedNetwork ? (
-
-
Network: {selectedNetwork.display}
-
-
- {selectedNetwork.lightclient ? (
-
- ) : null}
- {Object.entries(selectedNetwork.endpoints).map(
- ([rpcName, url]) => (
-
- ),
- )}
-
-
-
- ) : null}
- {selectedRpc && selectedRpc !== "light-client" && (
-
-
-
-
-
-
-
+
+
)
}
diff --git a/src/pages/Transactions/Transactions.tsx b/src/pages/Transactions/Transactions.tsx
index 4582105..9cc6d06 100644
--- a/src/pages/Transactions/Transactions.tsx
+++ b/src/pages/Transactions/Transactions.tsx
@@ -28,7 +28,7 @@ export function Transactions() {
return !nTxs ? null : (
-