Skip to content

Commit

Permalink
feat: block functions (#116)
Browse files Browse the repository at this point in the history
- Introduced a new function to retrieve the best or finalized block.
- Added an experimental function for extracting extrinsics from a block.
  • Loading branch information
tien authored Aug 15, 2024
1 parent cc61c11 commit c2c3a61
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 5 deletions.
6 changes: 6 additions & 0 deletions .changeset/fast-pans-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@reactive-dot/core": minor
---

- Introduced a new function to retrieve the best or finalized block.
- Added an experimental function for extracting extrinsics from a block.
139 changes: 139 additions & 0 deletions packages/core/src/actions/get-block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {
getDynamicBuilder,
getLookupFn,
} from "@polkadot-api/metadata-builders";
import {
Struct,
enhanceCodec,
metadata as metadataCodec,
u8,
type V15,
} from "@polkadot-api/substrate-bindings";
import type {
Binary,
ChainDefinition,
Codec,
Enum,
FixedSizeBinary,
PolkadotClient,
SS58String,
TypedApi,
} from "polkadot-api";
import { map } from "rxjs/operators";

export type GetBlockOptions = {
tag?: "best" | "finalized";
};

export function getBlock<TOptions extends GetBlockOptions>(
client: PolkadotClient,
options?: TOptions,
) {
switch (options?.tag) {
case "best":
return client.bestBlocks$.pipe(map((blockInfos) => blockInfos.at(0)!));
case "finalized":
default:
return client.finalizedBlock$;
}
}

export async function unstable_getBlockExtrinsics(
client: PolkadotClient,
typedApi: TypedApi<ChainDefinition>,
blockHash: string,
) {
const v15MetadataBinary = await (
typedApi.apis["Metadata"]!["metadata_at_version"]! as (
version: number,
) => Promise<Binary | undefined>
)(15);

if (v15MetadataBinary === undefined) {
return undefined;
}

const metadataResult = metadataCodec.dec(v15MetadataBinary.asBytes());

if (metadataResult.metadata.tag !== "v15") {
return undefined;
}

const metadata = metadataResult.metadata.value;

const dynamicBuilder = await getOrCreateDynamicBuilder(client, metadata);

const version = enhanceCodec(
u8,
(value: { signed: boolean; version: number }) =>
(+!!value.signed << 7) | value.version,
(value) => ({
version: value & ~(1 << 7),
signed: !!(value & (1 << 7)),
}),
);

const address = dynamicBuilder.buildDefinition(
metadata.extrinsic.address,
) as Codec<
Enum<{
Id: SS58String;
Index: number | bigint;
Raw: Binary;
Address32: FixedSizeBinary<32>;
Address20: FixedSizeBinary<20>;
}>
>;

const call = dynamicBuilder.buildDefinition(
metadata.extrinsic.call,
) as Codec<{ module: string; method: string; args: unknown }>;

const inherentExtrinsic = Struct({
version: version as Codec<{ version: number; signed: false }>,
body: Struct({ call }),
});

const signedExtrinsic = Struct({
version: version as Codec<{ version: number; signed: true }>,
body: Struct({
signer: address,
signature: dynamicBuilder.buildDefinition(metadata.extrinsic.signature),
extra: dynamicBuilder.buildDefinition(metadata.extrinsic.extra),
call,
}),
});

const blockBody = await client.getBlockBody(blockHash);

const simpleVersion = Struct({
version: version,
});

return blockBody.map((hexBody: string) => {
const {
version: { signed },
} = simpleVersion.dec(hexBody);

return (signed ? signedExtrinsic.dec : inherentExtrinsic.dec)(hexBody);
});
}

const dynamicBuilders = new WeakMap<
PolkadotClient,
ReturnType<typeof getDynamicBuilder>
>();

async function getOrCreateDynamicBuilder(
client: PolkadotClient,
metadata: V15,
) {
if (dynamicBuilders.has(client)) {
return dynamicBuilders.get(client)!;
}

const lookup = getLookupFn(metadata);
const dynamicBuilder = getDynamicBuilder(lookup);

return dynamicBuilders.set(client, dynamicBuilder).get(client)!;
}
1 change: 1 addition & 0 deletions packages/core/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { aggregateWallets } from "./aggregate-wallets.js";
export { connectWallet } from "./connect-wallet.js";
export { disconnectWallet } from "./disconnect-wallet.js";
export { getAccounts } from "./get-accounts.js";
export { getBlock, unstable_getBlockExtrinsics } from "./get-block.js";
export { getClient } from "./get-client.js";
export { getConnectedWallets } from "./get-connected-wallets.js";
export { preflight, query } from "./query.js";
9 changes: 4 additions & 5 deletions packages/react/src/stores/block.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { clientAtomFamily } from "./client.js";
import type { ChainId } from "@reactive-dot/core";
import { type ChainId, getBlock } from "@reactive-dot/core";
import { atomFamily, atomWithObservable } from "jotai/utils";
import { from } from "rxjs";
import { switchMap, map } from "rxjs/operators";
import { switchMap } from "rxjs/operators";

export const finalizedBlockAtomFamily = atomFamily((chainId: ChainId) =>
atomWithObservable((get) =>
from(get(clientAtomFamily(chainId))).pipe(
switchMap((client) => client.finalizedBlock$),
switchMap((client) => getBlock(client, { tag: "finalized" })),
),
),
);

export const bestBlockAtomFamily = atomFamily((chainId: ChainId) =>
atomWithObservable((get) =>
from(get(clientAtomFamily(chainId))).pipe(
switchMap((client) => client.bestBlocks$),
map((blocks) => blocks.at(0)!),
switchMap((client) => getBlock(client, { tag: "best" })),
),
),
);

0 comments on commit c2c3a61

Please sign in to comment.