diff --git a/_tasks/download_frame_metadata.ts b/_tasks/download_frame_metadata.ts index ef0600d8e..d2db63db7 100755 --- a/_tasks/download_frame_metadata.ts +++ b/_tasks/download_frame_metadata.ts @@ -8,15 +8,15 @@ const outDir = path.join(Deno.cwd(), "frame_metadata", "_downloaded"); await fs.emptyDir(outDir); await Promise.all( Object.entries({ - acala: known.ACALA_PROXY_WS_URL, - kusama: known.KUSAMA_PROXY_WS_URL, - moonbeam: known.MOONBEAM_PROXY_WS_URL, - polkadot: known.POLKADOT_PROXY_WS_URL, - statemint: known.STATEMINT_PROXY_WS_URL, - subsocial: known.SUBSOCIAL_PROXY_WS_URL, - westend: known.WESTEND_PROXY_WS_URL, - }).map(async ([name, url]) => { - const client = await rpc.client(rpc.beacon([url])); + acala: known.acalaBeacon, + kusama: known.kusamaBeacon, + moonbeam: known.moonbeamBeacon, + polkadot: known.polkadotBeacon, + statemint: known.statemintBeacon, + subsocial: known.subsocialBeacon, + westend: known.westendBeacon, + }).map(async ([name, beacon]) => { + const client = await rpc.client(beacon); assert(!(client instanceof Error)); try { const metadata = await client.call("state_getMetadata", []); diff --git a/effect/std/metadata.ts b/effect/std/metadata.ts index 3330fbb16..800ce9a3f 100644 --- a/effect/std/metadata.ts +++ b/effect/std/metadata.ts @@ -7,7 +7,7 @@ import { rpcCall } from "./rpcCall.ts"; export const metadata = effector( "metadata", - (rpc: EffectorItem, blockHash?: EffectorItem) => { + (rpc: EffectorItem, blockHash?: EffectorItem) => { const rpcCall_ = rpcCall(rpc, "state_getMetadata", blockHash); const result = select(rpcCall_, "result"); return metadataDecoded(result); diff --git a/effect/std/pallet.ts b/effect/std/pallet.ts index 8477fe82e..03365c80a 100644 --- a/effect/std/pallet.ts +++ b/effect/std/pallet.ts @@ -9,7 +9,7 @@ export interface Pallet { export const pallet = effector.sync( "pallet", () => - (rpc: AnyClient, name: string): Pallet => ({ + (rpc: any, name: string): Pallet => ({ rpc, name, }), diff --git a/examples/balance.ts b/examples/balance.ts index 28ddc4ba0..302dc1705 100755 --- a/examples/balance.ts +++ b/examples/balance.ts @@ -3,7 +3,7 @@ import { polkadotBeacon } from "../known/mod.ts"; import * as C from "../mod.ts"; import * as rpc from "../rpc/mod.ts"; -const client = await rpc.client(rpc.beacon(polkadotBeacon)); +const client = await rpc.client(polkadotBeacon); assert(!(client instanceof Error)); const ss58 = C.ss58FromText("13SceNt2ELz3ti4rnQbY1snpYH4XE4fLFsW8ph9rpwJd6HFC"); const pubKey = C.pubKeyFromSs58(ss58); diff --git a/examples/events.ts b/examples/events.ts index 822a12c92..b9c36a52a 100644 --- a/examples/events.ts +++ b/examples/events.ts @@ -3,7 +3,7 @@ import { westendBeacon } from "../known/mod.ts"; import * as C from "../mod.ts"; import * as rpc from "../rpc/mod.ts"; -const client = await rpc.client(rpc.beacon(westendBeacon)); +const client = await rpc.client(westendBeacon); assert(!(client instanceof Error)); const $pallet = C.pallet(client, "System"); const $entry = C.entry($pallet, "Events"); diff --git a/examples/first_ten_keys.ts b/examples/first_ten_keys.ts index fade367f7..75441b018 100644 --- a/examples/first_ten_keys.ts +++ b/examples/first_ten_keys.ts @@ -3,8 +3,9 @@ import { polkadotBeacon } from "../known/mod.ts"; import * as C from "../mod.ts"; import * as rpc from "../rpc/mod.ts"; -const client = await rpc.client(rpc.beacon(polkadotBeacon)); +const client = await rpc.client(polkadotBeacon); assert(!(client instanceof Error)); +client; const pallet = C.pallet(client, "System"); const map = C.map(pallet, "Account"); const result = await C.mapKeys(map, 10).run(); diff --git a/examples/metadata.ts b/examples/metadata.ts index 6b2f8bda6..0c237da9e 100644 --- a/examples/metadata.ts +++ b/examples/metadata.ts @@ -3,7 +3,7 @@ import { polkadotBeacon } from "../known/mod.ts"; import * as C from "../mod.ts"; import * as rpc from "../rpc/mod.ts"; -const client = await rpc.client(rpc.beacon(polkadotBeacon)); +const client = await rpc.client(polkadotBeacon); assert(!(client instanceof Error)); const $metadata = C.metadata(client); const result = await $metadata.run(); diff --git a/examples/rpc/call.ts b/examples/rpc/call.ts index 228f6e5ad..edb6134b5 100755 --- a/examples/rpc/call.ts +++ b/examples/rpc/call.ts @@ -2,7 +2,7 @@ import { assert } from "../../_deps/asserts.ts"; import { polkadotBeacon } from "../../known/mod.ts"; import * as rpc from "../../rpc/mod.ts"; -const client = await rpc.client(rpc.beacon(polkadotBeacon)); +const client = await rpc.client(polkadotBeacon); assert(!(client instanceof Error)); const result = await client.call("state_getMetadata", []); console.log(result); diff --git a/examples/rpc/subscription.ts b/examples/rpc/subscription.ts index 2a2bab6da..a4ace029e 100755 --- a/examples/rpc/subscription.ts +++ b/examples/rpc/subscription.ts @@ -2,7 +2,7 @@ import { assert } from "../../_deps/asserts.ts"; import { polkadotBeacon } from "../../known/mod.ts"; import * as rpc from "../../rpc/mod.ts"; -const client = await rpc.client(rpc.beacon(polkadotBeacon)); +const client = await rpc.client(polkadotBeacon); assert(!(client instanceof Error)); const stop = await client.subscribe("chain_subscribeAllHeads", [], (message) => { console.log(message.params.result); diff --git a/examples/transfer.ts b/examples/transfer.ts index 3863ed667..9129221f9 100644 --- a/examples/transfer.ts +++ b/examples/transfer.ts @@ -7,7 +7,7 @@ import * as rpc from "../rpc/mod.ts"; import * as U from "../util/mod.ts"; const [client, sr25519, hashers] = await Promise.all([ - rpc.client(rpc.beacon(westendBeacon)), + rpc.client(westendBeacon), Sr25519(), Hashers(), ]); diff --git a/known/beacons.ts b/known/beacons.ts index 235b9f2c7..215819bd8 100644 --- a/known/beacons.ts +++ b/known/beacons.ts @@ -1,28 +1,13 @@ -import { EnsureLookup } from "../util/types.ts"; -import { LOOKUP } from "./generated.ts"; - -export type EnsureKnownLookup< - T, - L extends { [N in keyof LOOKUP]: T }, -> = EnsureLookup; - -export const ACALA_PROXY_WS_URL = "wss://acala-polkadot.api.onfinality.io/public-ws"; -export const acalaBeacon = [ACALA_PROXY_WS_URL] as const; - -export const KUSAMA_PROXY_WS_URL = "wss://kusama-rpc.polkadot.io"; -export const kusamaBeacon = [KUSAMA_PROXY_WS_URL] as const; - -export const MOONBEAM_PROXY_WS_URL = "wss://wss.api.moonbeam.network"; -export const moonbeamBeacon = [MOONBEAM_PROXY_WS_URL] as const; - -export const POLKADOT_PROXY_WS_URL = "wss://rpc.polkadot.io"; -export const polkadotBeacon = [POLKADOT_PROXY_WS_URL] as const; - -export const STATEMINT_PROXY_WS_URL = "wss://statemint-rpc.polkadot.io"; -export const statemintBeacon = [STATEMINT_PROXY_WS_URL] as const; - -export const SUBSOCIAL_PROXY_WS_URL = "wss://para.subsocial.network"; -export const subsocialBeacon = [SUBSOCIAL_PROXY_WS_URL] as const; - -export const WESTEND_PROXY_WS_URL = "wss://westend-rpc.polkadot.io"; -export const westendBeacon = [WESTEND_PROXY_WS_URL] as const; +import { beacon } from "../rpc/mod.ts"; +import { KnownRpcMethods } from "./methods.ts"; + +// TODO: swap out `KnownRpcMethods` with narrowed lookups +export const acalaBeacon = beacon( + "wss://acala-polkadot.api.onfinality.io/public-ws", +); +export const kusamaBeacon = beacon("wss://kusama-rpc.polkadot.io"); +export const moonbeamBeacon = beacon("wss://wss.api.moonbeam.network"); +export const polkadotBeacon = beacon("wss://rpc.polkadot.io"); +export const statemintBeacon = beacon("wss://statemint-rpc.polkadot.io"); +export const subsocialBeacon = beacon("wss://para.subsocial.network"); +export const westendBeacon = beacon("wss://westend-rpc.polkadot.io"); diff --git a/known/mod.ts b/known/mod.ts index 318743f02..4ba8185c6 100644 --- a/known/mod.ts +++ b/known/mod.ts @@ -1,15 +1,3 @@ -import { Branded } from "../util/mod.ts"; - -// TODO: narrowly type the template literal of `ProxyWsUrlBeacon` -declare const _proxyWsUrl: unique symbol; -export type ProxyWsUrl = Branded<`wss://${string}`, typeof _proxyWsUrl>; - -// TODO: use branded type to represent validated chain spec string -declare const _chainSpec: unique symbol; -export type ChainSpec = Branded; - -export type Beacon = ProxyWsUrl | ChainSpec; - export * from "./beacons.ts"; export * from "./generated.ts"; export * from "./methods.ts"; diff --git a/rpc/Base.ts b/rpc/Base.ts index 6356ef08f..fb0276ca9 100644 --- a/rpc/Base.ts +++ b/rpc/Base.ts @@ -18,10 +18,10 @@ export type Subscription = { [_N]: NotificationResult export interface ClientProps< M extends AnyMethods, - Beacon, + DiscoveryValue, ParsedError extends Error, > { - beacon: Beacon; + discoveryValue: DiscoveryValue; hooks?: { send?: (message: InitMessage) => void; receive?: (message: IngressMessage) => void; @@ -51,7 +51,17 @@ export abstract class Client< * * @param egressMessage the message you wish to send to the RPC server */ - abstract send: (egressMessage: InitMessage) => void; + send = (egressMessage: InitMessage): void => { + this.props.hooks?.send?.(egressMessage); + this._send(egressMessage); + }; + + /** + * The provider-specific send implementation + * + * @param egressMessage the message you wish to send to the RPC server + */ + abstract _send: (egressMessage: InitMessage) => void; /** * Parse messages returned from the RPC server (this includes RPC server errors) diff --git a/rpc/Beacon.ts b/rpc/Beacon.ts new file mode 100644 index 000000000..9694e06e4 --- /dev/null +++ b/rpc/Beacon.ts @@ -0,0 +1,17 @@ +import * as rpc from "../rpc/mod.ts"; + +export type DiscoveryValues = [string, ...string[]]; + +declare const _M: unique symbol; + +export class Beacon { + declare [_M]: M; + discoveryValues; + + constructor(...discoveryValues: DiscoveryValues) { + this.discoveryValues = discoveryValues; + } +} +export function beacon(...discoveryValues: DiscoveryValues): Beacon { + return new Beacon(...discoveryValues); +} diff --git a/rpc/auto.ts b/rpc/auto.ts index 60841ff55..3c6716018 100644 --- a/rpc/auto.ts +++ b/rpc/auto.ts @@ -1,51 +1,35 @@ -import { ErrorCtor } from "../util/mod.ts"; +import { ErrorCtor, isWsUrl } from "../util/mod.ts"; import { AnyMethods } from "./Base.ts"; +import { Beacon } from "./Beacon.ts"; import { FailedToAddChainError, FailedToStartSmoldotError, SmoldotClient } from "./smoldot.ts"; import { FailedToOpenConnectionError, ProxyWsUrlClient } from "./ws.ts"; -type DiscoveryValues = readonly [string, ...string[]]; -// TODO: replace with better branded types -type Beacon = DiscoveryValues & { _beacon: { supported: M } }; -export function beacon( - discoveryValues: DiscoveryValues, -): Beacon { - return discoveryValues as Beacon; -} - -// TODO: use branded beacon types instead of string -// TODO: dyn import smoldot and provider if chain spec is provided -// TODO: handle retry -// TODO: narrow to `[string, ...string[]]` -export async function client(beacon: Beacon): Promise< +export async function client( + beacon: Beacon, + currentDiscoveryValueI = 0, +): Promise< | SmoldotClient | ProxyWsUrlClient | FailedToOpenConnectionError | FailedToStartSmoldotError | FailedToAddChainError - | AllBeaconsErroredError + | BeaconFailedError > { - const [e0, ...rest] = beacon; - const result = await (async () => { - if (isWsUrl(e0)) { - return ProxyWsUrlClient.open({ beacon: e0 }); - } else { - return SmoldotClient.open({ beacon: e0 }); - } - })(); - if (result instanceof Error) { - if (rest.length > 0) { - return await client(rest as unknown as Beacon); + const currentDiscoveryValue = beacon.discoveryValues[currentDiscoveryValueI]; + if (currentDiscoveryValue) { + const result = await (async () => { + if (isWsUrl(currentDiscoveryValue)) { + return ProxyWsUrlClient.open({ discoveryValue: currentDiscoveryValue }); + } else { + return SmoldotClient.open({ discoveryValue: currentDiscoveryValue }); + } + })(); + if (result instanceof Error) { + return await client(beacon, currentDiscoveryValueI + 1); } - return new AllBeaconsErroredError(); + return result; } - // TODO: fix - return result as any; -} - -// TODO: validate chain spec as well -// TODO: better validation -function isWsUrl(inQuestion: string): boolean { - return inQuestion.startsWith("wss://"); + return new BeaconFailedError(); } -export class AllBeaconsErroredError extends ErrorCtor("AllBeaconsErrored") {} +export class BeaconFailedError extends ErrorCtor("AllBeaconsErrored") {} diff --git a/rpc/mod.ts b/rpc/mod.ts index 39e30315a..de12a3dc7 100644 --- a/rpc/mod.ts +++ b/rpc/mod.ts @@ -6,6 +6,7 @@ export type AnyClient = ProxyWsUrlClient | SmoldotClient export * from "./auto.ts"; export * from "./Base.ts"; +export * from "./Beacon.ts"; export * from "./messages.ts"; export * from "./smoldot.ts"; export * from "./ws.ts"; diff --git a/rpc/smoldot.ts b/rpc/smoldot.ts index ba67f73db..f986e12e9 100644 --- a/rpc/smoldot.ts +++ b/rpc/smoldot.ts @@ -32,7 +32,7 @@ export class SmoldotClient const client = new SmoldotClient(props); // TODO: wire up `onError` client.#chain = await inner.addChain({ - chainSpec: props.beacon, + chainSpec: props.discoveryValue, jsonRpcCallback: client.onMessage, }); return client; @@ -55,7 +55,7 @@ export class SmoldotClient } }; - send = (egressMessage: InitMessage): void => { + _send = (egressMessage: InitMessage): void => { this.#chain?.sendJsonRpc(JSON.stringify(egressMessage)); }; diff --git a/rpc/ws.ts b/rpc/ws.ts index 23d2c8226..71dd556fc 100644 --- a/rpc/ws.ts +++ b/rpc/ws.ts @@ -16,7 +16,7 @@ export class ProxyWsUrlClient props: B.ClientProps, ): Promise | FailedToOpenConnectionError> => { const client = new ProxyWsUrlClient(props); - const ws = new WebSocket(props.beacon); + const ws = new WebSocket(props.discoveryValue); client.#ws = ws; ws.addEventListener("error", client.onError); ws.addEventListener("message", client.onMessage); @@ -60,7 +60,7 @@ export class ProxyWsUrlClient return pending; }; - send = (egressMessage: InitMessage): void => { + _send = (egressMessage: InitMessage): void => { this.#ws?.send(JSON.stringify(egressMessage)); }; diff --git a/util/discovery_value_validation.ts b/util/discovery_value_validation.ts new file mode 100644 index 000000000..98ca8ec34 --- /dev/null +++ b/util/discovery_value_validation.ts @@ -0,0 +1,6 @@ +// TODO: validate chain spec as well +// TODO: better ws validation + +export function isWsUrl(inQuestion: string): boolean { + return inQuestion.startsWith("wss://"); +} diff --git a/util/load_env.ts b/util/loadEnv.ts similarity index 100% rename from util/load_env.ts rename to util/loadEnv.ts diff --git a/util/mod.ts b/util/mod.ts index d8357b93e..71d4eca2e 100644 --- a/util/mod.ts +++ b/util/mod.ts @@ -1,6 +1,6 @@ export * from "./branded.ts"; +export * from "./discovery_value_validation.ts"; export * from "./ErrorCtor.ts"; export * from "./fn.ts"; export * as hex from "./hex.ts"; -export * from "./load_env.ts"; export * from "./types.ts";