diff --git a/effects/const.ts b/effects/const.ts index d366cd168..e83ace124 100644 --- a/effects/const.ts +++ b/effects/const.ts @@ -2,8 +2,8 @@ import * as Z from "../deps/zones.ts"; import * as rpc from "../rpc/mod.ts"; import * as U from "../util/mod.ts"; import { codec } from "./core/codec.ts"; -import { decoded } from "./core/decoded.ts"; import { deriveCodec } from "./core/deriveCodec.ts"; +import * as e$ from "./core/scale.ts"; import { constMetadata, metadata, palletMetadata } from "./metadata.ts"; export function const_>(client: Client) { @@ -23,7 +23,7 @@ export function const_>(client: Client) { const entryValueTypeI = constMetadata_.access("ty").access("id"); const constValue = constMetadata_.access("value"); const $const = codec(deriveCodec_, entryValueTypeI); - return decoded($const, constValue, "value").zoned("Const"); + return e$.decoded($const, constValue, "value").zoned("Const"); }; } Object.defineProperty(const_, "name", { diff --git a/effects/core/$key.ts b/effects/core/$key.ts deleted file mode 100644 index 13ec2412f..000000000 --- a/effects/core/$key.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as Z from "../../deps/zones.ts"; -import * as M from "../../frame_metadata/mod.ts"; - -export const $key = Z.call.fac(( - deriveCodec: M.DeriveCodec, - pallet: M.Pallet, - storageEntry: M.StorageEntry, -) => { - return M.$storageKey({ - deriveCodec, - pallet, - storageEntry, - }); -}); diff --git a/effects/core/decoded.ts b/effects/core/decoded.ts deleted file mode 100644 index 9cc0afde1..000000000 --- a/effects/core/decoded.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as $ from "../../deps/scale.ts"; -import * as Z from "../../deps/zones.ts"; - -export function decoded< - Codec extends Z.$<$.Codec>, - Encoded extends Z.$, - Key extends Z.$, ->( - codec: Codec, - encoded: Encoded, - key: Key, -) { - return Z.call( - Z.ls(codec, encoded, key), - function decodedImpl([codec, encoded, key]): Record, any> { - return { [key]: codec.decode(encoded) } as any; - }, - ); -} diff --git a/effects/core/hex.ts b/effects/core/hex.ts deleted file mode 100644 index bce33905f..000000000 --- a/effects/core/hex.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as Z from "../../deps/zones.ts"; -import * as U from "../../util/mod.ts"; - -export const hexEncode = Z.call.fac(U.hex.encode); -export const hexDecode = Z.call.fac(U.hex.decode); diff --git a/effects/core/option.ts b/effects/core/option.ts new file mode 100644 index 000000000..2efbdeb93 --- /dev/null +++ b/effects/core/option.ts @@ -0,0 +1,11 @@ +import * as Z from "../../deps/zones.ts"; + +// TODO: move this into zones +export function option( + target: Target, + use: (resolved: NonNullable>) => UseResult, +): Z.Effect, Z.E | Extract> { + return Z.call(target, (resolved) => { + return resolved ? use(resolved) : undefined; + }); +} diff --git a/effects/core/scale.ts b/effects/core/scale.ts new file mode 100644 index 000000000..49ea20092 --- /dev/null +++ b/effects/core/scale.ts @@ -0,0 +1,35 @@ +import * as $ from "../../deps/scale.ts"; +import * as Z from "../../deps/zones.ts"; + +export function decoded< + Codec extends Z.$<$.Codec>, + Encoded extends Z.$, + Key extends Z.$, +>( + codec: Codec, + encoded: Encoded, + key: Key, +) { + return Z.call( + Z.ls(codec, encoded, key), + function decodedImpl([codec, encoded, key]): Record, any> { + return { [key]: codec.decode(encoded) } as any; + }, + ); +} + +// TODO: eventually, utilize `V` to toggle runtime validation +export function encoded>, Decoded>( + codec: Codec, + decoded: Decoded, + isAsync?: boolean, +) { + return Z.call(Z.ls(codec, decoded, isAsync), function encodedImpl([codec, decoded]) { + try { + $.assert(codec, decoded); + } catch (e) { + return e as $.ScaleAssertError; + } + return codec[isAsync ? "encodeAsync" : "encode"](decoded); + }); +} diff --git a/effects/core/storageKey.ts b/effects/core/storageKey.ts deleted file mode 100644 index 5fea34201..000000000 --- a/effects/core/storageKey.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as $ from "../../deps/scale.ts"; -import * as Z from "../../deps/zones.ts"; -import * as U from "../../util/mod.ts"; - -export const storageKey = Z.call.fac(( - $storageKey: $.Codec, - ...keys: unknown[] -) => { - return U.hex.encode($storageKey.encode(keys)) as U.Hex; -}); diff --git a/effects/entryRead.ts b/effects/entryRead.ts index 7f8a4006d..0c79b2994 100644 --- a/effects/entryRead.ts +++ b/effects/entryRead.ts @@ -3,10 +3,8 @@ 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 { decoded } from "./core/decoded.ts"; import { deriveCodec } from "./core/deriveCodec.ts"; -import { hexDecode } from "./core/hex.ts"; -import { storageKey } from "./core/storageKey.ts"; +import * as e$ from "./core/scale.ts"; import { entryMetadata, metadata, palletMetadata } from "./metadata.ts"; import { state } from "./rpc/known.ts"; @@ -27,10 +25,11 @@ export function entryRead>(client: Client) { const palletMetadata_ = palletMetadata(metadata_, palletName); const entryMetadata_ = entryMetadata(palletMetadata_, entryName); const $storageKey_ = $storageKey(deriveCodec_, palletMetadata_, entryMetadata_); - const storageKey_ = storageKey($storageKey_, ...keys); - const storageValueHex = state.getStorage(client)(storageKey_, blockHash); + const storageKey = Z.call(e$.encoded($storageKey_, Z.ls(...keys)), U.hex.encode); + const storageBytesHex = state.getStorage(client)(storageKey, blockHash); + const storageBytes = Z.call(storageBytesHex, U.hex.decode); const entryValueTypeI = entryMetadata_.access("value"); const $entry = codec(deriveCodec_, entryValueTypeI); - return decoded($entry, hexDecode(storageValueHex), "value").zoned("EntryRead"); + return e$.decoded($entry, storageBytes, "value").zoned("EntryRead"); }; } diff --git a/effects/entryWatch.ts b/effects/entryWatch.ts index 22e1bd98d..907e62a5e 100644 --- a/effects/entryWatch.ts +++ b/effects/entryWatch.ts @@ -5,11 +5,11 @@ 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 * as e$ from "./core/scale.ts"; import { entryMetadata, metadata, palletMetadata } from "./metadata.ts"; import { state } from "./rpc/known.ts"; -export type WatchEntryEvent = [key?: U.Hex, value?: unknown]; +export type WatchEntryEvent = [key?: unknown, value?: unknown]; export function entryWatch>(client: Client) { return < @@ -30,9 +30,9 @@ export function entryWatch>(client: Client) { const entryValueTypeI = entryMetadata_.access("value"); const $entry = codec(deriveCodec_, entryValueTypeI); const storageKeys = Z.call( - storageKey($storageKey_, ...keys.length ? [keys] : []), - function wrapWithList(v) { - return [v]; + e$.encoded($storageKey_, keys.length ? [keys] : []), + function hexEncodeAndWrapWithList(v) { + return [U.hex.encode(v)]; }, ); const listenerMapped = Z.call(Z.ls($entry, listener), ([$entry, listener]) => { @@ -40,8 +40,13 @@ export function entryWatch>(client: Client) { this: rpc.ClientSubscribeContext, changeset: known.StorageChangeSet, ) { + // TODO: in some cases there might be keys to decode + // key ? $storageKey.decode(U.hex.decode(key)) : undefined + const getKey = (key: known.Hex) => { + return key; + }; const changes: WatchEntryEvent[] = changeset.changes.map(([key, val]) => { - return [key, val ? $entry.decode(U.hex.decode(val)) : undefined]; + return [getKey(key), val ? $entry.decode(U.hex.decode(val)) : undefined]; }); listener.apply(this, [changes]); }; diff --git a/effects/extrinsic.ts b/effects/extrinsic.ts index cc2d9d489..3370ed793 100644 --- a/effects/extrinsic.ts +++ b/effects/extrinsic.ts @@ -8,7 +8,8 @@ import * as U from "../util/mod.ts"; import { const as const_ } from "./const.ts"; import { $extrinsic } from "./core/$extrinsic.ts"; import { deriveCodec } from "./core/deriveCodec.ts"; -import { hexDecode } from "./core/hex.ts"; +import { option } from "./core/option.ts"; +import * as e$ from "./core/scale.ts"; import { metadata } from "./metadata.ts"; import { author, chain, system } from "./rpc/known.ts"; @@ -43,19 +44,22 @@ export class SignedExtrinsic< Props extends Z.Rec$, Sign extends Z.$, > { + props; extrinsic; - constructor(readonly props_: Props, readonly sign: Sign) { - const props = props_ as Z.Rec$Access; - const metadata_ = metadata(props.client)(); + constructor(props_: Props, readonly sign: Sign) { + this.props = props_ as Z.Rec$Access; + const metadata_ = metadata(this.props.client)(); const deriveCodec_ = deriveCodec(metadata_); - const addrPrefix = const_(props.client)("System", "SS58Prefix").access("value").as(); + const addrPrefix = const_(this.props.client)("System", "SS58Prefix") + .access("value").as(); const $extrinsic_ = $extrinsic(deriveCodec_, metadata_, this.sign, addrPrefix); - const versions = const_(props.client)("System", "Version").access("value"); + const versions = const_(this.props.client)("System", "Version").access("value"); const specVersion = versions.access("spec_version").as(); const transactionVersion = versions.access("transaction_version").as(); + // TODO: create match effect in zones and use here const senderSs58 = Z.call( - Z.ls(addrPrefix, props.sender), + Z.ls(addrPrefix, this.props.sender), function senderSs58([addrPrefix, sender]) { switch (sender.type) { case "Id": { @@ -68,79 +72,45 @@ export class SignedExtrinsic< } }, ); - const nonce = system.accountNextIndex(props.client)(senderSs58); - const genesisHash = hexDecode(chain.getBlockHash(props.client)(0)); - const checkpointHash = props.checkpoint - ? Z.call(props.checkpoint, function checkpointOrUndef(v) { - return v ? U.hex.decode(v) : v; - }) + const nonce = system.accountNextIndex(this.props.client)(senderSs58); + const genesisHashBytes = chain.getBlockHash(this.props.client)(0); + const genesisHash = Z.call(genesisHashBytes, U.hex.decode); + const checkpointHash = this.props.checkpoint + ? option(this.props.checkpoint, U.hex.decode) : genesisHash; - this.extrinsic = Z.call( - Z.ls( - $extrinsic_, - props.sender, - props.methodName, - props.palletName, - specVersion, - transactionVersion, - nonce, - genesisHash, - props.args, - checkpointHash, - props.tip, - props.mortality, - ), - async function formExtrinsicHex([ - $extrinsic, - sender, - methodName, - palletName, - specVersion, - transactionVersion, - nonce, - genesisHash, - args, - checkpoint, - tip, - mortality, - ]) { - const extrinsicBytes = await $extrinsic.encodeAsync({ - protocolVersion: 4, // TODO: grab this from elsewhere - palletName, - methodName, - args, - signature: { - address: sender, - extra: [ - mortality - ? { - type: "Mortal", - value: mortality, - } - : { type: "Immortal" }, - nonce, - tip || 0, - ], - additional: [specVersion, transactionVersion, checkpoint, genesisHash], - }, - }); - return U.hex.encode(extrinsicBytes); - }, - ); + const $extrinsicProps = Z.rec({ + protocolVersion: 4, + palletName: this.props.palletName, + methodName: this.props.methodName, + args: this.props.args, + signature: Z.rec({ + address: this.props.sender, + extra: Z.ls( + this.props.mortality + ? Z.rec({ type: "Mortal", value: this.props.mortality }) + : { type: "Immortal" }, + nonce, + this.props.tip || 0, + ), + additional: Z.ls(specVersion, transactionVersion, checkpointHash, genesisHash), + }), + }); + this.extrinsic = Z.call(e$.encoded($extrinsic_, $extrinsicProps, true), U.hex.encode); } watch>>( listener: Listener, ) { - const subscriptionId = author.submitAndWatchExtrinsic( - this.props_.client as Props["client"], - )([this.extrinsic], listener); - return author.unwatchExtrinsic(this.props_.client as Props["client"])(subscriptionId) + const subscriptionId = author.submitAndWatchExtrinsic(this.props.client)( + [this.extrinsic], + listener, + ); + return author.unwatchExtrinsic(this.props.client)(subscriptionId) .zoned("ExtrinsicWatch"); } get sent() { - return author.submitExtrinsic(this.props_.client as Props["client"])(this.extrinsic) + return author.submitExtrinsic(this.props.client)(this.extrinsic) .zoned("ExtrinsicSent"); } } diff --git a/effects/keyPageRead.ts b/effects/keyPageRead.ts index dcd4ca282..4d49af8aa 100644 --- a/effects/keyPageRead.ts +++ b/effects/keyPageRead.ts @@ -1,10 +1,9 @@ import * as Z from "../deps/zones.ts"; import * as rpc from "../rpc/mod.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 * as e$ from "./core/scale.ts"; import { mapMetadata, metadata, palletMetadata } from "./metadata.ts"; import { state } from "./rpc/known.ts"; @@ -25,22 +24,20 @@ export function keyPageRead>(client: Client) { 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 storageKey = Z.call(e$.encoded($storageKey_, []), U.hex.encode); + const startKey = start ? Z.call(e$.encoded($storageKey_, []), U.hex.encode) : undefined; const keysEncoded = state.getKeysPaged(client)( - storageKey_, + storageKey, count, startKey, blockHash as Rest[1], ); - const $key_ = $key(deriveCodec_, palletMetadata_, entryMetadata_); - return Z.call( - Z.ls($key_, keysEncoded), - function keysDecodedImpl([$key, keysEncoded]) { + return Z + .call(Z.ls($storageKey_, keysEncoded), function keysDecodedImpl([$key, keysEncoded]) { return keysEncoded.map((keyEncoded: U.Hex) => { return $key.decode(U.hex.decode(keyEncoded)); }); - }, - ).zoned("KeyPageRead"); + }) + .zoned("KeyPageRead"); }; } diff --git a/effects/metadata.ts b/effects/metadata.ts index 26b58f35e..7b5874776 100644 --- a/effects/metadata.ts +++ b/effects/metadata.ts @@ -5,6 +5,7 @@ import * as rpc from "../rpc/mod.ts"; import * as U from "../util/mod.ts"; import { state } from "./rpc/known.ts"; +// TODO: callable object so that one doesn't need the extra parens when not specifying block hash? export function metadata>(client: Client) { return ]>(...[blockHash]: [...Rest]) => { return Z.call(