diff --git a/codegen/codecVisitor.test.ts b/codegen/codecVisitor.test.ts index 2bc8dbfa4..da8ed543c 100644 --- a/codegen/codecVisitor.test.ts +++ b/codegen/codecVisitor.test.ts @@ -7,7 +7,7 @@ import * as U from "../util/mod.ts"; for (const config of T.configs) { Deno.test(config.runtimeName, async () => { - const metadata = U.throwIfError(await C.run(new C.Metadata(config))); + const metadata = U.throwIfError(await C.run(C.metadata(config))); const codegen = await T.importCodegen(config); const deriveCodec = M.DeriveCodec(metadata.tys); const derivedCodecs = metadata.tys.map(deriveCodec); diff --git a/deps/zones.ts b/deps/zones.ts index 3f62bc925..2abba97af 100644 --- a/deps/zones.ts +++ b/deps/zones.ts @@ -1 +1 @@ -export * from "https://deno.land/x/zones@v0.1.0-beta.6/mod.ts"; +export * from "https://deno.land/x/zones@v0.1.0-beta.7/mod.ts"; diff --git a/effect/BlockRead.ts b/effect/BlockRead.ts deleted file mode 100644 index dcb83ff5b..000000000 --- a/effect/BlockRead.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Config } from "../config/mod.ts"; -import * as Z from "../deps/zones.ts"; -import * as U from "../util/mod.ts"; -import { $extrinsic } from "./core/$extrinsic.ts"; -import { deriveCodec } from "./core/deriveCodec.ts"; -import { Metadata } from "./Metadata.ts"; -import { RpcCall } from "./RpcCall.ts"; - -export class BlockRead]> extends Z.Name { - root; - - constructor( - config: Config, - ...[blockHash]: [...Rest] - ) { - super(); - const metadata_ = new Metadata(config, blockHash); - const $extrinsic_ = $extrinsic(deriveCodec(metadata_), metadata_, undefined!); - const call = new RpcCall(config, "chain_getBlock", [blockHash]); - const decoded = Z.call(Z.ls($extrinsic_, call), function mapExtrinsicCall([$extrinsic_, call]) { - const { block: { extrinsics, header }, justifications } = call.result; - return { - justifications, - block: { - header, - extrinsics: extrinsics.map((extrinsic: U.Hex) => { - return $extrinsic_.decode(U.hex.decode(extrinsic)); - }), - }, - }; - }); - this.root = Z.wrap(decoded, "block"); - } -} diff --git a/effect/BlockWatch.ts b/effect/BlockWatch.ts deleted file mode 100644 index 8078477ad..000000000 --- a/effect/BlockWatch.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Config } from "../config/mod.ts"; -import * as Z from "../deps/zones.ts"; -import * as known from "../known/mod.ts"; -import * as U from "../util/mod.ts"; -import { BlockRead } from "./BlockRead.ts"; -import { RpcCall } from "./RpcCall.ts"; -import { RpcSubscription } from "./RpcSubscription.ts"; -import { run } from "./run.ts"; - -export class BlockWatch extends Z.Name { - root; - - constructor( - config: Config, - createWatchHandler: U.CreateWatchHandler, - ) { - super(); - this.root = new RpcSubscription( - config, - "chain_subscribeNewHeads", - [], - function subscribeNewHeadsHandler(stop) { - const watchHandler = createWatchHandler(stop); - return async (result) => { - const blockNum: number = result.params.result.number; - const blockHash = Z.sel(new RpcCall(config, "chain_getBlockHash", [blockNum]), "result"); - // TODO: use derived util from Zones - const block = U.throwIfError(await run(new BlockRead(config, blockHash))); - watchHandler(block.block); - }; - }, - (ok) => new RpcCall(config, "chain_unsubscribeNewHead", [ok.result]), - ); - } -} diff --git a/effect/EntryRead.ts b/effect/EntryRead.ts deleted file mode 100644 index 998804c49..000000000 --- a/effect/EntryRead.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Config } from "../config/mod.ts"; -import * as Z from "../deps/zones.ts"; -import * as U from "../util/mod.ts"; -import { $storageKey } from "./core/$storageKey.ts"; -import { codec } from "./core/codec.ts"; -import { decoded } from "./core/decoded.ts"; -import { deriveCodec } from "./core/deriveCodec.ts"; -import { storageKey } from "./core/storageKey.ts"; -import { Metadata } from "./Metadata.ts"; -import { RpcCall } from "./RpcCall.ts"; - -export class EntryRead< - PalletName extends Z.$, - EntryName extends Z.$, - Keys extends unknown[], - Rest extends [blockHash?: Z.$], -> extends Z.Name { - root; - - constructor( - config: Config, - palletName: PalletName, - entryName: EntryName, - keys: [...Keys], - ...[blockHash]: [...Rest] - ) { - super(); - const metadata_ = new Metadata(config, blockHash); - const deriveCodec_ = deriveCodec(metadata_); - const palletMetadata_ = metadata_.pallet(palletName); - const entryMetadata_ = palletMetadata_.entry(entryName); - const $storageKey_ = $storageKey(deriveCodec_, palletMetadata_, entryMetadata_); - const storageKey_ = storageKey($storageKey_, ...keys); - const storageCall = new RpcCall(config, "state_getStorage", [storageKey_, blockHash]); - const entryValueTypeI = Z.sel(entryMetadata_, "value"); - const $entry = codec(deriveCodec_, entryValueTypeI); - const resultHex = Z.sel(storageCall, "result"); - this.root = decoded($entry, resultHex, "value"); - } -} diff --git a/effect/EntryWatch.ts b/effect/EntryWatch.ts deleted file mode 100644 index 2a291cec0..000000000 --- a/effect/EntryWatch.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Config } from "../config/mod.ts"; -import * as Z from "../deps/zones.ts"; -import * as rpc from "../rpc/mod.ts"; -import * as U from "../util/mod.ts"; -import { $storageKey } from "./core/$storageKey.ts"; -import { codec } from "./core/codec.ts"; -import { deriveCodec } from "./core/deriveCodec.ts"; -import { storageKey } from "./core/storageKey.ts"; -import { Metadata } from "./Metadata.ts"; -import { RpcCall } from "./RpcCall.ts"; -import { RpcSubscription } from "./RpcSubscription.ts"; - -export type WatchEntryEvent = [key?: U.Hex, value?: unknown]; - -export class EntryWatch< - PalletName extends Z.$, - EntryName extends Z.$, - Keys extends unknown[], -> extends Z.Name { - root; - - constructor( - readonly config: Config, - readonly palletName: PalletName, - readonly entryName: EntryName, - readonly keys: Keys, - readonly createWatchHandler: U.CreateWatchHandler, - ) { - super(); - const metadata_ = new Metadata(config); - const deriveCodec_ = deriveCodec(metadata_); - const palletMetadata_ = metadata_.pallet(palletName); - const entryMetadata_ = palletMetadata_.entry(entryName); - const $storageKey_ = $storageKey(deriveCodec_, palletMetadata_, entryMetadata_); - const entryValueTypeI = Z.sel(entryMetadata_, "value"); - const $entry = codec(deriveCodec_, entryValueTypeI); - const storageKeys = Z.call( - storageKey($storageKey_, ...keys.length ? [keys] : []), - function wrapWithList(v) { - return [v]; - }, - ); - const watchInit = Z.call($entry, function entryWatchInit($entry) { - return U.mapCreateWatchHandler( - createWatchHandler, - (message: rpc.NotifMessage) => { - return message.params.result.changes.map(([key, val]: any) => { - return [key, val ? $entry.decode(U.hex.decode(val)) : undefined]; - }); - }, - ); - }); - this.root = new RpcSubscription( - config, - "state_subscribeStorage", - [storageKeys], - watchInit, - (ok) => { - return new RpcCall(config, "state_unsubscribeStorage", [ok.result]); - }, - ); - } -} diff --git a/effect/KeyPageRead.ts b/effect/KeyPageRead.ts deleted file mode 100644 index 307c6ca35..000000000 --- a/effect/KeyPageRead.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Config } from "../config/mod.ts"; -import * as Z from "../deps/zones.ts"; -import * as U from "../util/mod.ts"; -import { $key } from "./core/$key.ts"; -import { $storageKey } from "./core/$storageKey.ts"; -import { deriveCodec } from "./core/deriveCodec.ts"; -import { storageKey } from "./core/storageKey.ts"; -import { Metadata } from "./Metadata.ts"; -import { RpcCall } from "./RpcCall.ts"; - -export class KeyPageRead< - PalletName extends Z.$, - EntryName extends Z.$, - Count extends Z.$, - Rest extends [start?: unknown[] | undefined, blockHash?: Z.$], -> extends Z.Name { - root; - - constructor( - config: Config, - palletName: PalletName, - entryName: EntryName, - count: Count, - ...[start, blockHash]: [...Rest] - ) { - super(); - const metadata_ = new Metadata(config, blockHash as Rest[1]); - const deriveCodec_ = deriveCodec(metadata_); - const palletMetadata_ = metadata_.pallet(palletName); - const entryMetadata_ = Z.call( - palletMetadata_.entry(entryName), - function assertIsMap(entryMetadata) { - if (entryMetadata.type !== "Map") { - return new ReadingKeysOfNonMapError(); - } - return entryMetadata; - }, - ); - const $storageKey_ = $storageKey(deriveCodec_, palletMetadata_, entryMetadata_); - const startKey = start ? storageKey($storageKey_, start) : undefined; - const storageKey_ = storageKey($storageKey_); - const call = new RpcCall(config, "state_getKeysPaged", [ - storageKey_, - count, - startKey, - blockHash as Rest[1], - ]); - const $key_ = $key(deriveCodec_, palletMetadata_, entryMetadata_); - const keysEncoded = Z.sel(call, "result"); - const keysDecoded = Z.call( - Z.ls($key_, keysEncoded), - function keysDecodedImpl([$key, keysEncoded]) { - return keysEncoded.map((keyEncoded: U.Hex) => { - return $key.decode(U.hex.decode(keyEncoded)); - }); - }, - ); - this.root = Z.wrap(keysDecoded, "keys"); - } -} - -export class ReadingKeysOfNonMapError extends U.ErrorCtor("ReadingKeysOfNonMap") {} diff --git a/effect/Metadata.ts b/effect/Metadata.ts deleted file mode 100644 index d1c309ccf..000000000 --- a/effect/Metadata.ts +++ /dev/null @@ -1,70 +0,0 @@ -import * as $ from "../deps/scale.ts"; -import * as Z from "../deps/zones.ts"; -import * as M from "../frame_metadata/mod.ts"; -import { Config } from "../mod.ts"; -import * as U from "../util/mod.ts"; -import { RpcCall } from "./RpcCall.ts"; - -export class Metadata]> extends Z.Name { - root; - - constructor(config: Config, ...[blockHash]: [...Rest]) { - super(); - this.root = Z.call( - new RpcCall(config, "state_getMetadata", [blockHash]), - function metadataImpl(call) { - try { - return M.fromPrefixedHex(call.result); - } catch (e) { - return e as $.CodecError; - } - }, - ); - } - - pallet = >(palletName: PalletName) => { - return new PalletMetadata(this, palletName); - }; -} - -export class PalletMetadata, PalletName extends Z.$> - extends Z.Name -{ - root; - - constructor( - readonly metadata: Metadata, - readonly palletName: PalletName, - ) { - super(); - this.root = Z.call( - Z.ls(metadata, palletName), - function palletMetadataImpl([metadata, palletName]) { - return M.getPallet(metadata, palletName); - }, - ); - } - - entry = >(entryName: EntryName) => { - return new EntryMetadata(this, entryName); - }; -} - -export class EntryMetadata, EntryName extends Z.$> - extends Z.Name -{ - root; - - constructor( - readonly palletMetadata: PalletMetadata, - readonly entryName: EntryName, - ) { - super(); - this.root = Z.call( - Z.ls(palletMetadata, entryName), - function entryMetadataImpl([palletMetadata, entryName]) { - return M.getEntry(palletMetadata, entryName); - }, - ); - } -} diff --git a/effect/RpcCall.ts b/effect/RpcCall.ts deleted file mode 100644 index 53aff279d..000000000 --- a/effect/RpcCall.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Config } from "../config/mod.ts"; -import * as Z from "../deps/zones.ts"; -import { RpcError } from "./common.ts"; -import { rpcClient } from "./core/rpcClient.ts"; - -export class RpcCall< - MethodName extends Z.$, - Params extends Z.Ls$, -> extends Z.Name { - root; - - constructor( - config: Config, - methodName: MethodName, - params: [...Params], - ) { - super(); - const client = rpcClient(config); - const deps = Z.ls(client, methodName, ...params); - this.root = Z.call( - Z.ls(deps, Z.rc(client, deps)), - async function rpcCallImpl([[client, methodName, ...params], rc]) { - const result = await client.call( - methodName, - params, - ); - if (result.error) { - return new RpcError({ - ...result.error, - attempt: { - methodName, - params, - }, - }); - } - if (rc() == 1) { - const close = await client.close(); - if (close instanceof Error) return close; - } - // TODO: should this effect implicitly index into `result`? - return result; - }, - ); - } -} diff --git a/effect/RpcSubscription.ts b/effect/RpcSubscription.ts deleted file mode 100644 index 45a2d36d9..000000000 --- a/effect/RpcSubscription.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Config } from "../config/mod.ts"; -import * as Z from "../deps/zones.ts"; -import * as rpc from "../rpc/mod.ts"; -import * as U from "../util/mod.ts"; -import { RpcError } from "./common.ts"; -import { rpcClient } from "./core/rpcClient.ts"; -import { run } from "./run.ts"; - -export class RpcSubscription< - MethodName extends Z.$, - Params extends Z.Ls$, - CreateListenerCb extends Z.$>, -> extends Z.Name { - root; - - constructor( - config: Config, - methodName: MethodName, - params: [...Params], - createListenerCb: CreateListenerCb, - cleanup?: (initOk: rpc.OkMessage) => Z.EffectLike, - ) { - super(); - const client = rpcClient(config); - const deps = Z.ls(client, methodName, createListenerCb, ...params); - this.root = Z.call( - Z.ls(deps, Z.rc(client, deps)), - async function rpcSubscriptionImpl([[client, methodName, createListenerCb, ...params], rc]) { - const result = await client.subscribe( - methodName, - params, - createListenerCb, - cleanup ? (x) => run(cleanup(x), undefined!) : undefined, - ); - if (result?.error) { - return new RpcError({ - ...result.error, - attempt: { - methodName, - params, - }, - }); - } - if (rc() == 1) { - const close = await client.close(); - if (close instanceof Error) return close; - } - // TODO: clean up typings –– should implicitly narrow to `undefined` - return result as undefined; - }, - ); - } -} - -// TODO: handle elsewhere -export class RpcSubscriptionError extends U.ErrorCtor("RpcSubscription") { - constructor(readonly error: rpc.ErrMessage["error"]) { - super(); - } -} diff --git a/effect/blockRead.ts b/effect/blockRead.ts new file mode 100644 index 000000000..13d91b328 --- /dev/null +++ b/effect/blockRead.ts @@ -0,0 +1,29 @@ +import { Config } from "../config/mod.ts"; +import * as Z from "../deps/zones.ts"; +import * as U from "../util/mod.ts"; +import { $extrinsic } from "./core/$extrinsic.ts"; +import { deriveCodec } from "./core/deriveCodec.ts"; +import { metadata } from "./metadata.ts"; +import { rpcCall } from "./rpcCall.ts"; + +export function blockRead]>( + config: Config, + ...[blockHash]: [...Rest] +) { + const metadata_ = metadata(config, blockHash); + const $extrinsic_ = $extrinsic(deriveCodec(metadata_), metadata_, undefined!); + const call = rpcCall(config, "chain_getBlock", [blockHash]); + const decoded = Z.call(Z.ls($extrinsic_, call), function mapExtrinsicCall([$extrinsic_, call]) { + const { block: { extrinsics, header }, justifications } = call.result; + return { + justifications, + block: { + header, + extrinsics: extrinsics.map((extrinsic: U.Hex) => { + return $extrinsic_.decode(U.hex.decode(extrinsic)); + }), + }, + }; + }); + return Z.wrap(decoded, "block"); +} diff --git a/effect/blockWatch.ts b/effect/blockWatch.ts new file mode 100644 index 000000000..50436d292 --- /dev/null +++ b/effect/blockWatch.ts @@ -0,0 +1,29 @@ +import { Config } from "../config/mod.ts"; +import * as known from "../known/mod.ts"; +import * as U from "../util/mod.ts"; +import { blockRead } from "./blockRead.ts"; +import { rpcCall } from "./rpcCall.ts"; +import { rpcSubscription } from "./rpcSubscription.ts"; +import { run } from "./run.ts"; + +export function blockWatch( + config: Config, + createWatchHandler: U.CreateWatchHandler, +) { + return rpcSubscription( + config, + "chain_subscribeNewHeads", + [], + function subscribeNewHeadsHandler(stop) { + const watchHandler = createWatchHandler(stop); + return async (result) => { + const blockNum: number = result.params.result.number; + const blockHash = rpcCall(config, "chain_getBlockHash", [blockNum]).access("result"); + // TODO: zones-level solution + const block = U.throwIfError(await run(blockRead(config, blockHash))); + watchHandler(block.block); + }; + }, + (ok) => rpcCall(config, "chain_unsubscribeNewHead", [ok.result]), + ); +} diff --git a/effect/entryRead.ts b/effect/entryRead.ts new file mode 100644 index 000000000..2ad04d4b3 --- /dev/null +++ b/effect/entryRead.ts @@ -0,0 +1,35 @@ +import { Config } from "../config/mod.ts"; +import * as Z from "../deps/zones.ts"; +import * as U from "../util/mod.ts"; +import { $storageKey } from "./core/$storageKey.ts"; +import { codec } from "./core/codec.ts"; +import { decoded } from "./core/decoded.ts"; +import { deriveCodec } from "./core/deriveCodec.ts"; +import { storageKey } from "./core/storageKey.ts"; +import { entryMetadata, metadata, palletMetadata } from "./metadata.ts"; +import { rpcCall } from "./rpcCall.ts"; + +export function entryRead< + PalletName extends Z.$, + EntryName extends Z.$, + Keys extends unknown[], + Rest extends [blockHash?: Z.$], +>( + config: Config, + palletName: PalletName, + entryName: EntryName, + keys: [...Keys], + ...[blockHash]: [...Rest] +) { + const metadata_ = metadata(config, blockHash); + const deriveCodec_ = deriveCodec(metadata_); + const palletMetadata_ = palletMetadata(metadata_, palletName); + const entryMetadata_ = entryMetadata(palletMetadata_, entryName); + const $storageKey_ = $storageKey(deriveCodec_, palletMetadata_, entryMetadata_); + const storageKey_ = storageKey($storageKey_, ...keys); + const storageCall = rpcCall(config, "state_getStorage", [storageKey_, blockHash]); + const entryValueTypeI = entryMetadata_.access("value"); + const $entry = codec(deriveCodec_, entryValueTypeI); + const resultHex = storageCall.access("result"); + return decoded($entry, resultHex, "value"); +} diff --git a/effect/entryWatch.ts b/effect/entryWatch.ts new file mode 100644 index 000000000..9b944361f --- /dev/null +++ b/effect/entryWatch.ts @@ -0,0 +1,58 @@ +import { Config } from "../config/mod.ts"; +import * as Z from "../deps/zones.ts"; +import * as rpc from "../rpc/mod.ts"; +import * as U from "../util/mod.ts"; +import { $storageKey } from "./core/$storageKey.ts"; +import { codec } from "./core/codec.ts"; +import { deriveCodec } from "./core/deriveCodec.ts"; +import { storageKey } from "./core/storageKey.ts"; +import { entryMetadata, metadata, palletMetadata } from "./metadata.ts"; +import { rpcCall } from "./rpcCall.ts"; +import { rpcSubscription } from "./rpcSubscription.ts"; + +export type WatchEntryEvent = [key?: U.Hex, value?: unknown]; + +export function entryWatch< + PalletName extends Z.$, + EntryName extends Z.$, + Keys extends unknown[], +>( + config: Config, + palletName: PalletName, + entryName: EntryName, + keys: Keys, + createWatchHandler: U.CreateWatchHandler, +) { + const metadata_ = metadata(config); + const deriveCodec_ = deriveCodec(metadata_); + const palletMetadata_ = palletMetadata(metadata_, palletName); + const entryMetadata_ = entryMetadata(palletMetadata_, entryName); + const $storageKey_ = $storageKey(deriveCodec_, palletMetadata_, entryMetadata_); + const entryValueTypeI = entryMetadata_.access("value"); + const $entry = codec(deriveCodec_, entryValueTypeI); + const storageKeys = Z.call( + storageKey($storageKey_, ...keys.length ? [keys] : []), + function wrapWithList(v) { + return [v]; + }, + ); + const watchInit = Z.call($entry, function entryWatchInit($entry) { + return U.mapCreateWatchHandler( + createWatchHandler, + (message: rpc.NotifMessage) => { + return message.params.result.changes.map(([key, val]: any) => { + return [key, val ? $entry.decode(U.hex.decode(val)) : undefined]; + }); + }, + ); + }); + return rpcSubscription( + config, + "state_subscribeStorage", + [storageKeys], + watchInit, + (ok) => { + return rpcCall(config, "state_unsubscribeStorage", [ok.result]); + }, + ); +} diff --git a/effect/extrinsic.test.ts b/effect/extrinsic.test.ts index 4be43b623..a509bf513 100644 --- a/effect/extrinsic.test.ts +++ b/effect/extrinsic.test.ts @@ -30,7 +30,7 @@ Deno.test({ await ctx.step({ name: "account balance updated", fn: async () => { - const state = await run(new C.EntryRead(T.westend, "System", "Account", [T.bob.publicKey])); + const state = await run(C.entryRead(T.westend, "System", "Account", [T.bob.publicKey])); assertObjectMatch(state, { value: { data: { free: 10000000000012345n } } }); }, }); diff --git a/effect/Extrinsic.ts b/effect/extrinsic.ts similarity index 69% rename from effect/Extrinsic.ts rename to effect/extrinsic.ts index 724b21a62..0ab8ac171 100644 --- a/effect/Extrinsic.ts +++ b/effect/extrinsic.ts @@ -9,9 +9,9 @@ import * as U from "../util/mod.ts"; import { $extrinsic } from "./core/$extrinsic.ts"; import { deriveCodec } from "./core/deriveCodec.ts"; import { hexDecode } from "./core/hex.ts"; -import { Metadata } from "./Metadata.ts"; -import { RpcCall } from "./RpcCall.ts"; -import { RpcSubscription } from "./RpcSubscription.ts"; +import { metadata } from "./metadata.ts"; +import { rpcCall } from "./rpcCall.ts"; +import { rpcSubscription } from "./rpcSubscription.ts"; export interface ExtrinsicProps { sender: M.MultiAddress; @@ -38,20 +38,19 @@ export class Extrinsic> { export class SignedExtrinsic< Props extends Z.Rec$, Sign extends Z.$, -> extends Z.Name { - root; +> { + extrinsic; constructor( readonly config: Config, readonly props_: Props, readonly sign: Sign, ) { - super(); const props = props_ as Z.Rec$Access; - const metadata_ = new Metadata(config); + const metadata_ = metadata(config); const deriveCodec_ = deriveCodec(metadata_); const $extrinsic_ = $extrinsic(deriveCodec_, metadata_, this.sign, config.addressPrefix); - const runtimeVersion = new RpcCall(config, "state_getRuntimeVersion", []); + const runtimeVersion = rpcCall(config, "state_getRuntimeVersion", []); const senderSs58 = Z.call(props.sender, function senderSs58(sender) { return ((): string => { switch (sender.type) { @@ -65,16 +64,14 @@ export class SignedExtrinsic< } })(); }); - const accountNextIndex = new RpcCall(config, "system_accountNextIndex", [senderSs58]); - const genesisHash = hexDecode( - Z.sel(new RpcCall(config, "chain_getBlockHash", [0]), "result"), - ); + const accountNextIndex = rpcCall(config, "system_accountNextIndex", [senderSs58]); + const genesisHash = hexDecode(rpcCall(config, "chain_getBlockHash", [0]).access("result")); const checkpointHash = props.checkpoint ? Z.call(props.checkpoint, function checkpointOrUndef(v) { return v ? U.hex.decode(v) : v; }) : genesisHash; - this.root = Z.call( + this.extrinsic = Z.call( Z.ls( $extrinsic_, props.sender, @@ -128,47 +125,36 @@ export class SignedExtrinsic< watch< WatchHandler extends U.CreateWatchHandler>, - >(watchHandler: WatchHandler): SignedExtrinsicWatch { - return new SignedExtrinsicWatch(this.config, this, watchHandler); + >(watchHandler: WatchHandler) { + return signedExtrinsicWatch(this.config, this.extrinsic, watchHandler); } - get sent(): SignedExtrinsicSent { - return new SignedExtrinsicSent(this.config, this); + get sent() { + return signedExtrinsicSent(this.config, this.extrinsic); } } // TODO: is this really required? Why not use the RPC call effect directly? -export class SignedExtrinsicWatch< +export function signedExtrinsicWatch< SignedExtrinsic extends Z.$, WatchHandler extends U.CreateWatchHandler>, -> extends Z.Name { - root; - - constructor( - readonly config: Config, - readonly signedExtrinsic: SignedExtrinsic, - readonly watchHandler: WatchHandler, - ) { - super(); - this.root = new RpcSubscription( - config, - "author_submitAndWatchExtrinsic", - [signedExtrinsic], - watchHandler, - // TODO: use effect system for cbs such as this - (ok) => new RpcCall(config, "author_unwatchExtrinsic", [ok.result]), - ); - } +>( + config: Config, + signedExtrinsic: SignedExtrinsic, + watchHandler: WatchHandler, +) { + return rpcSubscription( + config, + "author_submitAndWatchExtrinsic", + [signedExtrinsic], + watchHandler, + (ok) => rpcCall(config, "author_unwatchExtrinsic", [ok.result]), + ); } -export class SignedExtrinsicSent> extends Z.Name { - root; - - constructor( - readonly config: Config, - readonly signedExtrinsic: SignedExtrinsic, - ) { - super(); - this.root = new RpcCall(config, "author_submitExtrinsic", [signedExtrinsic]); - } +export function signedExtrinsicSent>( + config: Config, + signedExtrinsic: SignedExtrinsic, +) { + return rpcCall(config, "author_submitExtrinsic", [signedExtrinsic]); } diff --git a/effect/keyPageRead.ts b/effect/keyPageRead.ts new file mode 100644 index 000000000..a40a49516 --- /dev/null +++ b/effect/keyPageRead.ts @@ -0,0 +1,47 @@ +import { Config } from "../config/mod.ts"; +import * as Z from "../deps/zones.ts"; +import * as U from "../util/mod.ts"; +import { $key } from "./core/$key.ts"; +import { $storageKey } from "./core/$storageKey.ts"; +import { deriveCodec } from "./core/deriveCodec.ts"; +import { storageKey } from "./core/storageKey.ts"; +import { mapMetadata, metadata, palletMetadata } from "./metadata.ts"; +import { rpcCall } from "./rpcCall.ts"; + +export function keyPageRead< + PalletName extends Z.$, + EntryName extends Z.$, + Count extends Z.$, + Rest extends [start?: unknown[] | undefined, blockHash?: Z.$], +>( + config: Config, + palletName: PalletName, + entryName: EntryName, + count: Count, + ...[start, blockHash]: [...Rest] +) { + const metadata_ = metadata(config, blockHash as Rest[1]); + const deriveCodec_ = deriveCodec(metadata_); + const palletMetadata_ = palletMetadata(metadata_, palletName); + const entryMetadata_ = mapMetadata(palletMetadata_, entryName); + const $storageKey_ = $storageKey(deriveCodec_, palletMetadata_, entryMetadata_); + const startKey = start ? storageKey($storageKey_, start) : undefined; + const storageKey_ = storageKey($storageKey_); + const call = rpcCall(config, "state_getKeysPaged", [ + storageKey_, + count, + startKey, + blockHash as Rest[1], + ]); + const $key_ = $key(deriveCodec_, palletMetadata_, entryMetadata_); + const keysEncoded = call.access("result"); + const keysDecoded = Z.call( + Z.ls($key_, keysEncoded), + function keysDecodedImpl([$key, keysEncoded]) { + return keysEncoded.map((keyEncoded: U.Hex) => { + return $key.decode(U.hex.decode(keyEncoded)); + }); + }, + ); + return Z.wrap(keysDecoded, "keys"); +} diff --git a/effect/metadata.ts b/effect/metadata.ts new file mode 100644 index 000000000..4d0e56ef4 --- /dev/null +++ b/effect/metadata.ts @@ -0,0 +1,65 @@ +import * as $ from "../deps/scale.ts"; +import * as Z from "../deps/zones.ts"; +import * as M from "../frame_metadata/mod.ts"; +import { Config } from "../mod.ts"; +import * as U from "../util/mod.ts"; +import { rpcCall } from "./rpcCall.ts"; + +export function metadata]>( + config: Config, + ...[blockHash]: [...Rest] +) { + return Z.call( + rpcCall(config, "state_getMetadata", [blockHash]), + function metadataImpl(call) { + try { + return M.fromPrefixedHex(call.result); + } catch (e) { + return e as $.CodecError; + } + }, + ); +} + +export function palletMetadata, PalletName extends Z.$>( + metadata: Metadata, + palletName: PalletName, +) { + return Z.call( + Z.ls(metadata, palletName), + function palletMetadataImpl([metadata, palletName]) { + return M.getPallet(metadata, palletName); + }, + ); +} + +export function entryMetadata, EntryName extends Z.$>( + palletMetadata: PalletMetadata, + entryName: EntryName, +) { + return Z.call( + Z.ls(palletMetadata, entryName), + function entryMetadataImpl([palletMetadata, entryName]) { + return M.getEntry(palletMetadata, entryName); + }, + ); +} + +export function mapMetadata, EntryName extends Z.$>( + palletMetadata: PalletMetadata, + entryName: EntryName, +) { + return Z.call( + Z.ls(palletMetadata, entryName), + function mapMetadataImpl([palletMetadata, entryName]) { + const entryMetadata = M.getEntry(palletMetadata, entryName); + if (entryMetadata instanceof Error) return entryMetadata; + if (entryMetadata.type !== "Map") { + return new ExpectedMapError(); + } + return entryMetadata; + }, + ); +} + +export class ExpectedMapError extends U.ErrorCtor("ExpectedMap") {} diff --git a/effect/mod.ts b/effect/mod.ts index 4c5cdab71..0246f2049 100644 --- a/effect/mod.ts +++ b/effect/mod.ts @@ -1,10 +1,10 @@ -export * from "./BlockRead.ts"; -export * from "./BlockWatch.ts"; -export * from "./EntryRead.ts"; -export * from "./EntryWatch.ts"; -export * from "./Extrinsic.ts"; -export * from "./KeyPageRead.ts"; -export * from "./Metadata.ts"; -export * from "./RpcCall.ts"; -export * from "./RpcSubscription.ts"; +export * from "./blockRead.ts"; +export * from "./blockWatch.ts"; +export * from "./entryRead.ts"; +export * from "./entryWatch.ts"; +export * from "./extrinsic.ts"; +export * from "./keyPageRead.ts"; +export * from "./metadata.ts"; +export * from "./rpcCall.ts"; +export * from "./rpcSubscription.ts"; export * from "./run.ts"; diff --git a/effect/rpcCall.ts b/effect/rpcCall.ts new file mode 100644 index 000000000..1a47d9f78 --- /dev/null +++ b/effect/rpcCall.ts @@ -0,0 +1,37 @@ +import { Config } from "../config/mod.ts"; +import * as Z from "../deps/zones.ts"; +import { RpcError } from "./common.ts"; +import { rpcClient } from "./core/rpcClient.ts"; + +export function rpcCall, Params extends Z.Ls$>( + config: Config, + methodName: MethodName, + params: [...Params], +) { + const client = rpcClient(config); + const deps = Z.ls(client, methodName, ...params); + return Z.call( + Z.ls(deps, Z.rc(client, deps)), + async function rpcCallImpl([[client, methodName, ...params], rc]) { + const result = await client.call( + methodName, + params, + ); + if (result.error) { + return new RpcError({ + ...result.error, + attempt: { + methodName, + params, + }, + }); + } + if (rc() == 1) { + const close = await client.close(); + if (close instanceof Error) return close; + } + // TODO: should this effect implicitly index into `result`? + return result; + }, + ); +} diff --git a/effect/rpcSubscription.ts b/effect/rpcSubscription.ts new file mode 100644 index 000000000..5515a10cf --- /dev/null +++ b/effect/rpcSubscription.ts @@ -0,0 +1,56 @@ +import { Config } from "../config/mod.ts"; +import * as Z from "../deps/zones.ts"; +import * as rpc from "../rpc/mod.ts"; +import * as U from "../util/mod.ts"; +import { RpcError } from "./common.ts"; +import { rpcClient } from "./core/rpcClient.ts"; +import { run } from "./run.ts"; + +export function rpcSubscription< + MethodName extends Z.$, + Params extends Z.Ls$, + CreateListenerCb extends Z.$>, +>( + config: Config, + methodName: MethodName, + params: [...Params], + createListenerCb: CreateListenerCb, + // TODO: improve cleanup system + cleanup?: (initOk: rpc.OkMessage) => Z.Effect, +) { + const client = rpcClient(config); + const deps = Z.ls(client, methodName, createListenerCb, ...params); + return Z.call( + Z.ls(deps, Z.rc(client, deps)), + async function rpcSubscriptionImpl([[client, methodName, createListenerCb, ...params], rc]) { + const result = await client.subscribe( + methodName, + params, + createListenerCb, + cleanup ? (x) => run(cleanup(x), undefined!) : undefined, + ); + if (result?.error) { + return new RpcError({ + ...result.error, + attempt: { + methodName, + params, + }, + }); + } + if (rc() == 1) { + const close = await client.close(); + if (close instanceof Error) return close; + } + // TODO: clean up typings –– should implicitly narrow to `undefined` + return result as undefined; + }, + ); +} + +// TODO: handle elsewhere +export class RpcSubscriptionError extends U.ErrorCtor("RpcSubscription") { + constructor(readonly error: rpc.ErrMessage["error"]) { + super(); + } +} diff --git a/examples/balance.ts b/examples/balance.ts index 6de6c5545..5e17cc06a 100644 --- a/examples/balance.ts +++ b/examples/balance.ts @@ -2,6 +2,6 @@ import * as C from "../mod.ts"; import * as T from "../test_util/mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.EntryRead(T.polkadot, "System", "Account", [T.alice.publicKey]); +const root = C.entryRead(T.polkadot, "System", "Account", [T.alice.publicKey]); console.log(U.throwIfError(await C.run(root))); diff --git a/examples/derived.ts b/examples/derived.ts index 8740484a7..4c656f600 100644 --- a/examples/derived.ts +++ b/examples/derived.ts @@ -1,13 +1,12 @@ import * as C from "../mod.ts"; import * as U from "../util/mod.ts"; -const ids = new C.EntryRead(C.polkadot, "Paras", "Parachains", []); +const ids = C.entryRead(C.polkadot, "Paras", "Parachains", []) + .access("value") + .as(); -// TODO: fix error –– client rc goes down to 1 before new reads initialized -// aka., client disconnects before the following reads are evaluated -const root = C.each(C.sel(ids, "value"), (id) => { - return new C.EntryRead(C.polkadot, "Paras", "Heads", [id]); +const root = C.each(ids, (id) => { + return C.entryRead(C.polkadot, "Paras", "Heads", [id]); }); -// @ts-ignore for now console.log(U.throwIfError(await C.run(root))); diff --git a/examples/first_ten_keys.ts b/examples/first_ten_keys.ts index 7f45d92d5..4f7405f3f 100644 --- a/examples/first_ten_keys.ts +++ b/examples/first_ten_keys.ts @@ -2,6 +2,6 @@ import * as C from "../mod.ts"; import * as T from "../test_util/mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.KeyPageRead(T.polkadot, "System", "Account", 10); +const root = C.keyPageRead(T.polkadot, "System", "Account", 10); console.log(U.throwIfError(await C.run(root)).keys); diff --git a/examples/metadata.ts b/examples/metadata.ts index 994f09b7a..661be9037 100644 --- a/examples/metadata.ts +++ b/examples/metadata.ts @@ -2,6 +2,6 @@ import * as C from "../mod.ts"; import * as T from "../test_util/mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.Metadata(T.polkadot); +const root = C.metadata(T.polkadot); console.log(U.throwIfError(await C.run(root))); diff --git a/examples/read_block.ts b/examples/read_block.ts index 93b827beb..afde49de0 100644 --- a/examples/read_block.ts +++ b/examples/read_block.ts @@ -1,6 +1,6 @@ import * as C from "../mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.BlockRead(C.polkadot); +const root = C.blockRead(C.polkadot); console.log(U.throwIfError(await C.run(root))); diff --git a/examples/read_events.ts b/examples/read_events.ts index 9a1987e13..ff8beaac5 100644 --- a/examples/read_events.ts +++ b/examples/read_events.ts @@ -1,6 +1,6 @@ import * as C from "../mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.EntryRead(C.polkadot, "System", "Events", []); +const root = C.entryRead(C.polkadot, "System", "Events", []); console.log(U.throwIfError(await C.run(root))); diff --git a/examples/rpc_call.ts b/examples/rpc_call.ts index bde845c0e..988b7cb3a 100644 --- a/examples/rpc_call.ts +++ b/examples/rpc_call.ts @@ -1,6 +1,6 @@ import * as C from "../mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.RpcCall(C.polkadot, "rpc_methods", []); +const root = C.rpcCall(C.polkadot, "rpc_methods", []); console.log(U.throwIfError(await C.run(root))); diff --git a/examples/rpc_subscription.ts b/examples/rpc_subscription.ts index e4d853337..2df551509 100644 --- a/examples/rpc_subscription.ts +++ b/examples/rpc_subscription.ts @@ -2,7 +2,7 @@ import * as C from "../mod.ts"; import * as T from "../test_util/mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.RpcSubscription(T.polkadot, "chain_subscribeNewHead", [], (stop) => { +const root = C.rpcSubscription(T.polkadot, "chain_subscribeNewHead", [], (stop) => { let i = 0; return (m) => { i++; diff --git a/examples/those_of_subxt/read_bonded.ts b/examples/those_of_subxt/read_bonded.ts index 81c5f55c3..0d3da9b0d 100644 --- a/examples/those_of_subxt/read_bonded.ts +++ b/examples/those_of_subxt/read_bonded.ts @@ -4,6 +4,6 @@ import * as U from "../../util/mod.ts"; const aliceStash = T.alice.derive("//stash"); -const aliceBonded = new C.EntryRead(T.polkadot, "Staking", "Bonded", [aliceStash.publicKey]); +const aliceBonded = C.entryRead(T.polkadot, "Staking", "Bonded", [aliceStash.publicKey]); console.log(U.throwIfError(await C.run(aliceBonded))); diff --git a/examples/those_of_subxt/read_era_rewards.ts b/examples/those_of_subxt/read_era_rewards.ts index c87c30573..c11c14236 100644 --- a/examples/those_of_subxt/read_era_rewards.ts +++ b/examples/those_of_subxt/read_era_rewards.ts @@ -1,8 +1,9 @@ import * as C from "../../mod.ts"; import * as U from "../../util/mod.ts"; -const raw = new C.EntryRead(C.westend, "Staking", "ActiveEra", []); -const idx = C.sel(C.sel(raw, "value"), "index"); -const eraRewardPoints = new C.EntryRead(C.westend, "Staking", "ErasRewardPoints", [idx]); +const idx = C.entryRead(C.westend, "Staking", "ActiveEra", []) + .access("value") + .access("index"); +const eraRewardPoints = C.entryRead(C.westend, "Staking", "ErasRewardPoints", [idx]); console.log(U.throwIfError(await C.run(eraRewardPoints))); diff --git a/examples/ticker.ts b/examples/ticker.ts index 4b9514232..c7ad9c7d2 100644 --- a/examples/ticker.ts +++ b/examples/ticker.ts @@ -2,7 +2,7 @@ import * as C from "../mod.ts"; import * as T from "../test_util/mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.EntryWatch(T.polkadot, "Timestamp", "Now", [], (stop) => { +const root = C.entryWatch(T.polkadot, "Timestamp", "Now", [], (stop) => { let i = 0; return (m) => { i++; diff --git a/examples/watch_blocks.ts b/examples/watch_blocks.ts index 29ae6d1c3..b3c6e0648 100644 --- a/examples/watch_blocks.ts +++ b/examples/watch_blocks.ts @@ -1,7 +1,7 @@ import * as C from "../mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.BlockWatch(C.polkadot, (stop) => { +const root = C.blockWatch(C.polkadot, (stop) => { let i = 0; return ({ block }) => { console.log(block.header); diff --git a/examples/watch_blocks_iter.ts b/examples/watch_blocks_iter.ts index a083be502..25b20ff39 100644 --- a/examples/watch_blocks_iter.ts +++ b/examples/watch_blocks_iter.ts @@ -7,7 +7,7 @@ import * as C from "../mod.ts"; const watchIter = C.watchIter(); -C.run(new C.BlockWatch(C.rococo, watchIter)); +C.run(C.blockWatch(C.rococo, watchIter)); let i = 0; diff --git a/examples/watch_events.ts b/examples/watch_events.ts index 4c5917c11..1aef2c1aa 100644 --- a/examples/watch_events.ts +++ b/examples/watch_events.ts @@ -1,7 +1,7 @@ import * as C from "../mod.ts"; import * as U from "../util/mod.ts"; -const root = new C.EntryWatch(C.rococo, "System", "Events", [], (stop) => { +const root = C.entryWatch(C.rococo, "System", "Events", [], (stop) => { let i = 0; return (event) => { i++; diff --git a/test_util/codegen.ts b/test_util/codegen.ts index 4e6e0dea4..5491ffbf1 100644 --- a/test_util/codegen.ts +++ b/test_util/codegen.ts @@ -8,7 +8,7 @@ export type CodegenModule = any; const memo = new Map>(); export function importCodegen(config: TestConfig) { return U.getOr(memo, config, async () => { - const metadata = U.throwIfError(await C.run(new C.Metadata(config))); + const metadata = U.throwIfError(await C.run(C.metadata(config))); const outputDir = path.join( path.dirname(path.fromFileUrl(import.meta.url)), "../target/codegen/",