wip: reset states when switching chain
This commit is contained in:
+51
-51
@@ -1,17 +1,18 @@
|
||||
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
|
||||
import { getObservableClient } from "@polkadot-api/observable-client"
|
||||
import { decAnyMetadata } from "@polkadot-api/substrate-bindings"
|
||||
import {
|
||||
Binary,
|
||||
CodecType,
|
||||
decAnyMetadata,
|
||||
metadata as metadataCodec,
|
||||
V14,
|
||||
V15,
|
||||
} from "@polkadot-api/substrate-bindings"
|
||||
import { createClient as createSubstrateClient } from "@polkadot-api/substrate-client"
|
||||
createClient as createSubstrateClient,
|
||||
JsonRpcProvider,
|
||||
} from "@polkadot-api/substrate-client"
|
||||
import { toHex } from "@polkadot-api/utils"
|
||||
import { state } from "@react-rxjs/core"
|
||||
import { createClient, PolkadotClient } from "polkadot-api"
|
||||
import { sinkSuspense, state, SUSPENSE } from "@react-rxjs/core"
|
||||
import { createSignal } from "@react-rxjs/utils"
|
||||
import { createClient } from "polkadot-api"
|
||||
import { chainSpec as ksmChainSpec } from "polkadot-api/chains/ksmcc3"
|
||||
import { chainSpec as paseoChainSpec } from "polkadot-api/chains/paseo"
|
||||
import { chainSpec } from "polkadot-api/chains/polkadot"
|
||||
import { chainSpec as westendChainSpec } 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"
|
||||
@@ -21,8 +22,9 @@ import SmWorker from "polkadot-api/smoldot/worker?worker"
|
||||
import { getWsProvider } from "polkadot-api/ws-provider/web"
|
||||
import {
|
||||
concat,
|
||||
EMPTY,
|
||||
filter,
|
||||
finalize,
|
||||
from,
|
||||
map,
|
||||
NEVER,
|
||||
Observable,
|
||||
@@ -31,13 +33,8 @@ import {
|
||||
switchMap,
|
||||
tap,
|
||||
} from "rxjs"
|
||||
import polkadotRawNetworks from "./networks/polkadot.json"
|
||||
import ksmRawNetworks from "./networks/kusama.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"
|
||||
import polkadotRawNetworks from "./networks/polkadot.json"
|
||||
|
||||
export type ChainSource = { id: string } & (
|
||||
| {
|
||||
@@ -113,44 +110,46 @@ const relayChains = new Set(["polkadot", "kusama", "westend", "paseo"])
|
||||
const selectedSource$ = selectedChain$.pipe(
|
||||
switchMap(
|
||||
({ endpoint, network }): Observable<ChainSource> | Promise<ChainSource> => {
|
||||
const { id } = network
|
||||
if (endpoint !== "light-client")
|
||||
return of({
|
||||
id,
|
||||
type: "websocket",
|
||||
value: endpoint,
|
||||
} as ChainSource)
|
||||
const { id } = network
|
||||
})
|
||||
if (relayChains.has(id)) {
|
||||
return of({
|
||||
id,
|
||||
type: "chainSpec",
|
||||
value: { chainSpec: id },
|
||||
} as ChainSource)
|
||||
})
|
||||
}
|
||||
return import(`./chainspecs/${id}.ts`).then(({ chainSpec }) => {
|
||||
const parsed = JSON.parse(chainSpec)
|
||||
return {
|
||||
id,
|
||||
type: "chainSpec",
|
||||
value: {
|
||||
chainSpec,
|
||||
relayChain:
|
||||
network.relayChain || parsed.relayChain || parsed.relay_chain,
|
||||
},
|
||||
} as ChainSource
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
type AnyMetadata = CodecType<typeof metadataCodec>
|
||||
|
||||
export const chainClient$ = state(
|
||||
selectedSource$.pipe(
|
||||
map((src) => [src.id, getProvider(src)] as const),
|
||||
switchMap(([id, provider]) => {
|
||||
switchMap(([id, provider], i) => {
|
||||
const substrateClient = createSubstrateClient(provider)
|
||||
const observableClient = getObservableClient(substrateClient)
|
||||
const chainHead = observableClient.chainHead$(2)
|
||||
const client = createClient(provider)
|
||||
return concat(
|
||||
i === 0 ? EMPTY : of(SUSPENSE),
|
||||
of({ id, client, substrateClient, observableClient, chainHead }),
|
||||
NEVER,
|
||||
).pipe(
|
||||
@@ -161,6 +160,7 @@ export const chainClient$ = state(
|
||||
}),
|
||||
)
|
||||
}),
|
||||
sinkSuspense(),
|
||||
),
|
||||
)
|
||||
export const chainHead$ = state(
|
||||
@@ -171,47 +171,47 @@ export const unsafeApi$ = chainClient$.pipeState(
|
||||
map(({ client }) => client.getUnsafeApi()),
|
||||
)
|
||||
|
||||
export const metadata$ = chainClient$.pipeState(
|
||||
switchMap(({ id, client }) => {
|
||||
const metadata = from(getMetadata(client)).pipe(
|
||||
const uncachedRuntimeCtx$ = chainClient$.pipeState(
|
||||
switchMap(({ chainHead }) => chainHead.runtime$),
|
||||
filter((v) => !!v),
|
||||
)
|
||||
export const runtimeCtx$ = chainClient$.pipeState(
|
||||
switchMap(({ id }) => {
|
||||
const cached = localStorage.getItem(`metadata-${id}`)
|
||||
|
||||
const realCtx$ = uncachedRuntimeCtx$.pipe(
|
||||
tap((v) => {
|
||||
localStorage.setItem(`metadata-${id}`, toHex(metadataCodec.enc(v)))
|
||||
localStorage.setItem(`metadata-${id}`, toHex(v.metadataRaw))
|
||||
}),
|
||||
)
|
||||
const cached = localStorage.getItem(`metadata-${id}`)
|
||||
|
||||
if (cached) {
|
||||
return from(metadata).pipe(startWith(decAnyMetadata(cached)))
|
||||
const metadata = decAnyMetadata(cached)
|
||||
const lookup = getLookupFn(metadata.metadata.value as any)
|
||||
const dynamicBuilder = getDynamicBuilder(lookup)
|
||||
|
||||
return realCtx$.pipe(
|
||||
startWith({
|
||||
lookup,
|
||||
dynamicBuilder,
|
||||
}),
|
||||
)
|
||||
}
|
||||
return from(metadata)
|
||||
}),
|
||||
map((v): V14 | V15 => {
|
||||
const metadata = v.metadata
|
||||
if (metadata.tag === "v14" || metadata.tag === "v15") {
|
||||
return metadata.value
|
||||
}
|
||||
throw new Error("Incompatible metadata")
|
||||
return realCtx$
|
||||
}),
|
||||
)
|
||||
export const lookup$ = metadata$.pipeState(map(getLookupFn))
|
||||
|
||||
export const dynamicBuilder$ = lookup$.pipeState(
|
||||
map((lookup) => ({ lookup, ...getDynamicBuilder(lookup) })),
|
||||
export const lookup$ = runtimeCtx$.pipeState(map((ctx) => ctx.lookup))
|
||||
export const metadata$ = lookup$.pipe(map((lookup) => lookup.metadata))
|
||||
export const dynamicBuilder$ = runtimeCtx$.pipeState(
|
||||
map((ctx) => ctx.dynamicBuilder),
|
||||
)
|
||||
|
||||
async function getMetadata(client: PolkadotClient): Promise<AnyMetadata> {
|
||||
const unsafeApi = client.getUnsafeApi()
|
||||
const versions: number[] = await unsafeApi.apis.Metadata.metadata_versions()
|
||||
const metadataResponse: Binary = await (versions.includes(15)
|
||||
? unsafeApi.apis.Metadata.metadata_at_version(15)
|
||||
: unsafeApi.apis.Metadata.metadata())
|
||||
return decAnyMetadata(metadataResponse.asBytes())
|
||||
}
|
||||
|
||||
let smoldot: {
|
||||
client: Client
|
||||
relayChains: Record<string, Promise<Chain>>
|
||||
} | null = null
|
||||
export function getProvider(source: ChainSource) {
|
||||
export function getProvider(source: ChainSource): JsonRpcProvider {
|
||||
if (source.type === "websocket") {
|
||||
return withPolkadotSdkCompat(getWsProvider(source.value))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { dynamicBuilder$ } from "@/chain.state"
|
||||
import { runtimeCtx$ } from "@/chain.state"
|
||||
import { byteArraysAreEqual } from "@/utils/byteArray"
|
||||
import { CodecComponentType, CodecComponentValue } from "@codec-components"
|
||||
import { state, useStateObservable } from "@react-rxjs/core"
|
||||
@@ -18,10 +18,10 @@ import { BinaryDisplay } from "./BinaryDisplay"
|
||||
import { FocusPath } from "./FocusPath"
|
||||
|
||||
const editTypeMetadataProps$ = state(
|
||||
dynamicBuilder$.pipe(
|
||||
map((builder) => ({
|
||||
builder,
|
||||
metadata: builder.lookup.metadata,
|
||||
runtimeCtx$.pipe(
|
||||
map(({ dynamicBuilder, lookup }) => ({
|
||||
builder: dynamicBuilder,
|
||||
metadata: lookup.metadata,
|
||||
})),
|
||||
),
|
||||
null,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { dynamicBuilder$, lookup$ } from "@/chain.state"
|
||||
import { lookup$, runtimeCtx$ } from "@/chain.state"
|
||||
import { ViewCodec } from "@/codec-components/ViewCodec"
|
||||
import { ButtonGroup } from "@/components/ButtonGroup"
|
||||
import { DocsRenderer } from "@/components/DocsRenderer"
|
||||
@@ -81,10 +81,10 @@ const PalletConstants: FC<{
|
||||
}
|
||||
|
||||
const constantValueProps$ = state(
|
||||
dynamicBuilder$.pipe(
|
||||
map((builder) => ({
|
||||
builder,
|
||||
lookup: builder.lookup,
|
||||
runtimeCtx$.pipe(
|
||||
map(({ dynamicBuilder, lookup }) => ({
|
||||
builder: dynamicBuilder,
|
||||
lookup,
|
||||
})),
|
||||
),
|
||||
null,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { dynamicBuilder$ } from "@/chain.state"
|
||||
import { runtimeCtx$ } from "@/chain.state"
|
||||
import { CircularProgress } from "@/components/CircularProgress"
|
||||
import { groupBy } from "@/lib/groupBy"
|
||||
import { state, useStateObservable } from "@react-rxjs/core"
|
||||
@@ -10,13 +10,14 @@ import { BlockStatusIcon, statusText } from "./Detail/BlockState"
|
||||
import { filterEvt } from "./Events"
|
||||
|
||||
const maxBlockWeight$ = state(
|
||||
dynamicBuilder$.pipe(
|
||||
map((builder) => {
|
||||
const ct = builder.lookup.metadata.pallets
|
||||
runtimeCtx$.pipe(
|
||||
map(({ dynamicBuilder, lookup }) => {
|
||||
const ct = lookup.metadata.pallets
|
||||
.find((p) => p.name === "System")
|
||||
?.constants.find((ct) => ct.name === "BlockWeights")
|
||||
if (!ct) return null
|
||||
return builder.buildDefinition(ct.type).dec(ct.value).max_block as Weight
|
||||
return dynamicBuilder.buildDefinition(ct.type).dec(ct.value)
|
||||
.max_block as Weight
|
||||
}),
|
||||
),
|
||||
null,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { chainHead$, dynamicBuilder$ } from "@/chain.state"
|
||||
import { chainHead$, runtimeCtx$ } from "@/chain.state"
|
||||
import { CircularProgress } from "@/components/CircularProgress"
|
||||
import { state, useStateObservable } from "@react-rxjs/core"
|
||||
import {
|
||||
@@ -20,14 +20,14 @@ const bestBlockTime$ = best$.pipeState(
|
||||
}),
|
||||
)
|
||||
const targetTime$ = state(
|
||||
dynamicBuilder$.pipe(
|
||||
map((builder) => {
|
||||
const ct = builder.lookup.metadata.pallets
|
||||
runtimeCtx$.pipe(
|
||||
map(({ dynamicBuilder, lookup }) => {
|
||||
const ct = lookup.metadata.pallets
|
||||
.find((p) => p.name === "Babe")
|
||||
?.constants.find((ct) => ct.name === "ExpectedBlockTime")
|
||||
if (!ct) return null
|
||||
try {
|
||||
const res = builder.buildDefinition(ct.type).dec(ct.value)
|
||||
const res = dynamicBuilder.buildDefinition(ct.type).dec(ct.value)
|
||||
return Number(res)
|
||||
} catch (_) {
|
||||
return null
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { dynamicBuilder$ } from "@/chain.state"
|
||||
import { runtimeCtx$ } from "@/chain.state"
|
||||
import { AccountIdDisplay } from "@/components/AccountIdDisplay"
|
||||
import { ExpandBtn } from "@/components/Expand"
|
||||
import { JsonDisplay } from "@/components/JsonDisplay"
|
||||
import { Loading } from "@/components/Loading"
|
||||
import { groupBy } from "@/lib/groupBy"
|
||||
import { SystemEvent } from "@polkadot-api/observable-client"
|
||||
import * as Tabs from "@radix-ui/react-tabs"
|
||||
@@ -22,11 +23,12 @@ import { twMerge } from "tailwind-merge"
|
||||
import { blockInfoState$, blocksByHeight$, BlockState } from "../block.state"
|
||||
import { BlockStatusIcon, statusText } from "./BlockState"
|
||||
import { createExtrinsicCodec, DecodedExtrinsic } from "./extrinsicDecoder"
|
||||
import { Loading } from "@/components/Loading"
|
||||
|
||||
const blockExtrinsics$ = state((hash: string) => {
|
||||
const decoder$ = dynamicBuilder$.pipe(
|
||||
map((builder) => createExtrinsicCodec(builder, builder.lookup)),
|
||||
const decoder$ = runtimeCtx$.pipe(
|
||||
map(({ dynamicBuilder, lookup }) =>
|
||||
createExtrinsicCodec(dynamicBuilder, lookup),
|
||||
),
|
||||
)
|
||||
const body$ = blockInfoState$(hash).pipe(
|
||||
filter((v) => !!v),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { dynamicBuilder$ } from "@/chain.state"
|
||||
import { runtimeCtx$ } from "@/chain.state"
|
||||
import { BinaryDisplay } from "@/codec-components/LookupTypeEdit"
|
||||
import { ButtonGroup } from "@/components/ButtonGroup"
|
||||
import { LoadingMetadata } from "@/components/Loading"
|
||||
@@ -13,17 +13,17 @@ import { JsonMode } from "./JsonMode"
|
||||
import { ExtrinsicModal } from "./SubmitTx/SubmitTx"
|
||||
|
||||
const extrinsicProps$ = state(
|
||||
dynamicBuilder$.pipe(
|
||||
map((builder) => {
|
||||
runtimeCtx$.pipe(
|
||||
map(({ dynamicBuilder, lookup }) => {
|
||||
const codecType =
|
||||
"call" in builder.lookup.metadata.extrinsic
|
||||
? builder.lookup.metadata.extrinsic.call
|
||||
"call" in lookup.metadata.extrinsic
|
||||
? lookup.metadata.extrinsic.call
|
||||
: // TODO v14 is this one?
|
||||
builder.lookup.metadata.extrinsic.type
|
||||
lookup.metadata.extrinsic.type
|
||||
return {
|
||||
metadata: builder.lookup.metadata,
|
||||
metadata: lookup.metadata,
|
||||
codecType,
|
||||
codec: builder.buildDefinition(codecType),
|
||||
codec: dynamicBuilder.buildDefinition(codecType),
|
||||
}
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { dynamicBuilder$, unsafeApi$ } from "@/chain.state"
|
||||
import { dynamicBuilder$, runtimeCtx$, unsafeApi$ } from "@/chain.state"
|
||||
import { EditCodec } from "@/codec-components/EditCodec"
|
||||
import { ActionButton } from "@/components/ActionButton"
|
||||
import { BinaryEditButton } from "@/components/BinaryEditButton"
|
||||
@@ -148,7 +148,15 @@ const StorageKeysInput: FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const builderState$ = state(dynamicBuilder$, null)
|
||||
const builderState$ = state(
|
||||
runtimeCtx$.pipe(
|
||||
map((ctx) => ({
|
||||
...ctx.dynamicBuilder,
|
||||
lookup: ctx.lookup,
|
||||
})),
|
||||
),
|
||||
null,
|
||||
)
|
||||
const keyInputValue$ = state(
|
||||
(idx: number) =>
|
||||
keyValues$.pipe(
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Observable } from "rxjs"
|
||||
|
||||
export const withLogs =
|
||||
(msg: string, withSubId?: boolean) =>
|
||||
<T>(base: Observable<T>): Observable<T> => {
|
||||
let nextId = 0
|
||||
return new Observable((observer) => {
|
||||
const id = nextId++
|
||||
const log = (...args: any[]) =>
|
||||
console.log(...(withSubId ? [id, ...args] : args))
|
||||
log(`withLogs(${msg}): subscribed`)
|
||||
const sub = base.subscribe({
|
||||
next(v) {
|
||||
log(`withLogs(${msg}): next`, v)
|
||||
observer.next(v)
|
||||
},
|
||||
error(e) {
|
||||
log(`withLogs(${msg}): error`, e)
|
||||
observer.error(e)
|
||||
},
|
||||
complete() {
|
||||
log(`withLogs(${msg}): complete`)
|
||||
observer.complete()
|
||||
},
|
||||
})
|
||||
|
||||
return () => {
|
||||
log(`withLogs(${msg}): cleanup`)
|
||||
sub.unsubscribe()
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user