From 3c62d19779de01a2c06a3e6b60de278f20b16665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Jedli=C4=8Dka?= Date: Tue, 3 Oct 2023 14:13:06 +0200 Subject: [PATCH] runtime metadata refactoring --- package-lock.json | 48 ++++++-- package.json | 1 + .../update-networks/sources/runtime-spec.ts | 2 +- src/components/DataViewer.tsx | 12 +- src/components/DataViewerValueParsed.tsx | 18 +-- src/components/calls/CallInfoTable.tsx | 10 +- src/components/events/EventInfoTable.tsx | 4 +- src/components/events/EventsTable.tsx | 9 +- .../extrinsics/ExtrinsicInfoTable.tsx | 5 +- src/components/extrinsics/ExtrinsicsTable.tsx | 4 +- src/hooks/useRuntimeMetadataPallets.ts | 23 ++++ src/hooks/useRuntimeSpecVersions.ts | 3 +- src/hooks/useRuntimeSpecs.ts | 11 -- src/model/account.ts | 2 - src/model/balance.ts | 3 - src/model/block.ts | 3 - src/model/call.ts | 7 +- src/model/event.ts | 7 +- src/model/extrinsic.ts | 8 +- .../runtime-metadata/runtimeMetadataArg.ts | 5 + .../runtime-metadata/runtimeMetadataCall.ts | 9 ++ .../runtime-metadata/runtimeMetadataEvent.ts | 9 ++ .../runtime-metadata/runtimeMetadataPallet.ts | 5 + .../runtime-metadata/runtimeMetadataSpec.ts | 4 + src/model/transfer.ts | 3 - src/networks.json | 36 ++++-- src/repositories/runtimeMetadataRepository.ts | 26 +++++ src/screens/runtime.tsx | 64 ++--------- src/services/accountService.ts | 10 +- src/services/balancesService.ts | 7 +- src/services/blocksService.ts | 22 ++-- src/services/callsService.ts | 35 +++--- src/services/eventsService.ts | 74 +++++++----- src/services/extrinsicsService.ts | 68 +++++++---- src/services/runtimeMetadataService.ts | 47 ++++++++ src/services/runtimeService.ts | 97 ---------------- src/services/runtimeSpecService.ts | 35 ++++++ src/services/transfersService.ts | 8 +- src/utils/addMetadata.ts | 35 ++++++ src/utils/addRuntimeSpec.ts | 36 ------ src/utils/decodeMetadata.ts | 78 ------------- src/utils/string.ts | 8 ++ src/utils/webWorker.ts | 48 ++++++++ src/workers/runtimeSpecWorker.runtime.ts | 107 ++++++++++++++++++ src/workers/runtimeSpecWorker.ts | 16 +++ tsconfig.json | 3 +- 46 files changed, 617 insertions(+), 458 deletions(-) create mode 100644 src/hooks/useRuntimeMetadataPallets.ts delete mode 100644 src/hooks/useRuntimeSpecs.ts create mode 100644 src/model/runtime-metadata/runtimeMetadataArg.ts create mode 100644 src/model/runtime-metadata/runtimeMetadataCall.ts create mode 100644 src/model/runtime-metadata/runtimeMetadataEvent.ts create mode 100644 src/model/runtime-metadata/runtimeMetadataPallet.ts create mode 100644 src/model/runtime-metadata/runtimeMetadataSpec.ts create mode 100644 src/repositories/runtimeMetadataRepository.ts create mode 100644 src/services/runtimeMetadataService.ts delete mode 100644 src/services/runtimeService.ts create mode 100644 src/services/runtimeSpecService.ts create mode 100644 src/utils/addMetadata.ts delete mode 100644 src/utils/addRuntimeSpec.ts delete mode 100644 src/utils/decodeMetadata.ts create mode 100644 src/utils/webWorker.ts create mode 100644 src/workers/runtimeSpecWorker.runtime.ts create mode 100644 src/workers/runtimeSpecWorker.ts diff --git a/package-lock.json b/package-lock.json index dc074c56..6c12a442 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "date-fns-tz": "^2.0.0", "decimal.js": "^10.4.3", "deepmerge": "^4.3.1", + "dexie": "^3.2.4", "echarts": "^5.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -13814,6 +13815,14 @@ "node": ">=0.8.0" } }, + "node_modules/dexie": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.4.tgz", + "integrity": "sha512-VKoTQRSv7+RnffpOJ3Dh6ozknBqzWw/F3iqMdsZg958R0AS8AnY9x9d1lbwENr0gzeGJHXKcGhAMRaqys6SxqA==", + "engines": { + "node": ">=6.0" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -16560,11 +16569,6 @@ "postcss": "^8.1.0" } }, - "node_modules/idb": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.5.tgz", - "integrity": "sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw==" - }, "node_modules/identity-obj-proxy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", @@ -26540,6 +26544,11 @@ "workbox-core": "6.5.3" } }, + "node_modules/workbox-background-sync/node_modules/idb": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.5.tgz", + "integrity": "sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw==" + }, "node_modules/workbox-broadcast-update": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.3.tgz", @@ -26701,6 +26710,11 @@ "workbox-core": "6.5.3" } }, + "node_modules/workbox-expiration/node_modules/idb": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.5.tgz", + "integrity": "sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw==" + }, "node_modules/workbox-google-analytics": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.3.tgz", @@ -36989,6 +37003,11 @@ "minimist": "^1.2.6" } }, + "dexie": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.4.tgz", + "integrity": "sha512-VKoTQRSv7+RnffpOJ3Dh6ozknBqzWw/F3iqMdsZg958R0AS8AnY9x9d1lbwENr0gzeGJHXKcGhAMRaqys6SxqA==" + }, "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -39024,11 +39043,6 @@ "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "requires": {} }, - "idb": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.5.tgz", - "integrity": "sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw==" - }, "identity-obj-proxy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", @@ -46166,6 +46180,13 @@ "requires": { "idb": "^6.1.4", "workbox-core": "6.5.3" + }, + "dependencies": { + "idb": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.5.tgz", + "integrity": "sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw==" + } } }, "workbox-broadcast-update": { @@ -46310,6 +46331,13 @@ "requires": { "idb": "^6.1.4", "workbox-core": "6.5.3" + }, + "dependencies": { + "idb": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.5.tgz", + "integrity": "sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw==" + } } }, "workbox-google-analytics": { diff --git a/package.json b/package.json index f805b9ba..5222c378 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "date-fns-tz": "^2.0.0", "decimal.js": "^10.4.3", "deepmerge": "^4.3.1", + "dexie": "^3.2.4", "echarts": "^5.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/scripts/update-networks/sources/runtime-spec.ts b/scripts/update-networks/sources/runtime-spec.ts index 1c8a7ee9..e0a21bb0 100644 --- a/scripts/update-networks/sources/runtime-spec.ts +++ b/scripts/update-networks/sources/runtime-spec.ts @@ -1,6 +1,6 @@ import { Metadata, TypeRegistry } from "@polkadot/types"; -import { getRuntimeSpec } from "../../../src/services/runtimeService"; +import { getRuntimeSpec } from "../../../src/services/runtimeSpecService"; import { Network, SourceData, SourceType } from "../model"; import { log } from "../utils/log"; diff --git a/src/components/DataViewer.tsx b/src/components/DataViewer.tsx index 520e11f4..52ff92bf 100644 --- a/src/components/DataViewer.tsx +++ b/src/components/DataViewer.tsx @@ -8,7 +8,6 @@ import { config } from "../config"; import { DecodedArg } from "../model/decodedMetadata"; import { Network } from "../model/network"; -import { RuntimeSpec } from "../model/runtimeSpec"; import CopyToClipboardButton from "./CopyToClipboardButton"; import { DataViewerValueJson } from "./DataViewerValueJson"; @@ -207,28 +206,22 @@ export type DataViewerProps = { network: Network; data: any; metadata?: DecodedArg[]; - runtimeSpec?: RuntimeSpec; modes?: DataViewerMode[]; defaultMode?: DataViewerMode; simple?: boolean; copyToClipboard?: boolean; }; -function DataViewer(props: DataViewerProps) { +export const DataViewer = (props: DataViewerProps) => { const { network, data, metadata, - runtimeSpec, defaultMode = MODES.find(Boolean), simple, copyToClipboard, } = props; - if (metadata && !runtimeSpec) { - console.warn("If is used with metadata argument, runtimeSpec argument should be passed too."); - } - const [mode, setMode] = useState(defaultMode || MODES.find(Boolean) as DataViewerMode); console.log("render"); @@ -250,7 +243,6 @@ function DataViewer(props: DataViewerProps) { network={network} value={data} metadata={metadata} - runtimeSpec={runtimeSpec} /> ), [data]); @@ -289,6 +281,6 @@ function DataViewer(props: DataViewerProps) { ); -} +}; export default DataViewer; diff --git a/src/components/DataViewerValueParsed.tsx b/src/components/DataViewerValueParsed.tsx index 1e40d076..d9d04433 100644 --- a/src/components/DataViewerValueParsed.tsx +++ b/src/components/DataViewerValueParsed.tsx @@ -6,7 +6,6 @@ import { DecodedArg } from "../model/decodedMetadata"; import { noCase } from "../utils/string"; import { AccountAddress } from "./AccountAddress"; -import { RuntimeSpec } from "../model/runtimeSpec"; import { Network } from "../model/network"; // found in https://github.com/polkadot-js/apps/blob/59c2badf87c29fd8cb5b7dfcc045c3ce451a54bc/packages/react-params/src/Param/findComponent.ts#L51 @@ -79,7 +78,6 @@ type ValueOfKindProps = { value: any; }; valueMetadata?: DecodedArg; - runtimeSpec?: RuntimeSpec; } const ValueOfKind = (props: ValueOfKindProps) => { @@ -87,7 +85,6 @@ const ValueOfKind = (props: ValueOfKindProps) => { network, value: {__kind: kind, ...value}, valueMetadata: metadata, - runtimeSpec } = props; return ( @@ -102,7 +99,6 @@ const ValueOfKind = (props: ValueOfKindProps) => { network={network} value={value.value || value} metadata={metadata} - runtimeSpec={runtimeSpec} /> @@ -115,11 +111,10 @@ type MaybeAccountLinkValueProps = { network: Network; value: any; valueMetadata: DecodedArg; - runtimeSpec: RuntimeSpec; } const AccountValue = (props: MaybeAccountLinkValueProps) => { - const {network, value, valueMetadata, runtimeSpec} = props; + const {network, value, valueMetadata} = props; if (typeof value === "object") { if (ADDRESS_KINDS.includes(value.__kind)) { @@ -130,7 +125,6 @@ const AccountValue = (props: MaybeAccountLinkValueProps) => { ...valueMetadata, type: "AccountId" }} - runtimeSpec={runtimeSpec} />; } @@ -138,7 +132,6 @@ const AccountValue = (props: MaybeAccountLinkValueProps) => { ); } @@ -158,20 +151,18 @@ export type DataViewerValueParsedProps = { network: Network; value: any; metadata?: DecodedArg[]|DecodedArg; - runtimeSpec?: RuntimeSpec; }; export const DataViewerValueParsed = (props: DataViewerValueParsedProps) => { - const { network, metadata, runtimeSpec } = props; + const { network, metadata } = props; let { value } = props; - if (metadata && runtimeSpec && ADDRESS_TYPES.includes((metadata as DecodedArg).type)) { + if (metadata && ADDRESS_TYPES.includes((metadata as DecodedArg).type)) { return ( ); } @@ -206,7 +197,6 @@ export const DataViewerValueParsed = (props: DataViewerValueParsedProps) => { network={network} value={item} metadata={itemsMetadata[index]} - runtimeSpec={runtimeSpec} /> @@ -222,7 +212,6 @@ export const DataViewerValueParsed = (props: DataViewerValueParsedProps) => { ); } @@ -246,7 +235,6 @@ export const DataViewerValueParsed = (props: DataViewerValueParsedProps) => { ? metadata?.find(it => noCase(it.name) === noCase(key)) : undefined } - runtimeSpec={runtimeSpec} /> diff --git a/src/components/calls/CallInfoTable.tsx b/src/components/calls/CallInfoTable.tsx index e01b53c5..eb5c312b 100644 --- a/src/components/calls/CallInfoTable.tsx +++ b/src/components/calls/CallInfoTable.tsx @@ -8,7 +8,6 @@ import { Network } from "../../model/network"; import { Resource } from "../../model/resource"; import { encodeAddress } from "../../utils/formatAddress"; -import { getCallMetadataByName } from "../../utils/queryMetadata"; import { AccountAddress } from "../AccountAddress"; import { ButtonLink } from "../ButtonLink"; @@ -116,14 +115,7 @@ export const CallInfoTable = (props: CallInfoTableProps) => { } diff --git a/src/components/events/EventInfoTable.tsx b/src/components/events/EventInfoTable.tsx index b6e20e5b..bb22e71e 100644 --- a/src/components/events/EventInfoTable.tsx +++ b/src/components/events/EventInfoTable.tsx @@ -1,7 +1,6 @@ import { Event } from "../../model/event"; import { Network } from "../../model/network"; import { Resource } from "../../model/resource"; -import { getEventMetadataByName } from "../../utils/queryMetadata"; import { ButtonLink } from "../ButtonLink"; import DataViewer from "../DataViewer"; @@ -90,8 +89,7 @@ export const EventInfoTable = (props: EventInfoTableProps) => { } diff --git a/src/components/events/EventsTable.tsx b/src/components/events/EventsTable.tsx index 80ce08a3..6ee9bfe9 100644 --- a/src/components/events/EventsTable.tsx +++ b/src/components/events/EventsTable.tsx @@ -5,8 +5,6 @@ import { Event } from "../../model/event"; import { Network } from "../../model/network"; import { PaginatedResource } from "../../model/paginatedResource"; -import { getEventMetadataByName } from "../../utils/queryMetadata"; - import { ButtonLink } from "../ButtonLink"; import DataViewer from "../DataViewer"; import { ItemsTable, ItemsTableAttribute } from "../ItemsTable"; @@ -79,12 +77,7 @@ function EventsTable(props: EventsTableProps) { ); diff --git a/src/components/extrinsics/ExtrinsicInfoTable.tsx b/src/components/extrinsics/ExtrinsicInfoTable.tsx index 5f0a2ee2..2c1ceb72 100644 --- a/src/components/extrinsics/ExtrinsicInfoTable.tsx +++ b/src/components/extrinsics/ExtrinsicInfoTable.tsx @@ -8,7 +8,6 @@ import { Network } from "../../model/network"; import { Resource } from "../../model/resource"; import { encodeAddress } from "../../utils/formatAddress"; -import { getCallMetadataByName } from "../../utils/queryMetadata"; import { AccountAddress } from "../AccountAddress"; import { ButtonLink } from "../ButtonLink"; @@ -102,8 +101,7 @@ export const ExtrinsicInfoTable = (props: ExtrinsicInfoTableProps) => { } @@ -125,7 +123,6 @@ export const ExtrinsicInfoTable = (props: ExtrinsicInfoTableProps) => { simple network={network} data={data.signature} - runtimeSpec={data.runtimeSpec} copyToClipboard /> } diff --git a/src/components/extrinsics/ExtrinsicsTable.tsx b/src/components/extrinsics/ExtrinsicsTable.tsx index 132c95b4..852cd95a 100644 --- a/src/components/extrinsics/ExtrinsicsTable.tsx +++ b/src/components/extrinsics/ExtrinsicsTable.tsx @@ -38,7 +38,7 @@ function ExtrinsicsTable(props: ExtrinsicsTableProps) { - + {extrinsic.id} } @@ -47,7 +47,7 @@ function ExtrinsicsTable(props: ExtrinsicsTableProps) { label="Name" render={(extrinsic) => diff --git a/src/hooks/useRuntimeMetadataPallets.ts b/src/hooks/useRuntimeMetadataPallets.ts new file mode 100644 index 00000000..b62b315e --- /dev/null +++ b/src/hooks/useRuntimeMetadataPallets.ts @@ -0,0 +1,23 @@ +import { useCallback } from "react"; + +import { FetchOptions } from "../model/fetchOptions"; +import { getPalletsRuntimeMetadata } from "../services/runtimeMetadataService"; + +import { useResource } from "./useResource"; + +export function useRuntimeMetadataPallets( + network?: string | undefined, + specVersion?: number | undefined, + options?: FetchOptions +) { + return useResource( + useCallback(async (network?: string, specVersion?: number) => { + if (network && specVersion) { + const pallets = await getPalletsRuntimeMetadata(network, specVersion); + return await pallets.toArray(); + } + }, []), + [network, specVersion], + options + ); +} diff --git a/src/hooks/useRuntimeSpecVersions.ts b/src/hooks/useRuntimeSpecVersions.ts index 6b9012cc..5b1fa9f7 100644 --- a/src/hooks/useRuntimeSpecVersions.ts +++ b/src/hooks/useRuntimeSpecVersions.ts @@ -1,5 +1,6 @@ import { FetchOptions } from "../model/fetchOptions"; -import { getRuntimeSpecVersions } from "../services/runtimeService"; +import { getRuntimeSpecVersions } from "../services/runtimeSpecService"; + import { useResource } from "./useResource"; export function useRuntimeSpecVersions( diff --git a/src/hooks/useRuntimeSpecs.ts b/src/hooks/useRuntimeSpecs.ts deleted file mode 100644 index 7eeb243e..00000000 --- a/src/hooks/useRuntimeSpecs.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FetchOptions } from "../model/fetchOptions"; -import { getRuntimeSpecs } from "../services/runtimeService"; -import { useResource } from "./useResource"; - -export function useRuntimeSpecs( - network: string, - specVersions: number[] | undefined, - options?: FetchOptions -) { - return useResource(getRuntimeSpecs, [network, specVersions], options); -} diff --git a/src/model/account.ts b/src/model/account.ts index 9b57c6a7..0e9ba991 100644 --- a/src/model/account.ts +++ b/src/model/account.ts @@ -1,9 +1,7 @@ import { AccountIdentity } from "./accountIdentity"; -import { RuntimeSpec } from "./runtimeSpec"; export type Account = { id: string; address: string; identity: AccountIdentity | null; - runtimeSpec: RuntimeSpec } diff --git a/src/model/balance.ts b/src/model/balance.ts index 48b8dd7d..c6afebdd 100644 --- a/src/model/balance.ts +++ b/src/model/balance.ts @@ -1,12 +1,9 @@ import Decimal from "decimal.js"; -import { RuntimeSpec } from "./runtimeSpec"; - export type Balance = { id: string; free: Decimal; reserved: Decimal; total: Decimal; updatedAtBlock?: number; - runtimeSpec: RuntimeSpec; } diff --git a/src/model/block.ts b/src/model/block.ts index 2cc74f07..089d2cfe 100644 --- a/src/model/block.ts +++ b/src/model/block.ts @@ -1,5 +1,3 @@ -import { RuntimeSpec } from "./runtimeSpec"; - export type Block = { id: string; hash: string; @@ -8,5 +6,4 @@ export type Block = { parentHash: string; validator: string|null; specVersion: number; - runtimeSpec: RuntimeSpec; } diff --git a/src/model/call.ts b/src/model/call.ts index ea5005d2..ca57ec30 100644 --- a/src/model/call.ts +++ b/src/model/call.ts @@ -1,7 +1,8 @@ -import { RuntimeSpec } from "./runtimeSpec"; +import { DecodedCall } from "./decodedMetadata"; export type Call = { id: string; + network: string; callName: string; palletName: string; blockId: string; @@ -13,5 +14,7 @@ export type Call = { args: any|null; success: boolean; specVersion: number; - runtimeSpec: RuntimeSpec; + metadata: { + call?: DecodedCall + } } diff --git a/src/model/event.ts b/src/model/event.ts index c6d9fa13..87ce1471 100644 --- a/src/model/event.ts +++ b/src/model/event.ts @@ -1,7 +1,8 @@ -import { RuntimeSpec } from "./runtimeSpec"; +import { DecodedEvent } from "./decodedMetadata"; export type Event = { id: string; + network: string; eventName: string; palletName: string; blockId: string; @@ -11,5 +12,7 @@ export type Event = { extrinsicId: string|null; callId: string|null; args: any|null; - runtimeSpec: RuntimeSpec; + metadata: { + event?: DecodedEvent + } } diff --git a/src/model/extrinsic.ts b/src/model/extrinsic.ts index f720890f..3b7f4030 100644 --- a/src/model/extrinsic.ts +++ b/src/model/extrinsic.ts @@ -1,7 +1,8 @@ -import { RuntimeSpec } from "./runtimeSpec"; +import { DecodedCall } from "./decodedMetadata"; export type Extrinsic = { id: string; + network: string; hash: string; blockId: string; blockHeight: number; @@ -18,5 +19,8 @@ export type Extrinsic = { error: object|null; version: number; specVersion: number; - runtimeSpec: RuntimeSpec; + metadata: { + call: DecodedCall|undefined, + // TODO error?: any + } } diff --git a/src/model/runtime-metadata/runtimeMetadataArg.ts b/src/model/runtime-metadata/runtimeMetadataArg.ts new file mode 100644 index 00000000..0a04a202 --- /dev/null +++ b/src/model/runtime-metadata/runtimeMetadataArg.ts @@ -0,0 +1,5 @@ +export interface RuntimeMetadataArg { + name: string; + type: string; + typeName?: string; +} diff --git a/src/model/runtime-metadata/runtimeMetadataCall.ts b/src/model/runtime-metadata/runtimeMetadataCall.ts new file mode 100644 index 00000000..95f12c72 --- /dev/null +++ b/src/model/runtime-metadata/runtimeMetadataCall.ts @@ -0,0 +1,9 @@ +import { RuntimeMetadataArg } from "./runtimeMetadataArg"; + +export interface RuntimeMetadataCall { + network: string; + specVersion: number; + pallet: string; + name: string; + args: RuntimeMetadataArg[]; +} diff --git a/src/model/runtime-metadata/runtimeMetadataEvent.ts b/src/model/runtime-metadata/runtimeMetadataEvent.ts new file mode 100644 index 00000000..46c1c003 --- /dev/null +++ b/src/model/runtime-metadata/runtimeMetadataEvent.ts @@ -0,0 +1,9 @@ +import { RuntimeMetadataArg } from "./runtimeMetadataArg"; + +export interface RuntimeMetadataEvent { + network: string; + specVersion: number; + pallet: string; + name: string; + args: RuntimeMetadataArg[]; +} diff --git a/src/model/runtime-metadata/runtimeMetadataPallet.ts b/src/model/runtime-metadata/runtimeMetadataPallet.ts new file mode 100644 index 00000000..01fec2d6 --- /dev/null +++ b/src/model/runtime-metadata/runtimeMetadataPallet.ts @@ -0,0 +1,5 @@ +export interface RuntimeMetadataPallet { + network: string; + specVersion: number; + name: string; +} diff --git a/src/model/runtime-metadata/runtimeMetadataSpec.ts b/src/model/runtime-metadata/runtimeMetadataSpec.ts new file mode 100644 index 00000000..526030b1 --- /dev/null +++ b/src/model/runtime-metadata/runtimeMetadataSpec.ts @@ -0,0 +1,4 @@ +export interface RuntimeMetadataSpec { + network: string; + specVersion: number; +} diff --git a/src/model/transfer.ts b/src/model/transfer.ts index 84df4f37..919ed6c8 100644 --- a/src/model/transfer.ts +++ b/src/model/transfer.ts @@ -1,7 +1,5 @@ import Decimal from "decimal.js"; -import { RuntimeSpec } from "./runtimeSpec"; - export type Transfer = { id: string; direction: string; @@ -13,7 +11,6 @@ export type Transfer = { toPublicKey: string; amount: Decimal; success: boolean; - runtimeSpec: RuntimeSpec; extrinsic: { id: string; } | null; diff --git a/src/networks.json b/src/networks.json index 6224dad0..63c1773d 100644 --- a/src/networks.json +++ b/src/networks.json @@ -27,7 +27,8 @@ "decimals": 12, "symbol": "TZERO", "squids": { - "archive": "https://aleph-zero-testnet.explorer.subsquid.io/graphql" + "archive": "https://aleph-zero-testnet.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-aleph-zero-testnet/graphql" } }, { @@ -78,6 +79,7 @@ "symbol": "BAJU", "squids": { "archive": "https://bajun.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-bajun/graphql", "main": "https://squid.subsquid.io/gs-main-bajun/graphql" }, "coinGeckoId": "ajuna-network" @@ -146,7 +148,8 @@ "decimals": 9, "symbol": "EQ", "squids": { - "archive": "https://equilibrium.explorer.subsquid.io/graphql" + "archive": "https://equilibrium.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-equilibrium/graphql" } }, { @@ -162,7 +165,6 @@ "symbol": "FREN", "squids": { "archive": "https://gmordie.explorer.subsquid.io/graphql", - "explorer": "https://squid.subsquid.io/gs-explorer-gmordie/graphql", "main": "https://squid.subsquid.io/gs-main-gmordie/graphql" } }, @@ -214,7 +216,8 @@ "decimals": 12, "symbol": "TNKR", "squids": { - "archive": "https://invarch-tinkernet.explorer.subsquid.io/graphql" + "archive": "https://invarch-tinkernet.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-invarch-tinkernet/graphql" } }, { @@ -227,7 +230,8 @@ "decimals": 10, "symbol": "JOY", "squids": { - "archive": "https://joystream.explorer.subsquid.io/graphql" + "archive": "https://joystream.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-joystream/graphql" }, "coinGeckoId": "joystream" }, @@ -279,7 +283,8 @@ "decimals": 12, "symbol": "KINT", "squids": { - "archive": "https://kintsugi.explorer.subsquid.io/graphql" + "archive": "https://kintsugi.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-kintsugi/graphql" }, "coinGeckoId": "kintsugi" }, @@ -316,6 +321,7 @@ "symbol": "LIT", "squids": { "archive": "https://litentry.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-litentry/graphql", "main": "https://squid.subsquid.io/gs-main-litentry/graphql" }, "coinGeckoId": "litentry" @@ -333,6 +339,7 @@ "symbol": "LIT", "squids": { "archive": "https://litmus.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-litmus/graphql", "main": "https://squid.subsquid.io/gs-main-litmus/graphql" }, "coinGeckoId": "litentry" @@ -382,7 +389,8 @@ "decimals": 18, "symbol": "PEAQ", "squids": { - "archive": "https://peaq.explorer.subsquid.io/graphql" + "archive": "https://peaq.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-peaq/graphql" } }, { @@ -397,6 +405,7 @@ "symbol": "PEN", "squids": { "archive": "https://pendulum.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-pendulum/graphql", "main": "https://squid.subsquid.io/gs-main-pendulum/graphql" }, "coinGeckoId": "pendulum-chain" @@ -452,6 +461,7 @@ "symbol": "XRT", "squids": { "archive": "https://robonomics.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-robonomics/graphql", "main": "https://squid.subsquid.io/gs-main-robonomics/graphql" }, "coinGeckoId": "robonomics-network" @@ -523,6 +533,7 @@ "name": "statemint", "displayName": "Statemint", "icon": "/assets/network-icons/statemint.png", + "color": "#86e62a", "website": "https://www.parity.io/", "parachainId": 1000, "relayChain": "polkadot", @@ -563,7 +574,8 @@ "decimals": 12, "symbol": "T0RN", "squids": { - "archive": "https://t0rn.explorer.subsquid.io/graphql" + "archive": "https://t0rn.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-t0rn/graphql" } }, { @@ -576,7 +588,8 @@ "decimals": 12, "symbol": "VARA", "squids": { - "archive": "https://vara.explorer.subsquid.io/graphql" + "archive": "https://vara.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-vara/graphql" } }, { @@ -588,8 +601,9 @@ "decimals": 9, "symbol": "XX", "squids": { - "archive": "https://xx-network.explorer.subsquid.io/graphql" + "archive": "https://xx-network.explorer.subsquid.io/graphql", + "explorer": "https://squid.subsquid.io/gs-explorer-xx-network/graphql" }, "coinGeckoId": "xxcoin" } -] +] \ No newline at end of file diff --git a/src/repositories/runtimeMetadataRepository.ts b/src/repositories/runtimeMetadataRepository.ts new file mode 100644 index 00000000..da9e6858 --- /dev/null +++ b/src/repositories/runtimeMetadataRepository.ts @@ -0,0 +1,26 @@ +import Dexie, { Table } from "dexie"; + +import { RuntimeMetadataSpec } from "../model/runtime-metadata/runtimeMetadataSpec"; +import { RuntimeMetadataPallet } from "../model/runtime-metadata/runtimeMetadataPallet"; +import { RuntimeMetadataCall } from "../model/runtime-metadata/runtimeMetadataCall"; +import { RuntimeMetadataEvent } from "../model/runtime-metadata/runtimeMetadataEvent"; + +export class RuntimeMetadataRepository extends Dexie { + specs!: Table; + pallets!: Table; + calls!: Table; + events!: Table; + + constructor() { + super("runtime-metadata-dexie"); + + this.version(1).stores({ + specs: "[network+specVersion]", + pallets: "[network+specVersion+name],[network+specVersion]", + calls: "[network+specVersion+pallet+name],[network+specVersion+pallet]", + events: "[network+specVersion+pallet+name],[network+specVersion+pallet]" + }); + } +} + +export const runtimeMetadataRepository = new RuntimeMetadataRepository(); diff --git a/src/screens/runtime.tsx b/src/screens/runtime.tsx index e604b408..7cae878a 100644 --- a/src/screens/runtime.tsx +++ b/src/screens/runtime.tsx @@ -3,9 +3,11 @@ import { MenuItem, Select } from "@mui/material"; import { Card, CardHeader } from "../components/Card"; import { Devtool } from "../components/Devtool"; -import { useRuntimeSpecs } from "../hooks/useRuntimeSpecs"; import { useRuntimeSpecVersions } from "../hooks/useRuntimeSpecVersions"; import { useRootLoaderData } from "../hooks/useRootLoaderData"; +import { useRuntimeMetadataPallets } from "../hooks/useRuntimeMetadataPallets"; + +import { tryParseInt } from "../utils/string"; export type RuntimeParams = { specVersion?: string; @@ -18,20 +20,16 @@ export const RuntimePage = () => { const navigate = useNavigate(); const runtimeVersions = useRuntimeSpecVersions(network.name); - const runtimeSpecs = useRuntimeSpecs(network.name, specVersion ? [parseInt(specVersion)] : [], { + const pallets = useRuntimeMetadataPallets(network.name, tryParseInt(specVersion), { skip: !specVersion }); - const metadata = specVersion && runtimeSpecs.data?.[parseInt(specVersion)]!.metadata; - - console.log(metadata); - - if (runtimeVersions.loading) { - return null; - } + console.log(pallets); if (!specVersion) { - return ; + return runtimeVersions.data + ? + : null; } return ( @@ -41,58 +39,18 @@ export const RuntimePage = () => { {network.displayName} runtime See console - {metadata && + {pallets.data &&
    - {metadata.pallets.map(pallet => + {pallets.data.map(pallet =>
  • {pallet.name} -
      - {pallet.calls.length > 0 && -
    • - calls -
        - {pallet.calls.map(call => -
      • - {call.name} -
          - {call.args.map(arg => -
        • - {arg.name}: {arg.type} {arg.typeName && `(${arg.typeName})`} -
        • - )} -
        -
      • - )} -
      -
    • - } - {pallet.events.length > 0 && -
    • - events -
        - {pallet.events.map(event => -
      • - {event.name} -
          - {event.args.map(arg => -
        • - {arg.name}: {arg.type} {arg.typeName && `(${arg.typeName})`} -
        • - )} -
        -
      • - )} -
      -
    • - } -
  • )}
diff --git a/src/services/accountService.ts b/src/services/accountService.ts index cf37d15d..bd9bd3b5 100644 --- a/src/services/accountService.ts +++ b/src/services/accountService.ts @@ -1,14 +1,14 @@ import { isAddress } from "@polkadot/util-crypto"; import { Account } from "../model/account"; +import { AccountIdentity } from "../model/accountIdentity"; import { MainSquidIdentity } from "../model/main-squid/mainSquidIdentity"; -import { addRuntimeSpec } from "../utils/addRuntimeSpec"; + import { DataError } from "../utils/error"; import { decodeAddress, encodeAddress } from "../utils/formatAddress"; import { getNetwork, hasSupport } from "./networksService"; import { fetchIdentitiesSquid } from "./fetchService"; -import { AccountIdentity } from "../model/accountIdentity"; export async function getAccount(network: string, address: string): Promise { if (!isAddress(address)) { @@ -22,18 +22,16 @@ export async function getAccount(network: string, address: string): Promise = { + const account: Account = { id: address, address, identity: null }; if (hasSupport(network, "identities-squid")) { - data.identity = await getAccountIdentity(network, address); + account.identity = await getAccountIdentity(network, address); } - const account = await addRuntimeSpec(network, data, () => "latest"); - return account; } diff --git a/src/services/balancesService.ts b/src/services/balancesService.ts index 758ad4cb..0e85c287 100644 --- a/src/services/balancesService.ts +++ b/src/services/balancesService.ts @@ -1,11 +1,11 @@ import Decimal from "decimal.js"; + import { AccountBalance } from "../model/accountBalance"; import { Balance } from "../model/balance"; import { StatsSquidAccountBalance } from "../model/stats-squid/statsSquidAccountBalance"; import { ItemsConnection } from "../model/itemsConnection"; import { PaginationOptions } from "../model/paginationOptions"; -import { addRuntimeSpecs } from "../utils/addRuntimeSpec"; import { extractConnectionItems } from "../utils/extractConnectionItems"; import { encodeAddress } from "../utils/formatAddress"; import { rawAmountToDecimal } from "../utils/number"; @@ -58,8 +58,7 @@ export async function getBalances( } ); - const items = extractConnectionItems(response.accountsConnection, pagination, unifyStatsSquidAccountBalance, network); - const balances = await addRuntimeSpecs(network, items, () => "latest"); + const balances = extractConnectionItems(response.accountsConnection, pagination, unifyStatsSquidAccountBalance, network); return balances; } @@ -130,7 +129,7 @@ export async function getAccountBalances(address: string) { /*** PRIVATE ***/ -function unifyStatsSquidAccountBalance(balance: StatsSquidAccountBalance, networkName: string): Omit { +function unifyStatsSquidAccountBalance(balance: StatsSquidAccountBalance, networkName: string): Balance { const network = getNetwork(networkName); return { diff --git a/src/services/blocksService.ts b/src/services/blocksService.ts index d0c3d1f2..bc212269 100644 --- a/src/services/blocksService.ts +++ b/src/services/blocksService.ts @@ -4,7 +4,6 @@ import { ExplorerSquidBlock } from "../model/explorer-squid/explorerSquidBlock"; import { ItemsConnection } from "../model/itemsConnection"; import { PaginationOptions } from "../model/paginationOptions"; -import { addRuntimeSpec, addRuntimeSpecs } from "../utils/addRuntimeSpec"; import { extractConnectionItems } from "../utils/extractConnectionItems"; import { fetchArchive, fetchExplorerSquid } from "./fetchService"; @@ -61,8 +60,7 @@ async function getArchiveBlock(network: string, filter: BlocksFilter) { } ); - const data = response.blocks[0] && unifyArchiveBlock(response.blocks[0]); - const block = await addRuntimeSpec(network, data, it => it.specVersion); + const block = response.blocks[0] && unifyArchiveBlock(response.blocks[0]); return block; } @@ -86,8 +84,7 @@ async function getExplorerSquidBlock(network: string, filter: BlocksFilter) { } ); - const data = response.blocks[0] && unifyExplorerSquidBlock(response.blocks[0]); - const block = await addRuntimeSpec(network, data, it => it.specVersion); + const block = response.blocks[0] && unifyExplorerSquidBlock(response.blocks[0]); return block; } @@ -134,8 +131,7 @@ async function getArchiveBlocks( } ); - const data = extractConnectionItems(response.blocksConnection, pagination, unifyArchiveBlock); - const blocks = await addRuntimeSpecs(network, data, it => it.specVersion); + const blocks = extractConnectionItems(response.blocksConnection, pagination, unifyArchiveBlock); return blocks; } @@ -180,25 +176,23 @@ async function getExplorerSquidBlocks( } ); - const data = extractConnectionItems(response.blocksConnection, pagination, unifyExplorerSquidBlock); - - const blocks = await addRuntimeSpecs(network, data, it => it.specVersion); + const blocks = extractConnectionItems(response.blocksConnection, pagination, unifyExplorerSquidBlock); return blocks; } -function unifyArchiveBlock(block: ArchiveBlock): Omit { +function unifyArchiveBlock(block: ArchiveBlock): Block { return { ...block, specVersion: block.spec.specVersion }; } -function unifyExplorerSquidBlock(block: ExplorerSquidBlock): Omit { +function unifyExplorerSquidBlock(block: ExplorerSquidBlock): Block { return block; } -function blocksFilterToArchiveFilter(filter?: BlocksFilter) { +export function blocksFilterToArchiveFilter(filter?: BlocksFilter) { if (!filter) { return undefined; } @@ -206,7 +200,7 @@ function blocksFilterToArchiveFilter(filter?: BlocksFilter) { return filter; } -function blocksFilterToExplorerSquidFilter(filter?: BlocksFilter) { +export function blocksFilterToExplorerSquidFilter(filter?: BlocksFilter) { if (!filter) { return undefined; } diff --git a/src/services/callsService.ts b/src/services/callsService.ts index 7cd95a72..e0eab6f9 100644 --- a/src/services/callsService.ts +++ b/src/services/callsService.ts @@ -5,11 +5,12 @@ import { Call } from "../model/call"; import { ItemsConnection } from "../model/itemsConnection"; import { PaginationOptions } from "../model/paginationOptions"; -import { addRuntimeSpec, addRuntimeSpecs } from "../utils/addRuntimeSpec"; -import { decodeAddress } from "../utils/formatAddress"; +import { addItemMetadata, addItemsMetadata } from "../utils/addMetadata"; import { extractConnectionItems } from "../utils/extractConnectionItems"; +import { decodeAddress } from "../utils/formatAddress"; import { fetchArchive, fetchExplorerSquid } from "./fetchService"; +import { getCallRuntimeMetadata } from "./runtimeMetadataService"; import { hasSupport } from "./networksService"; export type CallsFilter = @@ -74,8 +75,8 @@ async function getArchiveCall(network: string, filter: CallsFilter) { } ); - const data = response.calls[0] && unifyArchiveCall(response.calls[0]); - const call = await addRuntimeSpec(network, data, it => it.specVersion); + const data = response.calls[0] && unifyArchiveCall(response.calls[0], network); + const call = await addItemMetadata(data, getCallMetadata); return call; } @@ -107,9 +108,9 @@ async function getExplorerSquidCall(network: string, filter: CallsFilter) { } ); - const data = response.calls[0] && unifyExplorerSquidCall(response.calls[0]); - const dataWithRuntimeSpec = await addRuntimeSpec(network, data, it => it.specVersion); - const call = await addCallArgs(network, dataWithRuntimeSpec); + const data = response.calls[0] && unifyExplorerSquidCall(response.calls[0], network); + const dataWithMetadata = await addItemMetadata(data, getCallMetadata); + const call = await addCallArgs(network, dataWithMetadata); return call; } @@ -168,8 +169,8 @@ async function getArchiveCalls( } ); - const data = extractConnectionItems(response?.callsConnection, pagination, unifyArchiveCall); - const calls = await addRuntimeSpecs(network, data, it => it.specVersion); + const data = extractConnectionItems(response?.callsConnection, pagination, unifyArchiveCall, network); + const calls = await addItemsMetadata(data, getCallMetadata); return calls; } @@ -222,8 +223,8 @@ async function getExplorerSquidCalls( } ); - const data = extractConnectionItems(response.callsConnection, pagination, unifyExplorerSquidCall); - const calls = await addRuntimeSpecs(network, data, it => it.specVersion); + const data = extractConnectionItems(response.callsConnection, pagination, unifyExplorerSquidCall, network); + const calls = await addItemsMetadata(data, getCallMetadata); return calls; } @@ -261,11 +262,12 @@ async function addCallArgs(network: string, call: Call|undefined) { }; } -function unifyArchiveCall(call: ArchiveCall): Omit { +function unifyArchiveCall(call: ArchiveCall, network: string): Omit { const [palletName, callName] = call.name.split(".") as [string, string]; return { ...call, + network, callName, palletName, blockId: call.block.id, @@ -281,9 +283,10 @@ function unifyArchiveCall(call: ArchiveCall): Omit { }; } -function unifyExplorerSquidCall(call: ExplorerSquidCall): Omit { +function unifyExplorerSquidCall(call: ExplorerSquidCall, network: string): Omit { return { ...call, + network, blockId: call.block.id, blockHeight: call.block.height, timestamp: call.block.timestamp, @@ -295,6 +298,12 @@ function unifyExplorerSquidCall(call: ExplorerSquidCall): Omit): Promise { + return { + call: await getCallRuntimeMetadata(call.network, call.specVersion, call.palletName, call.callName) + }; +} + function callsFilterToArchiveFilter(filter?: CallsFilter) { if (!filter) { return undefined; diff --git a/src/services/eventsService.ts b/src/services/eventsService.ts index 33657f3a..184ee24b 100644 --- a/src/services/eventsService.ts +++ b/src/services/eventsService.ts @@ -1,17 +1,20 @@ import { ArchiveEvent } from "../model/archive/archiveEvent"; import { ExplorerSquidEvent } from "../model/explorer-squid/explorerSquidEvent"; + import { Event } from "../model/event"; import { ItemsConnection } from "../model/itemsConnection"; import { ItemsCounter } from "../model/itemsCounter"; import { ItemsResponse } from "../model/itemsResponse"; import { PaginationOptions } from "../model/paginationOptions"; -import { addRuntimeSpec, addRuntimeSpecs } from "../utils/addRuntimeSpec"; -import { upperFirst } from "../utils/string"; + +import { addItemMetadata, addItemsMetadata } from "../utils/addMetadata"; import { extractConnectionItems } from "../utils/extractConnectionItems"; +import { lowerFirst, upperFirst } from "../utils/string"; import { fetchArchive, fetchExplorerSquid } from "./fetchService"; import { hasSupport } from "./networksService"; -import { getRuntimeSpec } from "./runtimeService"; +import { getEventsRuntimeMetadata, getPalletsRuntimeMetadata, getRuntimeEventMetadata } from "./runtimeMetadataService"; +import { getLatestRuntimeSpecVersion } from "./runtimeSpecService"; export type EventsFilter = { id_eq: string; } @@ -37,17 +40,7 @@ export async function getEventsByName( order: EventsOrder = "id_DESC", pagination: PaginationOptions ): Promise> { - let [palletName = "", eventName = ""] = name.split("."); - - const runtimeSpec = await getRuntimeSpec(network, "latest"); - - // try to fix casing according to latest runtime spec - const runtimePallet = runtimeSpec.metadata.pallets.find(it => it.name.toLowerCase() === palletName?.toLowerCase()); - const runtimeEvent = runtimePallet?.events.find(it => it.name.toLowerCase() === eventName?.toLowerCase()); - - // use found names from runtime metadata or try to fix the first letter casing as fallback - palletName = runtimePallet?.name.toString() || upperFirst(palletName); - eventName = runtimeEvent?.name.toString() || upperFirst(eventName); + const {palletName, eventName} = await normalizeEventName(network, name); const filter: EventsFilter = eventName ? { palletName_eq: palletName, eventName_eq: eventName } @@ -104,6 +97,27 @@ export async function getEvents( return getArchiveEvents(network, filter, order, pagination); } +export async function normalizeEventName(network: string, name: string) { + let [palletName = "", eventName = ""] = name.toLowerCase().split("."); + + const latestRuntimeSpecVersion = await getLatestRuntimeSpecVersion(network); + + const pallets = await getPalletsRuntimeMetadata(network, latestRuntimeSpecVersion); + const pallet = await pallets.and(it => it.name.toLowerCase() === palletName).first(); + + const events = pallet && await getEventsRuntimeMetadata(network, latestRuntimeSpecVersion, pallet.name); + const event = await events?.and(it => it.name.toLowerCase() === eventName).first(); + + // use found names from runtime metadata or try to fix the first letter casing as fallback + palletName = pallet?.name || upperFirst(palletName); + eventName = event?.name || upperFirst(eventName); + + return { + palletName, + eventName + }; +} + /*** PRIVATE ***/ async function getArchiveEvent(network: string, filter: EventsFilter) { @@ -135,8 +149,8 @@ async function getArchiveEvent(network: string, filter: EventsFilter) { } ); - const data = response.events[0] && unifyArchiveEvent(response.events[0]); - const event = addRuntimeSpec(network, data, it => it.specVersion); + const data = response.events[0] && unifyArchiveEvent(response.events[0], network); + const event = addItemMetadata(data, getEventMetadata); return event; } @@ -168,9 +182,9 @@ async function getExplorerSquidEvent(network: string, filter: EventsFilter) { } ); - const data = response.events[0] && unifyExplorerSquidEvent(response.events[0]); - const dataWithRuntimeSpec = await addRuntimeSpec(network, data, it => it.specVersion); - const event = await addEventArgs(network, dataWithRuntimeSpec); + const data = response.events[0] && unifyExplorerSquidEvent(response.events[0], network); + const dataWithMetadata = await addItemMetadata(data, getEventMetadata); + const event = await addEventArgs(network, dataWithMetadata); return event; } @@ -226,8 +240,8 @@ async function getArchiveEvents( } ); - const items = extractConnectionItems(response.eventsConnection, pagination, unifyArchiveEvent); - const events = await addRuntimeSpecs(network, items, it => it.specVersion); + const items = extractConnectionItems(response.eventsConnection, pagination, unifyArchiveEvent, network); + const events = await addItemsMetadata(items, getEventMetadata); return events; } @@ -281,9 +295,9 @@ async function getExplorerSquidEvents( } ); - const data = extractConnectionItems(response.eventsConnection, pagination, unifyExplorerSquidEvent); - const dataWithRuntimeSpecs = await addRuntimeSpecs(network, data, it => it.specVersion); - const events = await addEventsArgs(network, dataWithRuntimeSpecs); + const data = extractConnectionItems(response.eventsConnection, pagination, unifyExplorerSquidEvent, network); + const dataWithMetadata = await addItemsMetadata(data, getEventMetadata); + const events = await addEventsArgs(network, dataWithMetadata); return events; } @@ -337,11 +351,12 @@ async function addEventsArgs(network: string, items: ItemsResponse) { }; } -function unifyArchiveEvent(event: ArchiveEvent): Omit { +function unifyArchiveEvent(event: ArchiveEvent, network: string): Omit { const [palletName, eventName] = event.name.split(".") as [string, string]; return { ...event, + network, blockId: event.block.id, blockHeight: event.block.height, timestamp: event.block.timestamp, @@ -354,9 +369,10 @@ function unifyArchiveEvent(event: ArchiveEvent): Omit { }; } -function unifyExplorerSquidEvent(event: ExplorerSquidEvent): Omit { +function unifyExplorerSquidEvent(event: ExplorerSquidEvent, network: string): Omit { return { ...event, + network, blockId: event.block.id, blockHeight: event.block.height, timestamp: event.block.timestamp, @@ -367,6 +383,12 @@ function unifyExplorerSquidEvent(event: ExplorerSquidEvent): Omit): Promise { + return { + event: await getRuntimeEventMetadata(event.network, event.specVersion, event.palletName, event.eventName) + }; +} + function eventsFilterToArchiveFilter(filter?: EventsFilter) { if (!filter) { return undefined; diff --git a/src/services/extrinsicsService.ts b/src/services/extrinsicsService.ts index ced9a1f7..cd9a5555 100644 --- a/src/services/extrinsicsService.ts +++ b/src/services/extrinsicsService.ts @@ -1,17 +1,20 @@ import { ArchiveExtrinsic } from "../model/archive/archiveExtrinsic"; import { ExplorerSquidExtrinsic } from "../model/explorer-squid/explorerSquidExtrinsic"; + +import { Extrinsic } from "../model/extrinsic"; import { ItemsConnection } from "../model/itemsConnection"; import { ItemsCounter } from "../model/itemsCounter"; import { PaginationOptions } from "../model/paginationOptions"; -import { addRuntimeSpec, addRuntimeSpecs } from "../utils/addRuntimeSpec"; + +import { addItemMetadata, addItemsMetadata } from "../utils/addMetadata"; +import { extractConnectionItems } from "../utils/extractConnectionItems"; import { decodeAddress } from "../utils/formatAddress"; import { lowerFirst, upperFirst } from "../utils/string"; -import { extractConnectionItems } from "../utils/extractConnectionItems"; import { fetchArchive, fetchExplorerSquid } from "./fetchService"; import { hasSupport } from "./networksService"; -import { getRuntimeSpec } from "./runtimeService"; -import { Extrinsic } from "../model/extrinsic"; +import { getCallRuntimeMetadata, getCallsRuntimeMetadata, getPalletsRuntimeMetadata } from "./runtimeMetadataService"; +import { getLatestRuntimeSpecVersion } from "./runtimeSpecService"; export type ExtrinsicsFilter = { id_eq: string; } @@ -33,17 +36,7 @@ export async function getExtrinsicsByName( order: ExtrinsicsOrder = "id_DESC", pagination: PaginationOptions, ) { - let [palletName = "", callName = ""] = name.split("."); - - const latestRuntimeSpec = await getRuntimeSpec(network, "latest"); - - // try to fix casing according to latest runtime spec - const runtimePallet = latestRuntimeSpec.metadata.pallets.find(it => it.name.toLowerCase() === palletName.toLowerCase()); - const runtimeCall = runtimePallet?.calls.find(it => it.name.toLowerCase() === callName.toLowerCase()); - - // use found names from runtime metadata or try to fix the first letter casing as fallback - palletName = runtimePallet?.name.toString() || upperFirst(palletName); - callName = runtimeCall?.name.toString() || lowerFirst(callName); + const {palletName, callName} = await normalizeExtrinsicName(network, name); const filter: ExtrinsicsFilter = callName ? { palletName_eq: palletName, callName_eq: callName } @@ -90,6 +83,27 @@ export async function getExtrinsics( return getArchiveExtrinsics(network, filter, order, pagination, fetchTotalCount); } +export async function normalizeExtrinsicName(network: string, name: string) { + let [palletName = "", callName = ""] = name.toLowerCase().split("."); + + const latestRuntimeSpecVersion = await getLatestRuntimeSpecVersion(network); + + const pallets = await getPalletsRuntimeMetadata(network, latestRuntimeSpecVersion); + const pallet = await pallets.and(it => it.name.toLowerCase() === palletName).first(); + + const calls = pallet && await getCallsRuntimeMetadata(network, latestRuntimeSpecVersion, pallet.name); + const call = await calls?.and(it => it.name.toLowerCase() === callName).first(); + + // use found names from runtime metadata or try to fix the first letter casing as fallback + palletName = pallet?.name || upperFirst(palletName); + callName = call?.name || lowerFirst(callName); + + return { + palletName, + callName + }; +} + /*** PRIVATE ***/ async function getArchiveExtrinsic(network: string, filter?: ExtrinsicsFilter) { @@ -126,8 +140,8 @@ async function getArchiveExtrinsic(network: string, filter?: ExtrinsicsFilter) { } ); - const data = response.extrinsics[0] && unifyArchiveExtrinsic(response.extrinsics[0]); - const extrinsic = addRuntimeSpec(network, data, it => it.specVersion); + const data = response.extrinsics[0] && unifyArchiveExtrinsic(response.extrinsics[0], network); + const extrinsic = await addItemMetadata(data, getExtrinsicMetadata); return extrinsic; } @@ -188,8 +202,8 @@ async function getArchiveExtrinsics( } ); - const items = extractConnectionItems(response.extrinsicsConnection, pagination, unifyArchiveExtrinsic); - const extrinsics = await addRuntimeSpecs(network, items, it => it.specVersion); + const items = extractConnectionItems(response.extrinsicsConnection, pagination, unifyArchiveExtrinsic, network); + const extrinsics = await addItemsMetadata(items, getExtrinsicMetadata); return extrinsics; } @@ -248,17 +262,18 @@ async function getExplorerSquidExtrinsics( } ); - const items = extractConnectionItems(response.extrinsicsConnection, pagination, unifyExplorerSquidExtrinsic); - const extrinsics = await addRuntimeSpecs(network, items, it => it.specVersion); + const items = extractConnectionItems(response.extrinsicsConnection, pagination, unifyExplorerSquidExtrinsic, network); + const extrinsics = await addItemsMetadata(items, getExtrinsicMetadata); return extrinsics; } -function unifyArchiveExtrinsic(extrinsic: ArchiveExtrinsic): Omit { +function unifyArchiveExtrinsic(extrinsic: ArchiveExtrinsic, network: string): Omit { const [palletName, callName] = extrinsic.call.name.split(".") as [string, string]; return { ...extrinsic, + network, blockId: extrinsic.block.id, blockHeight: extrinsic.block.height, timestamp: extrinsic.block.timestamp, @@ -273,9 +288,10 @@ function unifyArchiveExtrinsic(extrinsic: ArchiveExtrinsic): Omit { +function unifyExplorerSquidExtrinsic(extrinsic: ExplorerSquidExtrinsic, network: string): Omit { return { ...extrinsic, + network, hash: extrinsic.extrinsicHash, blockId: extrinsic.block.id, blockHeight: extrinsic.block.height, @@ -292,6 +308,12 @@ function unifyExplorerSquidExtrinsic(extrinsic: ExplorerSquidExtrinsic): Omit): Promise { + return { + call: await getCallRuntimeMetadata(extrinsic.network, extrinsic.specVersion, extrinsic.palletName, extrinsic.callName) + }; +} + function extrinsicFilterToArchiveFilter(filter?: ExtrinsicsFilter) { if (!filter) { return undefined; diff --git a/src/services/runtimeMetadataService.ts b/src/services/runtimeMetadataService.ts new file mode 100644 index 00000000..48311293 --- /dev/null +++ b/src/services/runtimeMetadataService.ts @@ -0,0 +1,47 @@ +import { runtimeMetadataRepository } from "../repositories/runtimeMetadataRepository"; +import { RuntimeSpecWorker } from "../workers/runtimeSpecWorker"; + +export async function getPalletsRuntimeMetadata(network: string, specVersion: number) { + await loadRuntimeMetadata(network, specVersion); + return runtimeMetadataRepository.pallets.where({network, specVersion}); +} + +export async function getCallsRuntimeMetadata(network: string, specVersion: number, pallet: string) { + await loadRuntimeMetadata(network, specVersion); + return runtimeMetadataRepository.calls.where({network, specVersion, pallet}); +} + +export async function getEventsRuntimeMetadata(network: string, specVersion: number, pallet: string) { + await loadRuntimeMetadata(network, specVersion); + return runtimeMetadataRepository.events.where({network, specVersion, pallet}); +} + +export async function getCallRuntimeMetadata(network: string, specVersion: number, pallet: string, name: string) { + await loadRuntimeMetadata(network, specVersion); + return await runtimeMetadataRepository.calls.get({network, specVersion, pallet, name}); +} + +export async function getRuntimeEventMetadata(network: string, specVersion: number, pallet: string, name: string) { + await loadRuntimeMetadata(network, specVersion); + return await runtimeMetadataRepository.events.get({network, specVersion, pallet, name}); +} + +/*** PRIVATE ***/ + +export async function loadRuntimeMetadata(network: string, specVersion: number) { + await self.navigator.locks.request(`runtime-metadata/${network}/${specVersion}`, async () => { + const spec = await runtimeMetadataRepository.specs.get([network, specVersion]); + + if (spec) { + console.log("metadata already downloaded", network, specVersion); + return; + } + + console.log("downloading metadata", network, specVersion); + + const worker = new RuntimeSpecWorker(); + await worker.loadMetadata(network, specVersion); + + worker.terminate(); + }); +} diff --git a/src/services/runtimeService.ts b/src/services/runtimeService.ts deleted file mode 100644 index 2c39bad2..00000000 --- a/src/services/runtimeService.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { RuntimeSpec } from "../model/runtimeSpec"; -import { decodeMetadata } from "../utils/decodeMetadata"; -import { uniq } from "../utils/uniq"; - -import { fetchArchive } from "./fetchService"; - -export async function getRuntimeSpecVersions(network: string) { - const response = await fetchArchive<{metadata: {specVersion: number}[]}>( - network, ` - query { - metadata(orderBy: specVersion_DESC) { - specVersion - } - } - ` - ); - - return response.metadata.map(it => it.specVersion); -} - -export async function getRuntimeSpec(network: string, specVersion: "latest"): Promise; -export async function getRuntimeSpec(network: string, specVersion: number|"latest"): Promise; -export async function getRuntimeSpec(network: string, specVersion: number|"latest"): Promise { - if (specVersion === "latest") { - return getLatestRuntimeSpec(network); - } - - const specs = await getRuntimeSpecs(network, [specVersion]); - return specs[specVersion]; -} - -export async function getLatestRuntimeSpec(network: string) { - const response = await fetchArchive<{spec: Omit[]}>( - network, ` - query { - spec: metadata(orderBy: specVersion_DESC, limit: 1) { - id - blockHash - blockHeight - specName - specVersion - hex - } - } - ` - ); - - const spec = response.spec[0]!; - - return { - ...spec, - metadata: decodeMetadata(spec.hex) - }; -} - -export async function getRuntimeSpecs( - network: string, - specVersions: (number|"latest")[]| undefined -) { - if (specVersions == undefined || specVersions.length === 0) { - specVersions = []; - } - - const specs: Record = {}; - - if (specVersions.includes("latest")) { - specs["latest"] = await getLatestRuntimeSpec(network); - specVersions = specVersions.filter(it => it !== "latest"); - } - - const response = await fetchArchive<{specs: Omit[]}>( - network, ` - query ($specVersions: [Int!]!) { - specs: metadata(where: {specVersion_in: $specVersions}, orderBy: specVersion_DESC) { - id - blockHash - blockHeight - specName - specVersion - hex - } - } - `, - { - specVersions: uniq(specVersions) - } - ); - - for (const spec of response.specs) { - specs[spec.specVersion] = { - ...spec, - metadata: decodeMetadata(spec.hex) - }; - } - - return specs; -} diff --git a/src/services/runtimeSpecService.ts b/src/services/runtimeSpecService.ts new file mode 100644 index 00000000..ecfd32bb --- /dev/null +++ b/src/services/runtimeSpecService.ts @@ -0,0 +1,35 @@ +import { fetchArchive } from "./fetchService"; + +export async function getLatestRuntimeSpecVersion(network: string) { + const response = await fetchArchive<{spec: {specVersion: number}[]}>( + network, ` + query { + spec: metadata(orderBy: specVersion_DESC, limit: 1) { + specVersion + } + } + ` + ); + + const latestSpec = response.spec[0]; + + if (!latestSpec) { + throw new Error(`Cannot get latest spec for network '${network}'`); + } + + return latestSpec.specVersion; +} + +export async function getRuntimeSpecVersions(network: string) { + const response = await fetchArchive<{metadata: {specVersion: number}[]}>( + network, ` + query { + metadata(orderBy: specVersion_DESC) { + specVersion + } + } + ` + ); + + return response.metadata.map(it => it.specVersion); +} diff --git a/src/services/transfersService.ts b/src/services/transfersService.ts index 3165926a..90836034 100644 --- a/src/services/transfersService.ts +++ b/src/services/transfersService.ts @@ -4,13 +4,12 @@ import { MainSquidTransfer } from "../model/main-squid/mainSquidTransfer"; import { PaginationOptions } from "../model/paginationOptions"; import { Transfer } from "../model/transfer"; -import { addRuntimeSpecs } from "../utils/addRuntimeSpec"; import { decodeAddress } from "../utils/formatAddress"; import { extractConnectionItems } from "../utils/extractConnectionItems"; +import { rawAmountToDecimal } from "../utils/number"; import { fetchArchive, fetchMainSquid } from "./fetchService"; import { getNetwork, hasSupport } from "./networksService"; -import { rawAmountToDecimal } from "../utils/number"; export type TransfersFilter = { accountAddress_eq: string }; @@ -93,8 +92,7 @@ async function getMainSquidTransfers( ); const items = extractConnectionItems(response.transfersConnection, pagination, unifyMainSquidTransfer, network); - const itemsWithRuntimeSpec = await addRuntimeSpecs(network, items, () => "latest"); - const transfers = await addExtrinsicsInfo(network, itemsWithRuntimeSpec); + const transfers = await addExtrinsicsInfo(network, items); return transfers; } @@ -134,7 +132,7 @@ async function getArchiveExtrinsicsInfo(network: string, extrinsicHashes: string }, {} as Record); } -function unifyMainSquidTransfer(transfer: MainSquidTransfer, networkName: string): Omit { +function unifyMainSquidTransfer(transfer: MainSquidTransfer, networkName: string): Omit { const network = getNetwork(networkName); return { diff --git a/src/utils/addMetadata.ts b/src/utils/addMetadata.ts new file mode 100644 index 00000000..7409c447 --- /dev/null +++ b/src/utils/addMetadata.ts @@ -0,0 +1,35 @@ +import { ItemsResponse } from "../model/itemsResponse"; + +export async function addItemMetadata( + response: T|undefined, + getMetadata: (data: T) => M +) { + if (response === undefined) { + return undefined; + } + + return { + ...response, + metadata: await getMetadata(response) + }; +} + + +export async function addItemsMetadata( + response: ItemsResponse, + getMetadata: (data: T) => Promise +) { + const itemsWithMetadata = []; + + for (const item of response.data) { + itemsWithMetadata.push({ + ...item, + metadata: await getMetadata(item) + }); + } + + return { + ...response, + data: itemsWithMetadata + }; +} diff --git a/src/utils/addRuntimeSpec.ts b/src/utils/addRuntimeSpec.ts deleted file mode 100644 index 254e5ef1..00000000 --- a/src/utils/addRuntimeSpec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ItemsResponse } from "../model/itemsResponse"; -import { getRuntimeSpec, getRuntimeSpecs } from "../services/runtimeService"; - -import { uniq } from "./uniq"; - -export async function addRuntimeSpec(network: string, response: T|undefined, getSpecVersion: (data: T) => number|"latest") { - if (response === undefined) { - return undefined; - } - - const specVersion = getSpecVersion(response); - const spec = await getRuntimeSpec(network, specVersion); - - return { - ...response, - runtimeSpec: spec! - }; -} - - -export async function addRuntimeSpecs(network: string, response: ItemsResponse, getSpecVersion: (data: T) => number|"latest") { - const specVersions = uniq(response.data.map(getSpecVersion)); - - const specs = await getRuntimeSpecs(network, specVersions); - - const items = response.data.map(it => ({ - ...it, - runtimeSpec: specs[getSpecVersion(it)]! - })); - - - return { - ...response, - data: items - }; -} diff --git a/src/utils/decodeMetadata.ts b/src/utils/decodeMetadata.ts deleted file mode 100644 index 4028b59e..00000000 --- a/src/utils/decodeMetadata.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { TypeRegistry, Metadata, PortableRegistry } from "@polkadot/types"; -import { Si1Variant } from "@polkadot/types/interfaces"; -import { getSiName } from "@polkadot/types/metadata/util"; -import { DecodedCall, DecodedEvent, DecodedMetadata, DecodedPallet } from "../model/decodedMetadata"; - -function decodeCall(lookup: PortableRegistry, call: Si1Variant) { - const { fields } = call; - - const decodedCall: DecodedCall = { - name: call.name.toString(), - args: [] - }; - - decodedCall.args = fields.map((field, index) => ({ - name: field.name.unwrapOr(index).toString(), - type: getSiName(lookup, field.type), - typeName: field.typeName.isSome - ? field.typeName.unwrap().toString() - : undefined - })); - - return decodedCall; -} - -function decodeEvent(lookup: PortableRegistry, event: Si1Variant) { - const { fields } = event; - - const decodedEvent: DecodedEvent = { - name: event.name.toString(), - args: [] - }; - - decodedEvent.args = fields.map((field, index) => ({ - name: field.name.unwrapOr(index).toString(), - type: getSiName(lookup, field.type), - typeName: field.typeName.isSome - ? field.typeName.unwrap().toString() - : undefined - })); - - return decodedEvent; -} - -export function decodeMetadata(hex: `0x${string}`) { - const registry = new TypeRegistry(); - const metadata = new Metadata(registry, hex); - registry.setMetadata(metadata); - - const latestMetadata = metadata.asLatest; - const ss58Prefix = latestMetadata.registry.getChainProperties()?.ss58Format.unwrap()?.toNumber(); - - const decodedMetadata: DecodedMetadata = { - ss58Prefix: typeof ss58Prefix === "number" ? ss58Prefix : 42, - pallets: [] - }; - - decodedMetadata.pallets = latestMetadata.pallets.map(pallet => { - const decodedPallet: DecodedPallet = { - name: pallet.name.toString(), - calls: [], - events: [], - }; - - if (pallet.calls.isSome) { - const calls = latestMetadata.lookup.getSiType(pallet.calls.unwrap().type).def.asVariant.variants; - decodedPallet.calls = calls.map(call => decodeCall(latestMetadata.lookup, call)); - } - - if (pallet.events.isSome) { - const events = latestMetadata.lookup.getSiType(pallet.events.unwrap().type).def.asVariant.variants; - decodedPallet.events = events.map(event => decodeEvent(latestMetadata.lookup, event)); - } - - return decodedPallet; - }); - - return decodedMetadata; -} diff --git a/src/utils/string.ts b/src/utils/string.ts index 0adf369d..33652351 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -12,3 +12,11 @@ export function lowerFirst(str: string): string { export function noCase(str: string) { return str.replace(/[\s\-_]/g, "").toLowerCase(); } + +export function tryParseInt(str?: string) { + if (!str) { + return undefined; + } + + return parseInt(str) || undefined; +} diff --git a/src/utils/webWorker.ts b/src/utils/webWorker.ts new file mode 100644 index 00000000..1a5cffbd --- /dev/null +++ b/src/utils/webWorker.ts @@ -0,0 +1,48 @@ +type Parameters = T extends (...args: infer P) => any ? P : never; +type ReturnType = T extends (...args: any) => Promise ? R : never; + +/** + * Web worker handler + * + * Used to run the web worker in a type-safe way + */ +export class WebWorker { + constructor(protected worker: Worker) {} + + async run(method: M, ...args: Parameters): Promise> { + this.worker.postMessage({method, args}); + + const responseData = await new Promise>((resolve) => { + this.worker.onmessage = (e: MessageEvent>) => { + resolve(e.data); + }; + }); + + return responseData; + } + + terminate() { + this.worker.terminate(); + } +} + +interface WebWorkerRuntimeData { + method: string; + args: any[]; +} + +/** + * Web worker runtime + * + * Basically a type-safe wrapper around the web worker's runtime + * + * Must be separated from WebWorker class to prevent circular dependency between JS chunks + */ +export class WebWorkerRuntime { + constructor() { + self.onmessage = async (e: MessageEvent) => { + const {method, args} = e.data; + self.postMessage(await (this as any)[method](...args)); + }; + } +} diff --git a/src/workers/runtimeSpecWorker.runtime.ts b/src/workers/runtimeSpecWorker.runtime.ts new file mode 100644 index 00000000..f5e1d0be --- /dev/null +++ b/src/workers/runtimeSpecWorker.runtime.ts @@ -0,0 +1,107 @@ +import { Metadata, PortableRegistry, TypeRegistry, Vec } from "@polkadot/types"; +import { Si1Field } from "@polkadot/types/interfaces"; +import { getSiName } from "@polkadot/types/metadata/util"; + +import { RuntimeMetadataArg } from "../model/runtime-metadata/runtimeMetadataArg"; +import { runtimeMetadataRepository } from "../repositories/runtimeMetadataRepository"; +import { fetchArchive } from "../services/fetchService"; +import { WebWorkerRuntime } from "../utils/webWorker"; + +import { RuntimeSpecWorkerMethods } from "./runtimeSpecWorker"; + +/** + * The reason to obtaining runtime metadata in a web worker is + * because metadata decoding is memory and CPU-intensive operation + * and would block the main UI thread. + */ +class RuntimeSpecWorkerRuntime extends WebWorkerRuntime implements RuntimeSpecWorkerMethods { + async loadMetadata(network: string, specVersion: number) { + console.log("worker", network, specVersion); + + const response = await fetchArchive<{metadata: {hex: `0x${string}`}[]}>( + network, ` + query ($specVersion: Int!) { + metadata(where: {specVersion_eq: $specVersion}, orderBy: specVersion_DESC) { + hex + } + } + `, + { + specVersion + } + ); + + response.metadata[0] && await this.decodeAndSaveRuntimeMetadata(network, specVersion, response.metadata[0].hex); + } + + protected async decodeAndSaveRuntimeMetadata(network: string, specVersion: number, metadataHex: `0x${string}`) { + const registry = new TypeRegistry(); + const metadata = new Metadata(registry, metadataHex); + + registry.setMetadata(metadata); + + const latestMetadata = metadata.asLatest; + + const repository = runtimeMetadataRepository; + + await repository.transaction("rw", [ + repository.specs, + repository.pallets, + repository.calls, + repository.events + ], async () => { + await repository.specs.put({ + network, + specVersion + }); + + for (const pallet of latestMetadata.pallets) { + await repository.pallets.put({ + network, + specVersion, + name: pallet.name.toString() + }); + + if (pallet.calls.isSome) { + const calls = latestMetadata.lookup.getSiType(pallet.calls.unwrap().type).def.asVariant.variants; + + for (const call of calls) { + await repository.calls.put({ + network, + specVersion, + pallet: pallet.name.toString(), + name: call.name.toString(), + args: this.decodeArgs(latestMetadata.lookup, call.fields) + }); + } + } + + if (pallet.events.isSome) { + const events = latestMetadata.lookup.getSiType(pallet.events.unwrap().type).def.asVariant.variants; + + for (const event of events) { + await repository.events.put({ + network, + specVersion, + pallet: pallet.name.toString(), + name: event.name.toString(), + args: this.decodeArgs(latestMetadata.lookup, event.fields) + }); + } + } + } + }); + } + + protected decodeArgs(lookup: PortableRegistry, fields: Vec): RuntimeMetadataArg[] { + return fields.map((field, index) => ({ + name: field.name.unwrapOr(index).toString(), + type: getSiName(lookup, field.type), + typeName: field.typeName.isSome + ? field.typeName.unwrap().toString() + : undefined + })); + } +} + +new RuntimeSpecWorkerRuntime(); diff --git a/src/workers/runtimeSpecWorker.ts b/src/workers/runtimeSpecWorker.ts new file mode 100644 index 00000000..147049f6 --- /dev/null +++ b/src/workers/runtimeSpecWorker.ts @@ -0,0 +1,16 @@ +import { DecodedMetadata } from "../model/decodedMetadata"; +import { WebWorker } from "../utils/webWorker"; + +export interface RuntimeSpecWorkerMethods { + loadMetadata(network: string, specVersion: number): Promise; +} + +export class RuntimeSpecWorker extends WebWorker { + constructor() { + super(new Worker(new URL("./runtimeSpecWorker.runtime.ts", import.meta.url))); + } + + loadMetadata(network: string, specVersion: number) { + return this.run("loadMetadata", network, specVersion); + } +} diff --git a/tsconfig.json b/tsconfig.json index e2f1e72d..5cce071f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "lib": [ "dom", "dom.iterable", - "esnext" + "esnext", + "WebWorker" ], "allowJs": true, "skipLibCheck": true,