Skip to content
This repository was archived by the owner on Sep 14, 2023. It is now read-only.
4 changes: 2 additions & 2 deletions examples/watch.eg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const now = polkadot.Timestamp.Now.value(undefined, polkadot.latestBlockHash)
/// Create a simple counter so that we can break iteration at 3.
let i = 0

/// Use the `watch` method to retrieve an async iterable, which will
/// gather and yield the `collection`-described data upon new blocks.
/// Use the `iter` method to get an async iterable, which will
/// gather and yield the storage value upon new blocks.
for await (const item of now.iter()) {
console.log(item)
$.assert($.u64, item)
Expand Down
2 changes: 2 additions & 0 deletions fluent/AccountIdRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { Rune } from "../rune/mod.ts"
import { AddressPrefixChain, ChainRune } from "./ChainRune.ts"
import { Ss58Rune } from "./Ss58Rune.ts"

/** A rune representing a public key */
export class AccountIdRune<out U> extends Rune<Uint8Array, U> {
static from<X>(...[accountId]: RunicArgs<X, [accountId: Uint8Array]>) {
return Rune.resolve(accountId).into(this)
}

/** Get an SS58 of the current public key on the specified chain */
ss58<C extends AddressPrefixChain, U>(chain: ChainRune<C, U>) {
return Rune
.fn(ss58.encode)
Expand Down
2 changes: 2 additions & 0 deletions fluent/BlockHashRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { BlockRune } from "./BlockRune.ts"
import { Chain } from "./ChainRune.ts"
import { PatternRune } from "./PatternRune.ts"

/** A rune representing a block hash */
export class BlockHashRune<out C extends Chain, out U> extends PatternRune<string, C, U> {
/** Get the block at the current block hash */
block() {
return this.chain.connection
.call("chain_getBlock", this.as(BlockHashRune))
Expand Down
5 changes: 5 additions & 0 deletions fluent/BlockRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,28 @@ import { PatternRune } from "./PatternRune.ts"
export class BlockRune<out C extends Chain, out U>
extends PatternRune<known.SignedBlock, C, U, BlockHashRune<C, U>>
{
/** The hash of the current block */
hash = this.parent

/** The header of the current block */
header() {
return this.into(ValueRune).access("block", "header")
}

/** The list of scale-encoded extrinsics of the current block */
extrinsicsRaw() {
return this.into(ValueRune).access("block", "extrinsics")
}

/** The list of the extrinsics of the current block */
extrinsics() {
return this
.extrinsicsRaw()
.into(ArrayRune)
.mapArray((h) => this.chain.$extrinsic.decoded(h.map(hex.decode)))
}

/** The list of events of the current block */
events() {
return this.chain
.pallet("System")
Expand Down
27 changes: 26 additions & 1 deletion fluent/ChainRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ConnectionRune } from "./ConnectionRune.ts"
import { ExtrinsicRune } from "./ExtrinsicRune.ts"
import { PalletRune } from "./PalletRune.ts"

/** A container for the inner connection and FRAME metadata of a given chain */
export interface Chain<M extends FrameMetadata = FrameMetadata> {
connection: Connection
metadata: M
Expand Down Expand Up @@ -50,8 +51,9 @@ export namespace Chain {
}
}

// TODO: do we want to represent the discovery value and conn type within the type system?
/** The root Rune of Capi's fluent API, with which other core runes can be created */
export class ChainRune<out C extends Chain, out U> extends Rune<C, U> {
/** Get a chain with the specified connection and (optionally) static metadata */
static from<M extends FrameMetadata>(
connect: (signal: AbortSignal) => Connection,
staticMetadata?: M,
Expand All @@ -64,55 +66,78 @@ export class ChainRune<out C extends Chain, out U> extends Rune<C, U> {
return Rune.object({ connection, metadata }).into(this)
}

/** Get the current chain, but with a new inner connection */
with(connect: (signal: AbortSignal) => Connection) {
const connection = ConnectionRune.from(connect)
return Rune.object({ connection, metadata: this.metadata }).into(ChainRune) as ChainRune<C, U>
}

/** The connection with which to communicate with the chain */
connection = this.into(ValueRune<Chain, U>).access("connection").into(ConnectionRune)

/** The chain's metadata */
metadata = this.into(ValueRune).access("metadata")

/** The codec for extrinsics of the current chain */
$extrinsic = Rune.fn($extrinsic).call(this.metadata).into(CodecRune)

/** A stream of latest block numbers */
latestBlockNum = this.connection
.subscribe("chain_subscribeNewHeads", "chain_unsubscribeNewHeads")
.access("number")

/** A stream of latest block hashes */
latestBlockHash = this.connection
.call("chain_getBlockHash", this.latestBlockNum)
.unsafeAs<string>()
.into(BlockHashRune, this)

/** The specified block hash or the latest finalized block hash */
blockHash<X>(...[blockHash]: RunicArgs<X, [blockHash?: string]>) {
return Rune
.resolve(blockHash)
.handle(is(undefined), () => this.connection.call("chain_getFinalizedHead"))
.into(BlockHashRune, this)
}

/** The specified call data */
extrinsic<X>(...args: RunicArgs<X, [call: Chain.Call<C>]>) {
const [call] = RunicArgs.resolve(args)
return call.into(ExtrinsicRune, this.as(ChainRune))
}

/** The specified pallet */
pallet<P extends Chain.PalletName<C>, X>(...[palletName]: RunicArgs<X, [P]>) {
return this.metadata
.access("pallets", palletName)
.unsafeAs<Chain.Pallet<C, P>>()
.into(PalletRune, this.as(ChainRune))
}

/** The prefix of the current chain */
addressPrefix() {
return this
.pallet("System")
.constant("SS58Prefix")
.decoded
}

/** The system version */
chainVersion = this.connection.call("system_version")

// TODO: narrow type in event selection PR (will include the manually-typed `DispatchInfo`)
/** The chain's dispatch info codec */
$dispatchInfo = this.metadata
.access("paths", "frame_support::dispatch::DispatchInfo")
.into(CodecRune)

/** The chain's weight v2 codec */
$weight = this.metadata
.access("paths", "sp_weights::weight_v2::Weight")
.into(CodecRune)
}

// TODO: rework upon resolution of [#811](https://github.com/paritytech/capi/issues/811)
export interface AddressPrefixChain extends Chain {
metadata: FrameMetadata & {
pallets: {
Expand Down
2 changes: 2 additions & 0 deletions fluent/CodecRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export class CodecRune<in I, out O, out U> extends Rune<$.Codec<I, O>, U> {
}

// TODO: eventually, utilize `V` to toggle runtime validation
/** A rune representing the scale-encoded equivalent of the specified value */
encoded<X>(...[value]: RunicArgs<X, [value: I]>) {
return Rune.tuple([this, value]).map(async ([codec, value]) => {
$.assert(codec, value)
return await codec.encodeAsync(value)
}).throws(is($.ScaleError))
}

/** A rune representing the scale-decoded equivalent of the specified scale-encoded value */
decoded<X>(...[value]: RunicArgs<X, [value: Uint8Array]>) {
return Rune
.tuple([this, value]).map(([codec, value]) => codec.decode(value))
Expand Down
3 changes: 3 additions & 0 deletions fluent/ConnectionRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ class RunConnection extends Run<Connection, never> {
}
}

/** a rune representing an RPC connection */
export class ConnectionRune<U> extends Rune<Connection, U> {
static from(init: (signal: AbortSignal) => Connection | Promise<Connection>) {
return Rune.new(RunConnection, init).into(ConnectionRune)
}

/** Make an RPC call */
call<K extends keyof Calls, X>(
callMethod: K,
...args: RunicArgs<X, [...Parameters<Calls[K]>]>
Expand All @@ -40,6 +42,7 @@ export class ConnectionRune<U> extends Rune<Connection, U> {
.throws(is(ConnectionError), is(ServerError))
}

/** Create an RPC subscription */
subscribe<K extends keyof Subscriptions, X>(
subscribeMethod: K,
unsubscribeMethod: Subscription.UnsubscribeMethod<ReturnType<Subscriptions[K]>>,
Expand Down
2 changes: 2 additions & 0 deletions fluent/ConstantRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { Chain } from "./ChainRune.ts"
import { CodecRune } from "./CodecRune.ts"
import { PatternRune } from "./PatternRune.ts"

/** A rune representing an on-chain constant */
export class ConstantRune<
out C extends Chain,
out P extends Chain.PalletName<C>,
out K extends Chain.ConstantName<C, P>,
out U,
> extends PatternRune<Chain.Constant<C, P, K>, C, U> {
/** A rune representing the codec of the constant's type */
$value = this
.into(ValueRune)
.access("codec")
Expand Down
35 changes: 1 addition & 34 deletions fluent/EventsRune.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Chain } from "./ChainRune.ts"
import { PatternRune } from "./PatternRune.ts"

/** A rune representing a list of events in system storage */
export class EventsRune<out C extends Chain, out U>
extends PatternRune<Chain.Storage.Value<C, "System", "Events">, C, U>
{}
Expand All @@ -26,37 +27,3 @@ export interface FinalizationEventPhase {
export interface InitializationEventPhase {
type: "Initialization"
}

// TODO: delete this
export type SystemExtrinsicFailedEvent = Event<{
Copy link
Contributor Author

@harrysolovay harrysolovay Jun 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this into ExtrinsicEventsRune.ts (the only place it's utilized)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be cleaning this up further in a subsequent PR

type: "System"
value: {
type: "ExtrinsicFailed"
dispatchError: DispatchError
dispatchInfo: any // TODO
}
}>
export type DispatchError =
| "Other"
| "CannotLookup"
| "BadOrigin"
| "Module"
| "ConsumerRemaining"
| "NoProviders"
| "TooManyConsumers"
| "Token"
| "Arithmetic"
| "Transactional"
| "Exhausted"
| "Corruption"
| "Unavailable"
| { type: "Module"; value: number }

export function isSystemExtrinsicFailedEvent(event: Event): event is SystemExtrinsicFailedEvent {
if (event.event.type === "System") {
const { value } = event.event
return typeof value === "object" && value !== null && "type" in value
&& value.type === "ExtrinsicFailed"
}
return false
}
31 changes: 21 additions & 10 deletions fluent/ExtrinsicRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ export type SignatureDataFactory<C extends Chain, CU, SU> = (
chain: ChainRune<C, CU>,
) => Rune<SignatureData<C>, SU>

/** A rune representing an unsigned extrinsic */
export class ExtrinsicRune<out C extends Chain, out U> extends PatternRune<Chain.Call<C>, C, U> {
static readonly PROTOCOL_VERSION = 4

/** Get an extrinsic rune of the specified chain and call */
static from<C extends Chain, U, X>(
chain: ChainRune<C, U>,
...args: RunicArgs<X, [call: Chain.Call<C>]>
Expand All @@ -39,6 +41,7 @@ export class ExtrinsicRune<out C extends Chain, out U> extends PatternRune<Chain
return call.into(ExtrinsicRune, chain)
}

/** Get an extrinsic rune of the specified chain and scale-encoded call bytes */
static fromBytes<C extends Chain, U, X>(
chain: ChainRune<C, U>,
...args: RunicArgs<X, [callBytes: Uint8Array]>
Expand All @@ -51,23 +54,33 @@ export class ExtrinsicRune<out C extends Chain, out U> extends PatternRune<Chain
return this.from(chain, call.unsafeAs())
}

/** Get an extrinsic rune of the specified chain and hex-encoded scale-encoded call string */
static fromHex<C extends Chain, U, X>(
chain: ChainRune<C, U>,
...[value]: RunicArgs<X, [value: string]>
) {
return this.fromBytes(chain, Rune.resolve(value).map(hex.decode))
}

/** A rune representing the call data codec */
$callData = this.chain.into(ValueRune).access("metadata", "extrinsic", "call").into(CodecRune)

/** A rune representing the scale-encoded call data */
callData = this.$callData.encoded(this.unsafeAs())

/** A rune representing the hex-encoded scale-encoded call data */
hex = this.callData.map(hex.encode)

/** A rune representing the call hash codec */
$callHash = Rune
.fn(($inner: $.Codec<unknown>) => blake2_256.$hash($inner))
.call(this.$callData.unsafeAs())
.into(CodecRune)

/** A rune representing the call hash */
callHash = this.$callHash.encoded(this)

/** Get a signed extrinsic rune representing this extrinsic, signed in accordance with the specified signature factory */
signed<SU>(signatureFactory: SignatureDataFactory<C, U, SU>) {
return this.chain.$extrinsic
.encoded(Rune.object({
Expand All @@ -79,9 +92,7 @@ export class ExtrinsicRune<out C extends Chain, out U> extends PatternRune<Chain
.into(SignedExtrinsicRune, this.chain)
}

$dispatchInfo = this.chain.metadata
.access("paths", "frame_support::dispatch::DispatchInfo")
.into(CodecRune)
/** Get the dispatch info of the current extrinsic */
dispatchInfo() {
const extrinsic = this.chain.$extrinsic.encoded(Rune.object({
protocolVersion: ExtrinsicRune.PROTOCOL_VERSION,
Expand All @@ -94,24 +105,24 @@ export class ExtrinsicRune<out C extends Chain, out U> extends PatternRune<Chain
const info = this.chain.connection
.call("state_call", "TransactionPaymentApi_query_info", arg)
.map(hex.decode)
return this.$dispatchInfo.decoded(info)
return this.chain.$dispatchInfo.decoded(info)
}

$weight = this.chain.metadata.access("paths", "sp_weights::weight_v2::Weight").into(CodecRune)
/** Get the current extrinsic's weight */
weight() {
return this.dispatchInfo().unsafeAs<any>().into(ValueRune).access("weight")
}

/** Get the hex-encoded scale-encoded value of the current extrinsic's weight */
weightRaw() {
return this.$weight.encoded(this.weight().unsafeAs<never>()).map(hex.encode)
return this.chain.$weight.encoded(this.weight().unsafeAs<never>()).map(hex.encode)
}

/** Get the fee estimation for the current extrinsic rune */
estimate() {
const encoded = this.chain.connection
.call("state_call", "TransactionPaymentApi_query_weight_to_fee", this.weightRaw())
.map(hex.decode)
return Rune
.constant($.u128)
.into(CodecRune)
.decoded(encoded)
return Rune.constant($.u128).into(CodecRune).decoded(encoded)
}
}
Loading