This commit is contained in:
Josep M Sobrepere
2024-11-06 14:08:26 +01:00
parent 824689f753
commit b17ca50cc8
28 changed files with 1590 additions and 57 deletions

20
components.json Normal file
View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": false,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

View File

@@ -24,10 +24,14 @@
"@polkadot-api/substrate-bindings": "^0.9.3",
"@polkadot-api/substrate-client": "^0.3.0",
"@polkadot-api/utils": "^0.1.2",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-radio-group": "^1.2.1",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
@@ -40,6 +44,7 @@
"buffer": "^6.0.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "1.0.0",
"lucide-react": "^0.454.0",
"polkadot-api": "^1.7.0",
"react": "^18.3.1",
@@ -52,6 +57,7 @@
"rxjs": "^7.8.1",
"save-as": "^0.1.8",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"uuid": "^11.0.2"
},
"devDependencies": {

View File

@@ -12,11 +12,10 @@ import { createClient as createSubstrateClient } from "@polkadot-api/substrate-c
import { toHex } from "@polkadot-api/utils"
import { state } from "@react-rxjs/core"
import { createClient, PolkadotClient } from "polkadot-api"
import { chainSpec } from "polkadot-api/chains/westend2"
import { withLogsRecorder } from "polkadot-api/logs-provider"
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat"
import { getSmProvider } from "polkadot-api/sm-provider"
import { Client } from "polkadot-api/smoldot"
import { Chain, Client } from "polkadot-api/smoldot"
import { startFromWorker } from "polkadot-api/smoldot/from-worker"
import SmWorker from "polkadot-api/smoldot/worker?worker"
import { getWsProvider } from "polkadot-api/ws-provider/web"
@@ -26,11 +25,18 @@ import {
from,
map,
NEVER,
Observable,
of,
startWith,
switchMap,
tap,
} from "rxjs"
import polkadotRawNetworks from "./pages/Network/polkadot.json"
import { createSignal } from "@react-rxjs/utils"
import { chainSpec } from "polkadot-api/chains/polkadot"
import { chainSpec as ksmChainSpec } from "polkadot-api/chains/ksmcc3"
import { chainSpec as westendChainSpec } from "polkadot-api/chains/westend2"
import { chainSpec as paseoChainSpec } from "polkadot-api/chains/paseo"
export type ChainSource = { id: string } & (
| {
@@ -46,11 +52,75 @@ export type ChainSource = { id: string } & (
}
)
const selectedSource$ = of<ChainSource>({
id: "polkadot",
type: "chainSpec",
value: { chainSpec },
export type Network = {
id: string
display: string
endpoints: Record<string, string>
lightclient: boolean
}
export type NetworkCategory = {
name: string
networks: Network[]
}
export type SelectedChain = {
network: Network
endpoint: string
}
const polkadot = polkadotRawNetworks.map(
(x): Network => ({
endpoints: x.rpcs as any,
lightclient: x.hasChainSpecs,
id: x.id,
display: x.display,
}),
)
export const networkCategories: NetworkCategory[] = [
{
name: "Polkadot",
networks: polkadot,
},
]
export const [selectedChainChanged$, onChangeChain] =
createSignal<SelectedChain>()
export const selectedChain$ = state<SelectedChain>(selectedChainChanged$, {
network: polkadot[0],
endpoint: "light-client",
})
selectedChain$.subscribe()
const relayChains = new Set(["polkadot", "kusama", "westend", "paseo"])
const selectedSource$ = selectedChain$.pipe(
switchMap(
({ endpoint, network }): Observable<ChainSource> | Promise<ChainSource> => {
if (endpoint !== "light-client")
return of({
type: "websocket",
value: endpoint,
} as ChainSource)
const { id } = network
if (relayChains.has(id)) {
return of({
type: "chainSpec",
value: { chainSpec: id },
} as ChainSource)
}
return import(`./chainspecs/${id}.ts`).then(({ chainSpec }) => {
const parsed = JSON.parse(chainSpec)
return {
type: "chainSpec",
value: {
chainSpec,
relayChain: parsed.relayChain || parsed.relay_chain,
},
} as ChainSource
})
},
),
)
type AnyMetadata = CodecType<typeof metadataCodec>
@@ -119,31 +189,48 @@ async function getMetadata(client: PolkadotClient): Promise<AnyMetadata> {
return decAnyMetadata(metadataResponse.asBytes())
}
let smoldot: Client | null = null
export function getProvider(source: ChainSource) {
let smoldot: {
client: Client
relayChains: Record<string, Promise<Chain>>
} | null = null
function getProvider(source: ChainSource) {
if (source.type === "websocket") {
return withPolkadotSdkCompat(getWsProvider(source.value))
}
if (!smoldot) {
smoldot = startFromWorker(new SmWorker(), {
const client = startFromWorker(new SmWorker(), {
logCallback: (level, target, message) => {
console.debug("[%s(%s)] %s", target, level, message)
},
})
smoldot = {
client,
relayChains: {
polkadot: client.addChain({
chainSpec: chainSpec,
}),
kusama: client.addChain({
chainSpec: ksmChainSpec,
}),
westend: client.addChain({
chainSpec: westendChainSpec,
}),
paseo: client.addChain({
chainSpec: paseoChainSpec,
}),
},
}
}
const chain = source.value.relayChain
? smoldot
.addChain({
chainSpec: source.value.relayChain,
? smoldot.relayChains[source.value.relayChain].then((chain) => {
return smoldot!.client.addChain({
chainSpec: source.value.chainSpec,
potentialRelayChains: [chain],
})
.then((chain) =>
smoldot!.addChain({
chainSpec: source.value.chainSpec,
potentialRelayChains: [chain],
}),
)
: smoldot.addChain({
})
: smoldot.relayChains[source.value.chainSpec] ||
smoldot.client.addChain({
chainSpec: source.value.chainSpec,
})

30
src/chainspecs/acala.ts Normal file
View File

@@ -0,0 +1,30 @@
export const chainSpec = JSON.stringify({
name: "Acala",
id: "acala",
chainType: "Live",
bootNodes: [
"/dns/acala-bootnode-4.aca-api.network/tcp/30333/p2p/12D3KooWBLwm4oKY5fsbkdSdipHzYJJHSHhuoyb1eTrH31cidrnY",
"/dns/acala-bootnode-4.aca-api.network/tcp/30334/ws/p2p/12D3KooWBLwm4oKY5fsbkdSdipHzYJJHSHhuoyb1eTrH31cidrnY",
"/dns/acala-bootnode-5.aca-api.network/tcp/80/ws/p2p/12D3KooWN6ZZ2LFSJo2vDci3hqmmcvqMcKJAbREvuYCdvoBvV2D4",
"/dns/acala-bootnode-5.aca-api.network/tcp/443/wss/p2p/12D3KooWN6ZZ2LFSJo2vDci3hqmmcvqMcKJAbREvuYCdvoBvV2D4",
"/dns/acala-bootnode-6.aca-api.network/tcp/80/ws/p2p/12D3KooWEBniruZHpoVj8RUtAFPahaN8UaGP6UtQb5Bdp4MVYbLc",
"/dns/acala-bootnode-6.aca-api.network/tcp/443/wss/p2p/12D3KooWEBniruZHpoVj8RUtAFPahaN8UaGP6UtQb5Bdp4MVYbLc",
"/dns/acala-bootnode-7.aca-api.network/tcp/80/ws/p2p/12D3KooWMq7AtHFx3ZboMT92HQw8BvhZFzJh8UrPCZeMB3yFLe1V",
],
properties: {
ss58Format: 10,
tokenDecimals: [12, 12, 10, 10],
tokenSymbol: ["ACA", "AUSD", "DOT", "LDOT"],
},
relayChain: "polkadot",
paraId: 2000,
consensusEngine: null,
codeSubstitutes: {},
badBlocks: [
"0xa820d0e6b3babb3a7023a229cfe61c32ceb68602f5339e2d416d7fbca5e82aa7",
],
genesis: {
stateRootHash:
"0x010c5745a5d42bcfbe0a644d5a2a4e22e2ff0fd378d48208ecfacea5b7e05a74",
},
})

28
src/chainspecs/ajuna.ts Normal file
View File

@@ -0,0 +1,28 @@
export const chainSpec = JSON.stringify({
name: "Ajuna Polkadot",
id: "ajuna_polkadot",
chainType: "Live",
bootNodes: [
"/dns4/boot-node.helikon.io/tcp/8510/p2p/12D3KooWA1zjoSfN1CWRMY4nRxD94sSnNAj8UkhD7tsh4K6rwYSX",
"/dns4/boot-node.helikon.io/tcp/8512/wss/p2p/12D3KooWA1zjoSfN1CWRMY4nRxD94sSnNAj8UkhD7tsh4K6rwYSX",
"/dns4/node-7135141363928633344-0.p2p.onfinality.io/tcp/20256/ws/p2p/12D3KooWS8wVu6hmDwZvqDXJWnBX5sKb9edR1HWv5hYKD1Ysc2ud",
"/dns4/node-7163299142418038784-0.p2p.onfinality.io/tcp/20806/ws/p2p/12D3KooWKNTJ5zQr3SK2DphVxEzbBWW9wSqd64ruad1uiiZd6E1Y",
"/dns/ajuna.boot.stake.plus/tcp/30332/wss/p2p/12D3KooWRyAHbMPNL7CuQtm987a6iLKftME3eQpVqBC6fUsjWuof",
"/dns/ajuna.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWLXSQdRNS5EXuhpFYiRKHufRsQtb64t4CYj2ah7zx86jA",
"/dns/rpc-para.ajuna.network/tcp/30332/p2p/12D3KooWLFfa4J2T3JGZft74q3Wu6kSYHPJHNzLsVhdLPGbAZ9Wf",
"/dns/rpc-para.ajuna.network/tcp/30333/ws/p2p/12D3KooWLFfa4J2T3JGZft74q3Wu6kSYHPJHNzLsVhdLPGbAZ9Wf",
"/dns/ajuna-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWAPXggzmvxX8pefwGMNXDzUwigHEKCmrwZzMpySJbYwUK",
"/dns/ajuna-polkadot-boot-ng.dwellir.com/tcp/30363/p2p/12D3KooWAPXggzmvxX8pefwGMNXDzUwigHEKCmrwZzMpySJbYwUK",
"/dns/ajuna-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWAPXggzmvxX8pefwGMNXDzUwigHEKCmrwZzMpySJbYwUK",
"/dns/ajuna-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWCfjWNEmYcGJwF8S1xizhSHyFHwwDLmQxQCr2Wu9JUTYw",
"/dns/ajuna-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWCfjWNEmYcGJwF8S1xizhSHyFHwwDLmQxQCr2Wu9JUTYw",
],
properties: { ss58Format: 1328, tokenDecimals: 12, tokenSymbol: "AJUN" },
relay_chain: "polkadot",
para_id: 2051,
codeSubstitutes: {},
genesis: {
stateRootHash:
"0x6e666a1df855628a99876f9f876b94d6d20a397fb1b5d92a3747df75e29c61b1",
},
})

24
src/chainspecs/astar.ts Normal file
View File

@@ -0,0 +1,24 @@
export const chainSpec = JSON.stringify({
name: "Astar",
id: "astar",
chainType: "Live",
bootNodes: [
"/ip4/109.238.14.102/tcp/30333/ws/p2p/12D3KooWPH9bkXRkPcHGKd7DSgYfLLVE1Cw7QNr2M4kKwdVwGZXN",
"/ip4/199.85.208.179/tcp/30333/ws/p2p/12D3KooWMwrAXhSuzCrtiwfVQuB4oLAZj3CLxVzMrqBJB4GKjnQa",
"/ip4/131.153.79.50/tcp/30333/ws/p2p/12D3KooWB2XY9Uw1ZR8qtD5DQ4TKivJkQdqM5c6hyY9ixSDAASBa",
"/dns/bootnode-01.astar.network/tcp/443/wss/p2p/12D3KooWPH9bkXRkPcHGKd7DSgYfLLVE1Cw7QNr2M4kKwdVwGZXN",
"/dns/bootnode-02.astar.network/tcp/443/wss/p2p/12D3KooWMwrAXhSuzCrtiwfVQuB4oLAZj3CLxVzMrqBJB4GKjnQa",
"/dns/bootnode-03.astar.network/tcp/443/wss/p2p/12D3KooWB2XY9Uw1ZR8qtD5DQ4TKivJkQdqM5c6hyY9ixSDAASBa",
"/dns4/astar-bootnode-1-tls.p2p.onfinality.io/tcp/443/wss/p2p/12D3KooWERrQFE8ss7zYfcHp8ULVCc1N7gur7GqZ8ESuZB1Nmioh",
],
properties: { ss58Format: 5, tokenDecimals: 18, tokenSymbol: "ASTR" },
relayChain: "polkadot",
paraId: 2006,
consensusEngine: null,
codeSubstitutes: {},
badBlocks: [],
genesis: {
stateRootHash:
"0xc9451593261d67c47e14c5cbefeeffff5b5a1707cf81800becfc79e6df354da9",
},
})

25
src/chainspecs/hydradx.ts Normal file
View File

@@ -0,0 +1,25 @@
export const chainSpec = JSON.stringify({
name: "Hydration",
id: "hydra",
chainType: "Live",
bootNodes: [
"/dns/p2p-01.hydra.hydradx.io/tcp/30333/p2p/12D3KooWHzv7XVVBwY4EX1aKJBU6qzEjqGk6XtoFagr5wEXx6MsH",
"/dns/p2p-02.hydra.hydradx.io/tcp/30333/p2p/12D3KooWR72FwHrkGNTNes6U5UHQezWLmrKu6b45MvcnRGK8J3S6",
"/dns/p2p-03.hydra.hydradx.io/tcp/30333/p2p/12D3KooWFDwxZinAjgmLVgsideCmdB2bz911YgiQdLEiwKovezUz",
"/dns4/boot.helikon.io/tcp/15120/p2p/12D3KooWDcQY1L2ny3F7YPyP4snCZZYc4eKWgPLEzdBvWBUjH5Yt",
"/dns4/boot.helikon.io/tcp/15125/wss/p2p/12D3KooWDcQY1L2ny3F7YPyP4snCZZYc4eKWgPLEzdBvWBUjH5Yt",
"/dns/hydration.boot.stake.plus/tcp/30332/wss/p2p/12D3KooWGZaDfqPyzVxhA3k1qv72P7xqYTJS8W9U7GWUEdXYhtUU",
"/dns/hydration.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWBJMG8LCh6pLYbGapA3SNzjhQWE87ieGux41jKQrrf5js",
"/dns/hydration-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWCtrMH4H2p5XkGHkU7K4CcbSmErouNuN3j7Bysj4a8hJX",
"/dns/hydration-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWCtrMH4H2p5XkGHkU7K4CcbSmErouNuN3j7Bysj4a8hJX",
],
properties: { tokenDecimals: 12, tokenSymbol: "HDX" },
relay_chain: "polkadot",
para_id: 2034,
consensusEngine: null,
codeSubstitutes: {},
genesis: {
stateRootHash:
"0x33a542156b00e7dd467e2b7704563abd84f888ccbc6afd6f1a1802a55db1d4de",
},
})

1
src/chainspecs/kusama.ts Normal file
View File

@@ -0,0 +1 @@
export { chainSpec } from "polkadot-api/chains/ksmcc3"

View File

@@ -0,0 +1 @@
export { chainSpec } from "polkadot-api/chains/polkadot"

View File

@@ -0,0 +1 @@
export { chainSpec } from "polkadot-api/chains/polkadot_asset_hub"

View File

@@ -0,0 +1 @@
export { chainSpec } from "polkadot-api/chains/polkadot_bridge_hub"

View File

@@ -0,0 +1 @@
export { chainSpec } from "polkadot-api/chains/polkadot_collectives"

View File

@@ -0,0 +1 @@
export { chainSpec } from "polkadot-api/chains/polkadot_people"

View File

@@ -0,0 +1,56 @@
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -0,0 +1,56 @@
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-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
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:
"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<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,153 @@
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<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View File

@@ -1,6 +1,8 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { cn } from "@/utils"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
@@ -18,7 +20,7 @@ const DialogOverlay = React.forwardRef<
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
className
)}
{...props}
/>
@@ -34,12 +36,16 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background bg-polkadot-900 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className,
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
@@ -52,7 +58,7 @@ const DialogHeader = ({
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className,
className
)}
{...props}
/>
@@ -66,7 +72,7 @@ const DialogFooter = ({
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className,
className
)}
{...props}
/>
@@ -81,7 +87,7 @@ const DialogTitle = React.forwardRef<
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className,
className
)}
{...props}
/>

View File

@@ -0,0 +1,42 @@
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem }

View File

@@ -0,0 +1,46 @@
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

View File

@@ -2,8 +2,9 @@
@tailwind components;
@tailwind utilities;
input, dialog {
@apply bg-black
input,
dialog {
@apply bg-black;
}
:root {
@@ -38,3 +39,66 @@ input, dialog {
opacity: 1;
pointer-events: all;
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

6
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -1,47 +1,55 @@
import { Link } from "react-router-dom"
import { NetworkSwitcher } from "./Network/Network"
export const Header = () => (
<div className="flex p-4 pb-2 items-center flex-shrink-0">
<div className="flex flex-1 items-center flex-row gap-2">
<img className="w-14 min-w-14" src="/papi_logo-dark.svg" alt="papi-logo" />
<h1 className="hidden lg:block poppins-regular text-xl">papi <span className="poppins-extralight">console</span></h1>
<img
className="w-14 min-w-14"
src="/papi_logo-dark.svg"
alt="papi-logo"
/>
<h1 className="hidden lg:block poppins-regular text-xl">
papi <span className="poppins-extralight">console</span>
</h1>
</div>
<NetworkSwitcher />
<div className="flex flex-row items-center justify-end bg-polkadot-800 px-1 py-1 rounded-full">
<Link
to="/explorer"
className="text-polkadot-300 cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
className="cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
>
Explorer
</Link>
<Link
to="/metadata"
className="text-polkadot-300 cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
>
Metadata
</Link>
<Link
to="/storage"
className="text-polkadot-300 cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
className="cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
>
Storage
</Link>
<Link
to="/extrinsics"
className="cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
>
Extrinsics
</Link>
<Link
to="/constants"
className="text-polkadot-300 cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
className="cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
>
Constants
</Link>
<Link
to="/runtimeCalls"
className="text-polkadot-300 cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
className="cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
>
Runtime Calls
</Link>
<Link
to="/extrinsics"
className="text-polkadot-300 cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
to="/metadata"
className="cursor-pointer hover:text-polkadot-0 px-3 py-1 hover:bg-polkadot-500 rounded-full"
>
Extrinsics
Metadata
</Link>
</div>
</div>

View File

@@ -0,0 +1,151 @@
import { useState } from "react"
import { Check, ChevronDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { ScrollArea } from "@/components/ui/scroll-area"
import { useStateObservable } from "@react-rxjs/core"
import {
Network,
networkCategories,
onChangeChain,
selectedChain$,
} from "@/chain.state"
export function NetworkSwitcher() {
const [open, setOpen] = useState(false)
const selectedChain = useStateObservable(selectedChain$)
const [selectedNetwork, setSelectedNetwork] = useState<Network>(
selectedChain.network,
)
const [selectedRpc, setSelecteRpc] = useState<string>(
selectedChain.endpoint ?? "light-client",
)
const handleNetworkSelect = (network: Network) => {
setSelectedNetwork(network)
setSelecteRpc(
network.lightclient
? "light-client"
: Object.values(network.endpoints)[0],
)
}
const handleConfirm = () => {
setOpen(false)
onChangeChain({
network: selectedNetwork,
endpoint: selectedRpc,
})
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" className="w-[200px] justify-between">
{selectedNetwork.display}
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Switch Network</DialogTitle>
</DialogHeader>
<Command className="rounded-lg border shadow-md">
<CommandInput placeholder="Search networks..." />
<CommandList>
<CommandEmpty>No networks found.</CommandEmpty>
<ScrollArea className="h-[260px]">
{networkCategories.map((category) => (
<CommandGroup key={category.name} heading={category.name}>
{category.networks.map((network) => (
<CommandItem
key={network.id}
onSelect={() => handleNetworkSelect(network)}
>
<Check
className={`mr-2 h-4 w-4 ${
selectedNetwork.id === network.id
? "opacity-100"
: "opacity-0"
}`}
/>
{network.display}
</CommandItem>
))}
</CommandGroup>
))}
</ScrollArea>
</CommandList>
</Command>
{selectedNetwork && (
<Accordion type="single" collapsible className="w-full -mt-3">
<AccordionItem value="connection-options">
<AccordionTrigger>Connection Options</AccordionTrigger>
<AccordionContent>
<ScrollArea className="h-[155px] rounded-lg border p-2">
<RadioGroup>
{selectedNetwork.lightclient ? (
<div className="flex items-center space-x-2">
<RadioGroupItem
value="light-client"
id="light-client"
checked={selectedRpc === "light-client"}
onClick={() => setSelecteRpc("light-client")}
/>
<Label htmlFor="light-client">
Light Client (smoldot)
</Label>
</div>
) : null}
{Object.entries(selectedNetwork.endpoints).map(
([rpcName, url]) => (
<div key={url} className="flex items-center space-x-2">
<RadioGroupItem
value={url}
id={url}
checked={selectedRpc === url}
onClick={() => setSelecteRpc(url)}
/>
<Label htmlFor={url}>{rpcName}</Label>
</div>
),
)}
</RadioGroup>
</ScrollArea>
</AccordionContent>
</AccordionItem>
</Accordion>
)}
<Button
onClick={handleConfirm}
disabled={!selectedNetwork || selectedRpc == null}
>
Confirm Selection
</Button>
</DialogContent>
</Dialog>
)
}

View File

@@ -0,0 +1 @@
export * from "./Network"

View File

@@ -0,0 +1,642 @@
[
{
"id": "polkadot",
"display": "Polkadot Relay Chain",
"hasChainSpecs": true,
"rpcs": {
"Allnodes": "wss://polkadot-rpc.publicnode.com",
"Blockops": "wss://polkadot-public-rpc.blockops.network/ws",
"Dwellir": "wss://polkadot-rpc.dwellir.com",
"Dwellir Tunisia": "wss://polkadot-rpc-tn.dwellir.com",
"IBP1": "wss://rpc.ibp.network/polkadot",
"IBP2": "wss://polkadot.dotters.network",
"LuckyFriday": "wss://rpc-polkadot.luckyfriday.io",
"OnFinality": "wss://polkadot.api.onfinality.io/public-ws",
"RadiumBlock": "wss://polkadot.public.curie.radiumblock.co/ws",
"RockX": "wss://rockx-dot.w3node.com/polka-public-dot/ws",
"Stakeworld": "wss://dot-rpc.stakeworld.io",
"SubQuery": "wss://polkadot.rpc.subquery.network/public/ws"
},
"nativeToken": {
"symbol": "DOT",
"decimals": 10
}
},
{
"id": "polkadot_asset_hub",
"display": "AssetHub",
"rpcs": {
"Dwellir": "wss://asset-hub-polkadot-rpc.dwellir.com",
"Dwellir Tunisia": "wss://statemint-rpc-tn.dwellir.com",
"IBP1": "wss://sys.ibp.network/asset-hub-polkadot",
"IBP2": "wss://asset-hub-polkadot.dotters.network",
"LuckyFriday": "wss://rpc-asset-hub-polkadot.luckyfriday.io",
"OnFinality": "wss://statemint.api.onfinality.io/public-ws",
"Parity": "wss://polkadot-asset-hub-rpc.polkadot.io",
"RadiumBlock": "wss://statemint.public.curie.radiumblock.co/ws",
"Stakeworld": "wss://dot-rpc.stakeworld.io/assethub"
},
"relayChainInfo": {
"id": "polkadot",
"isSystem": true,
"parachain": 1000
},
"nativeToken": {
"symbol": "DOT",
"decimals": 10
},
"hasChainSpecs": true
},
{
"id": "polkadot_bridge_hub",
"display": "BridgeHub",
"rpcs": {
"Dwellir": "wss://bridge-hub-polkadot-rpc.dwellir.com",
"Dwellir Tunisia": "wss://polkadot-bridge-hub-rpc-tn.dwellir.com",
"IBP1": "wss://sys.ibp.network/bridgehub-polkadot",
"IBP2": "wss://bridge-hub-polkadot.dotters.network",
"LuckyFriday": "wss://rpc-bridge-hub-polkadot.luckyfriday.io",
"OnFinality": "wss://bridgehub-polkadot.api.onfinality.io/public-ws",
"Parity": "wss://polkadot-bridge-hub-rpc.polkadot.io",
"RadiumBlock": "wss://bridgehub-polkadot.public.curie.radiumblock.co/ws",
"Stakeworld": "wss://dot-rpc.stakeworld.io/bridgehub"
},
"relayChainInfo": {
"id": "polkadot",
"isSystem": true,
"parachain": 1002
},
"nativeToken": {
"symbol": "DOT",
"decimals": 10
},
"hasChainSpecs": true
},
{
"id": "polkadot_collectives",
"display": "Collectives",
"rpcs": {
"Dwellir": "wss://collectives-polkadot-rpc.dwellir.com",
"Dwellir Tunisia": "wss://polkadot-collectives-rpc-tn.dwellir.com",
"IBP1": "wss://sys.ibp.network/collectives-polkadot",
"IBP2": "wss://collectives-polkadot.dotters.network",
"LuckyFriday": "wss://rpc-collectives-polkadot.luckyfriday.io",
"OnFinality": "wss://collectives.api.onfinality.io/public-ws",
"Parity": "wss://polkadot-collectives-rpc.polkadot.io",
"RadiumBlock": "wss://collectives.public.curie.radiumblock.co/ws",
"Stakeworld": "wss://dot-rpc.stakeworld.io/collectives"
},
"relayChainInfo": {
"id": "polkadot",
"isSystem": true,
"parachain": 1001
},
"nativeToken": {
"symbol": "DOT",
"decimals": 10
},
"hasChainSpecs": true
},
{
"id": "polkadot_core_time",
"display": "Coretime",
"rpcs": {
"IBP2": "wss://coretime-polkadot.dotters.network",
"Parity": "wss://polkadot-coretime-rpc.polkadot.io"
},
"relayChainInfo": {
"id": "polkadot",
"isSystem": true,
"parachain": 1005
},
"nativeToken": {
"symbol": "DOT",
"decimals": 10
},
"hasChainSpecs": false
},
{
"id": "polkadot_people",
"display": "People",
"rpcs": {
"IBP1": "wss://sys.ibp.network/people-polkadot",
"IBP2": "wss://people-polkadot.dotters.network",
"LuckyFriday": "wss://rpc-people-polkadot.luckyfriday.io",
"Parity": "wss://polkadot-people-rpc.polkadot.io",
"RadiumBlock": "wss://people-polkadot.public.curie.radiumblock.co/ws"
},
"relayChainInfo": {
"id": "polkadot",
"isSystem": true,
"parachain": 1004
},
"nativeToken": {
"symbol": "DOT",
"decimals": 10
},
"hasChainSpecs": true
},
{
"id": "acala",
"display": "Acala",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2000
},
"rpcs": {
"Dwellir": "wss://acala-rpc.dwellir.com",
"OnFinality": "wss://acala-polkadot.api.onfinality.io/public-ws"
},
"nativeToken": {
"symbol": "ACA",
"decimals": 12
},
"hasChainSpecs": true
},
{
"id": "ajuna",
"display": "Ajuna Network",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2051
},
"rpcs": {
"AjunaNetwork": "wss://rpc-para.ajuna.network",
"IBP1": "wss://ajuna.ibp.network",
"IBP2": "wss://ajuna.dotters.network",
"OnFinality": "wss://ajuna.api.onfinality.io/public-ws",
"RadiumBlock": "wss://ajuna.public.curie.radiumblock.co/ws"
},
"nativeToken": {
"symbol": "AJUN",
"decimals": 12
},
"hasChainSpecs": true
},
{
"id": "astar",
"display": "Astar",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2006
},
"rpcs": {
"Astar": "wss://rpc.astar.network",
"Automata 1RPC": "wss://1rpc.io/astr",
"Dwellir": "wss://astar-rpc.dwellir.com",
"OnFinality": "wss://astar.api.onfinality.io/public-ws",
"RadiumBlock": "wss://astar.public.curie.radiumblock.co/ws"
},
"nativeToken": {
"symbol": "ASTR",
"decimals": 18
},
"hasChainSpecs": true
},
{
"id": "bifrost",
"display": "Bifrost",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2030
},
"rpcs": {
"Dwellir": "wss://bifrost-polkadot-rpc.dwellir.com",
"IBP1": "wss://bifrost-polkadot.ibp.network",
"IBP2": "wss://bifrost-polkadot.dotters.network",
"Liebi": "wss://hk.p.bifrost-rpc.liebi.com/ws",
"LiebiEU": "wss://eu.bifrost-polkadot-rpc.liebi.com/ws",
"RadiumBlock": "wss://bifrost.public.curie.radiumblock.co/ws"
},
"nativeToken": {
"symbol": "BNC",
"decimals": 12
},
"hasChainSpecs": false
},
{
"id": "bitgreen",
"display": "Bitgreen",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2048
},
"rpcs": {
"Bitgreen": "wss://mainnet.bitgreen.org"
},
"nativeToken": {
"symbol": "BBB",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "centrifuge",
"display": "Centrifuge",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2031
},
"rpcs": {
"Centrifuge": "wss://fullnode.centrifuge.io",
"Dwellir": "wss://centrifuge-rpc.dwellir.com",
"LuckyFriday": "wss://rpc-centrifuge.luckyfriday.io",
"OnFinality": "wss://centrifuge-parachain.api.onfinality.io/public-ws"
},
"nativeToken": {
"symbol": "CFG",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "crustParachain",
"display": "Crust",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2008
},
"rpcs": {
"Crust": "wss://crust-parachain.crustapps.net",
"Crust APP": "wss://crust-parachain.crustnetwork.app",
"Crust CC": "wss://crust-parachain.crustnetwork.cc",
"Crust XYZ": "wss://crust-parachain.crustnetwork.xyz"
},
"nativeToken": {
"symbol": "CRU",
"decimals": 12
},
"hasChainSpecs": false
},
{
"id": "darwinia",
"display": "Darwinia",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2046
},
"rpcs": {
"Darwinia": "wss://rpc.darwinia.network",
"Dcdao": "wss://darwinia-rpc.dcdao.box",
"Dwellir": "wss://darwinia-rpc.dwellir.com"
},
"nativeToken": {
"symbol": "RING",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "frequency",
"display": "Frequency",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2091
},
"rpcs": {
"Dwellir": "wss://frequency-rpc.dwellir.com",
"Frequency 0": "wss://0.rpc.frequency.xyz",
"Frequency 1": "wss://1.rpc.frequency.xyz",
"OnFinality": "wss://frequency-polkadot.api.onfinality.io/public-ws"
},
"nativeToken": {
"symbol": "FRQCY",
"decimals": 8
},
"hasChainSpecs": false
},
{
"id": "hydradx",
"display": "Hydration",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2034
},
"rpcs": {
"Dwellir": "wss://hydradx-rpc.dwellir.com",
"Galactic Council": "wss://rpc.hydradx.cloud",
"Helikon": "wss://rpc.helikon.io/hydradx",
"IBP1": "wss://hydradx.paras.ibp.network",
"IBP2": "wss://hydration.dotters.network"
},
"nativeToken": {
"symbol": "HDX",
"decimals": 12
},
"hasChainSpecs": true
},
{
"id": "hyperbridge",
"display": "Hyperbridge (Nexus)",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 3367
},
"rpcs": {
"BlockOps": "wss://hyperbridge-nexus-rpc.blockops.network",
"IBP1": "wss://nexus.ibp.network",
"IBP2": "wss://nexus.dotters.network"
},
"nativeToken": {
"symbol": "BRIDGE",
"decimals": 12
},
"hasChainSpecs": false
},
{
"id": "integritee",
"display": "Integritee Network",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 3359
},
"rpcs": {
"Dwellir": "wss://integritee-rpc.dwellir.com",
"Integritee": "wss://polkadot.api.integritee.network"
},
"nativeToken": {
"symbol": "TEER",
"decimals": 12
},
"hasChainSpecs": false
},
{
"id": "kilt",
"display": "KILT Spiritnet",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2086
},
"rpcs": {
"BOTLabs": "wss://spiritnet.kilt.io/",
"Dwellir": "wss://kilt-rpc.dwellir.com",
"IBP1": "wss://kilt.ibp.network",
"IBP2": "wss://kilt.dotters.network"
},
"nativeToken": {
"symbol": "KILT",
"decimals": 15
},
"hasChainSpecs": false
},
{
"id": "laos",
"display": "Laos",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 3370
},
"rpcs": {
"Dwellir": "wss://laos-rpc.dwellir.com",
"freeverse.io": "wss://rpc.laos.laosfoundation.io"
},
"nativeToken": {
"symbol": "LAOS",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "litentry",
"display": "Litentry",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2013
},
"rpcs": {
"Dwellir": "wss://litentry-rpc.dwellir.com",
"Litentry": "wss://rpc.litentry-parachain.litentry.io"
},
"nativeToken": {
"symbol": "LIT",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "logion",
"display": "Logion",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 3354
},
"rpcs": {
"Logion 1": "wss://para-rpc01.logion.network"
},
"nativeToken": {
"symbol": "LGNT",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "manta",
"display": "Manta",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2104
},
"rpcs": {
"Manta Network": "wss://ws.manta.systems"
},
"nativeToken": {
"symbol": "MANTA",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "moonbeam",
"display": "Moonbeam",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2004
},
"rpcs": {
"Dwellir": "wss://moonbeam-rpc.dwellir.com",
"IBP1": "wss://moonbeam.ibp.network",
"IBP2": "wss://moonbeam.dotters.network",
"Moonbeam Foundation": "wss://wss.api.moonbeam.network",
"OnFinality": "wss://moonbeam.api.onfinality.io/public-ws",
"RadiumBlock": "wss://moonbeam.public.curie.radiumblock.co/ws",
"UnitedBloc": "wss://moonbeam.unitedbloc.com",
"Allnodes": "wss://moonbeam-rpc.publicnode.com"
},
"nativeToken": {
"symbol": "GLMR",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "mythos",
"display": "Mythos",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 3369
},
"rpcs": {
"parity": "wss://polkadot-mythos-rpc.polkadot.io"
},
"nativeToken": {
"symbol": "MYTH",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "neuroweb",
"display": "NeuroWeb",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2043
},
"rpcs": {
"Dwellir": "wss://neuroweb-rpc.dwellir.com",
"TraceLabs": "wss://parachain-rpc.origin-trail.network"
},
"nativeToken": {
"symbol": "NEURO",
"decimals": 12
},
"hasChainSpecs": false
},
{
"id": "nodle",
"display": "Nodle",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2026
},
"rpcs": {
"Dwellir": "wss://nodle-rpc.dwellir.com",
"OnFinality": "wss://nodle-parachain.api.onfinality.io/public-ws"
},
"nativeToken": {
"symbol": "NODL",
"decimals": 11
},
"hasChainSpecs": false
},
{
"id": "pendulum",
"display": "Pendulum",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2094
},
"rpcs": {
"PendulumChain": "wss://rpc-pendulum.prd.pendulumchain.tech"
},
"nativeToken": {
"symbol": "PEN",
"decimals": 12
},
"hasChainSpecs": false
},
{
"id": "phala",
"display": "Phala Network",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2035
},
"rpcs": {
"Dwellir": "wss://phala-rpc.dwellir.com",
"Helikon": "wss://rpc.helikon.io/phala",
"OnFinality": "wss://phala.api.onfinality.io/public-ws",
"Phala": "wss://api.phala.network/ws",
"RadiumBlock": "wss://phala.public.curie.radiumblock.co/ws"
},
"nativeToken": {
"symbol": "PHA",
"decimals": 12
},
"hasChainSpecs": false
},
{
"id": "polimec",
"display": "Polimec",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 3344
},
"rpcs": {
"Amforc": "wss://polimec.rpc.amforc.com",
"Helikon": "wss://rpc.helikon.io/polimec",
"IBP1": "wss://polimec.ibp.network",
"IBP2": "wss://polimec.dotters.network",
"Polimec Foundation": "wss://rpc.polimec.org"
},
"nativeToken": {
"symbol": "PLMC",
"decimals": 10
},
"hasChainSpecs": false
},
{
"id": "unique",
"display": "Unique Network",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2037
},
"rpcs": {
"Dwellir": "wss://unique-rpc.dwellir.com",
"Geo Load Balancer": "wss://ws.unique.network",
"IBP1": "wss://unique.ibp.network",
"IBP2": "wss://unique.dotters.network",
"Unique America": "wss://us-ws.unique.network",
"Unique Asia": "wss://asia-ws.unique.network",
"Unique Europe": "wss://eu-ws.unique.network"
},
"nativeToken": {
"symbol": "UNQ",
"decimals": 18
},
"hasChainSpecs": false
},
{
"id": "zeitgeist",
"display": "Zeitgeist",
"relayChainInfo": {
"id": "polkadot",
"isSystem": false,
"parachain": 2092
},
"rpcs": {
"OnFinality": "wss://zeitgeist.api.onfinality.io/public-ws",
"ZeitgeistPM": "wss://main.rpc.zeitgeist.pm/ws"
},
"nativeToken": {
"symbol": "ZTG",
"decimals": 10
},
"hasChainSpecs": false
}
]

View File

@@ -2,30 +2,99 @@
* @type {import("tailwindcss").Config}
*/
export default {
darkMode: ["class"],
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
polkadot: {
950: "#030110",
900: "#0C052C",
800: "#23126D",
700: "#4027AE",
650: "#6543EF",
600: "#5E39F2",
500: "#E6007A",
400: "#FC76FF",
300: "#DB9EFF",
200: "#E4DAFF",
100: "#F2F3FF",
0: "#FBFCFF",
100: "#F2F3FF",
200: "#E4DAFF",
300: "#DB9EFF",
400: "#FC76FF",
500: "#E6007A",
600: "#5E39F2",
650: "#6543EF",
700: "#4027AE",
800: "#23126D",
900: "#0C052C",
950: "#030110",
},
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
chart: {
1: "hsl(var(--chart-1))",
2: "hsl(var(--chart-2))",
3: "hsl(var(--chart-3))",
4: "hsl(var(--chart-4))",
5: "hsl(var(--chart-5))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: {
height: "0",
},
to: {
height: "var(--radix-accordion-content-height)",
},
},
"accordion-up": {
from: {
height: "var(--radix-accordion-content-height)",
},
to: {
height: "0",
},
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [
function ({ addVariant }) {
addVariant("group-state-open", ':merge(.group)[data-state="open"] &');
addVariant("group-state-open", ':merge(.group)[data-state="open"] &')
},
require("tailwindcss-animate"),
],
};
}

View File

@@ -1,5 +1,6 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],

View File

@@ -3,5 +3,11 @@
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}